Handling File Uploads

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.

4 min read Level 2/5 #koa#data#uploads
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

OptionWhereEffect
limits.fileSizemulter configRejects files over N bytes
limits.filesmulter configLimits number of uploaded files
fileFiltermulter configRejects disallowed mime types / extensions
upload.single(field)routeEnforces exactly one file field
upload.array(field, n)routeEnforces 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.

Serving Static Files →