Accept Multipart Form Data and Save Files with @koa/multer
Handling File Uploads
Use @koa/multer to parse multipart/form-data requests in Koa 2, enforce file size limits, and either save uploads to disk or stream them directly to cloud storage.
What you'll learn
- Wire @koa/multer as route-level middleware to parse uploaded files
- Enforce file type and size limits to protect the server
- Access parsed file metadata and buffer or stream the upload to its destination
File uploads arrive as multipart/form-data requests. @koa/multer is the
official Koa wrapper around the multer library and handles parsing, validation,
and storage in one middleware call.
Installation
npm install @koa/multer multer Disk Storage
The simplest approach saves uploaded files to a local directory.
import multer from '@koa/multer';
import path from 'node:path';
const upload = multer({
dest: 'uploads/', // directory for saved files
limits: { fileSize: 5 * 1024 * 1024 }, // 5 MB cap
fileFilter(_ctx, file, cb) {
const allowed = ['.jpg', '.jpeg', '.png', '.webp'];
const ext = path.extname(file.originalname).toLowerCase();
cb(null, allowed.includes(ext));
},
}); Use the middleware on the specific route that expects a file. The single()
helper parses one file from the named field.
import Router from '@koa/router';
const router = new Router();
router.post('/avatar', upload.single('avatar'), async (ctx) => {
const { file } = ctx;
if (!file) ctx.throw(400, 'No file uploaded or invalid type');
ctx.body = {
filename: file.filename,
size: file.size,
mimetype: file.mimetype,
};
}); Multiple Files
router.post('/gallery', upload.array('photos', 10), async (ctx) => {
// ctx.files is an array of file descriptors
ctx.body = ctx.files.map((f) => ({ filename: f.filename, size: f.size }));
}); Memory Storage (Stream to Cloud)
To forward the upload to S3 or another service without writing to disk, switch to memory storage and pipe the buffer yourself.
const memUpload = multer({ storage: multer.memoryStorage() });
router.post('/document', memUpload.single('doc'), async (ctx) => {
const { buffer, originalname, mimetype } = ctx.file;
// Example: upload to S3 using AWS SDK
await s3.putObject({
Bucket: process.env.S3_BUCKET,
Key: `docs/${Date.now()}-${originalname}`,
Body: buffer,
ContentType: mimetype,
}).promise();
ctx.status = 201;
ctx.body = { message: 'Uploaded successfully' };
}); Size and Type Limits Summary
| Option | Where | Effect |
|---|---|---|
| limits.fileSize | multer config | Rejects files over N bytes |
| limits.files | multer config | Limits number of uploaded files |
| fileFilter | multer config | Rejects disallowed mime types / extensions |
| upload.single(field) | route | Enforces exactly one file field |
| upload.array(field, n) | route | Enforces up to n files on one field |
Up Next
Learn how to serve static assets and single-page app fallback routes with
koa-static and koa-send.