Documenting file upload APIs with Swagger and OpenAPI
Documenting your file upload and download APIs is essential for ensuring maintainability and ease of use in web services. In this post, we'll explore how to effectively document your file APIs using Swagger and the OpenAPI Specification, enabling developers to seamlessly interact with your RESTful services.
Introduction
In modern web development, file APIs are critical to handling file uploads and downloads in web services. Clear documentation is essential for helping developers quickly integrate and maintain these endpoints. This guide demonstrates how to document your file upload and download endpoints using Swagger tools with the OpenAPI Specification, which helps you create interactive and standardized documentation for your REST API.
Understanding file APIs
What is a file API?
A file API is a set of programmatic interfaces that enable the uploading and downloading of files over the internet. These APIs allow clients to interact with server-side resources to store and retrieve files such as images, documents, or any binary data. Properly designed file APIs are vital for web services that handle file transfers, ensuring efficient and secure communication between clients and servers.
What are Swagger and OpenAPI?
OpenAPI is a language-agnostic standard for describing HTTP APIs, originally based on the Swagger Specification. Swagger now names a set of tools that work with OpenAPI. This example uses OpenAPI 3.0.3 to describe endpoints, parameters, and responses. Swagger UI and Swagger Editor can use that definition to provide interactive documentation and simplify testing.
Benefits of documenting APIs
- Improved Developer Experience: Clear documentation helps developers understand how to use your API without confusion.
- Standardization: Using a standard like OpenAPI promotes consistency across your API documentation.
- Automatic Documentation Generation: Tools can automatically generate interactive documentation from your OpenAPI definition.
- Simplified Maintenance: Updating the documentation is easier when it is defined in a structured, machine-readable format.
- Enhanced API Testing: Developers can utilize tools like Postman or cURL to test your file upload and download endpoints effectively.
Setting up Swagger in your project
We'll use a Node.js project with Express for this example.
Initialize the project
mkdir file-api-swagger
cd file-api-swagger
npm init -y
Install dependencies
npm install express@4.22.2 swagger-ui-express@5.0.0 swagger-jsdoc@6.2.8 multer@2.3.0
Multer 2.3.0 includes the August 2026 security fixes. Keep upload dependencies patched as new advisories are published.
Basic Express Server setup
Use Node.js 22 or newer. Create index.js and supply a strong FILE_API_KEY through your
environment. The API key grants access to this demonstration's shared file store; a multi-user
service needs per-file ownership checks as well. Add subsequent server snippets before app.listen.
const express = require('express')
const { timingSafeEqual } = require('node:crypto')
const app = express()
const port = 3000
app.use(express.json())
const apiKey = process.env.FILE_API_KEY
if (!apiKey) throw new Error('FILE_API_KEY is required')
const expectedKey = Buffer.from(apiKey)
function requireApiKey(req, res, next) {
const suppliedKey = Buffer.from(req.get('X-API-Key') || '')
if (suppliedKey.length !== expectedKey.length || !timingSafeEqual(suppliedKey, expectedKey)) {
return res.status(401).json({ error: 'Unauthorized' })
}
next()
}
app.use(['/upload', '/uploads', '/download'], requireApiKey)
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`)
})
Documenting file upload endpoints
Setting up Multer for file uploads
const multer = require('multer')
const path = require('node:path')
const uploadsDir = path.join(__dirname, 'uploads')
const upload = multer({
dest: uploadsDir,
limits: {
fileSize: 5 * 1024 * 1024, // 5MB limit
files: 10,
fields: 0,
},
fileFilter: (req, file, cb) => {
const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf']
if (!allowedTypes.includes(file.mimetype)) {
return cb(Object.assign(new Error('Unsupported file type'), { code: 'UNSUPPORTED_FILE_TYPE' }))
}
cb(null, true)
},
})
Single file upload endpoint
app.post('/upload', upload.single('file'), (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' })
}
res.json({
message: 'File uploaded successfully',
file: {
id: req.file.filename,
name: req.file.originalname,
size: req.file.size,
mimetype: req.file.mimetype,
},
})
} catch (error) {
res.status(500).json({ error: 'File upload failed' })
}
})
Documenting with Swagger
Adding Swagger configuration
Create a swagger.js file:
const swaggerJsDoc = require('swagger-jsdoc')
const swaggerUi = require('swagger-ui-express')
const swaggerDefinition = {
openapi: '3.0.3',
info: {
title: 'File Upload API',
version: '1.0.0',
description: 'API documentation for file upload and download endpoints',
},
servers: [
{
url: 'http://localhost:3000',
},
],
components: {
securitySchemes: {
ApiKeyAuth: {
type: 'apiKey',
in: 'header',
name: 'X-API-Key',
},
},
},
}
const options = {
swaggerDefinition,
apis: ['./index.js'],
}
const swaggerSpec = swaggerJsDoc(options)
module.exports = {
swaggerUi,
swaggerSpec,
}
Integrate Swagger UI into the server
In your index.js:
const { swaggerUi, swaggerSpec } = require('./swagger')
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec))
Start your server:
node index.js
Visit http://localhost:3000/api-docs to view the interactive Swagger UI documentation.
Adding Swagger comments to document the endpoint
Place the following comment immediately above the existing single-file upload route in index.js.
Do not register the route a second time. Declaring ApiKeyAuth documents the requirement; the
requireApiKey middleware above is what actually enforces it.
/**
* @swagger
* /upload:
* post:
* security:
* - ApiKeyAuth: []
* summary: Uploads a file.
* requestBody:
* required: true
* content:
* multipart/form-data:
* schema:
* type: object
* properties:
* file:
* type: string
* format: binary
* responses:
* 200:
* description: File uploaded successfully.
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* file:
* type: object
* properties:
* id:
* type: string
* name:
* type: string
* size:
* type: number
* mimetype:
* type: string
* 400:
* description: No file uploaded
* 401:
* description: Missing or invalid API key
* 413:
* description: File exceeds the size limit
* 415:
* description: Unsupported file type
* 500:
* description: File upload failed
*/
Multiple file uploads
/**
* @swagger
* /uploads:
* post:
* security:
* - ApiKeyAuth: []
* summary: Uploads multiple files.
* requestBody:
* required: true
* content:
* multipart/form-data:
* schema:
* type: object
* properties:
* files:
* type: array
* items:
* type: string
* format: binary
* responses:
* 200:
* description: Files uploaded successfully.
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* files:
* type: array
* items:
* type: object
* properties:
* id:
* type: string
* name:
* type: string
* size:
* type: number
* mimetype:
* type: string
* 400:
* description: Missing files or invalid upload
* 401:
* description: Missing or invalid API key
* 413:
* description: A file exceeds the size limit
* 415:
* description: Unsupported file type
*/
app.post('/uploads', upload.array('files', 10), (req, res) => {
try {
if (!req.files || req.files.length === 0) {
return res.status(400).json({ error: 'No files uploaded' })
}
res.json({
message: 'Files uploaded successfully',
files: req.files.map((file) => ({
id: file.filename,
name: file.originalname,
size: file.size,
mimetype: file.mimetype,
})),
})
} catch (error) {
res.status(500).json({ error: 'File upload failed' })
}
})
Testing file upload APIs with Postman
Postman is a popular tool for testing APIs, including file upload endpoints. To test your file upload endpoints:
- Open Postman and create a new POST request.
- Enter your API endpoint URL (e.g.,
http://localhost:3000/upload). - In the Body tab, select
form-dataand add a key namedfile. - Change the type of the
filekey to File and select a file from your system. - Add the
X-API-Keyheader with your API key. - Send the request and observe the response.
Alternatively, you can test using cURL:
curl -fsSL -F 'file=@/path/to/your/file.jpg' -H 'X-API-Key: YOUR_API_KEY' http://localhost:3000/upload
Documenting file download endpoints
Use the generated id returned by an upload as filename, not the original client filename.
The allowlist and Express's root option prevent requests from selecting files outside the upload
directory. Multer's MIME filter trusts client metadata, so files remain private downloads, not
publicly executable content. Validate content with a suitable parser/scanner before processing it.
/**
* @swagger
* /download/{filename}:
* get:
* security:
* - ApiKeyAuth: []
* summary: Downloads a file.
* parameters:
* - in: path
* name: filename
* required: true
* schema:
* type: string
* description: Generated file ID returned by an upload.
* responses:
* 200:
* description: File downloaded successfully.
* content:
* application/octet-stream:
* schema:
* type: string
* format: binary
* 404:
* description: File not found
* 401:
* description: Missing or invalid API key
* 500:
* description: Download failed
*/
app.get('/download/:filename', (req, res, next) => {
if (!/^[a-f0-9]{32}$/.test(req.params.filename)) {
return res.status(404).json({ error: 'File not found' })
}
res.download(req.params.filename, { root: uploadsDir }, (error) => {
if (!error) return
if (res.headersSent) return next(error)
res.status(404).json({ error: 'File not found' })
})
})
// Register after all routes, including both upload routes.
app.use((error, req, res, next) => {
if (res.headersSent) return next(error)
if (error.code === 'UNSUPPORTED_FILE_TYPE') {
return res.status(415).json({ error: 'Unsupported file type' })
}
if (error instanceof multer.MulterError) {
return res.status(error.code === 'LIMIT_FILE_SIZE' ? 413 : 400).json({ error: 'Upload rejected' })
}
console.error('File API request failed')
res.status(500).json({ error: 'File request failed' })
})
Security considerations
API key management
The API-key middleware must run before upload processing. Rate limiting adds a separate abuse control; it is not authentication. Install the optional middleware:
npm install express-rate-limit@8 helmet@8
Register the limiter before the routes and upload middleware, not after them:
const { rateLimit } = require('express-rate-limit')
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per window
})
app.use(limiter)
Secure file transfers
To ensure secure file transfers, follow these best practices:
- Use HTTPS for all API endpoints to encrypt data in transit.
- Apply middleware like Helmet to set secure HTTP headers.
- Validate file types and enforce file size limits, as demonstrated in the Multer configuration.
- Regularly update dependencies and monitor for security advisories.
For example, set security headers on the file API before registering its routes. Keep Swagger UI's page policy separate and configure it for the assets/scripts that your deployment actually uses:
const helmet = require('helmet')
app.use(['/upload', '/uploads', '/download'], helmet())
Conclusion
Documenting your file upload and download APIs using Swagger and the OpenAPI Specification not only clarifies how to interact with your services but also simplifies maintenance and testing. In this guide, we set up an Express server, integrated Swagger for interactive documentation, and implemented essential security measures. For more advanced file handling solutions, consider exploring Uppy, which offers modern, modular approaches to file uploads.
Happy coding!
