Scanning file uploads in Next.js Pages Router (API Routes)
Next.js Pages Router API Routes live at pages/api/*.ts. Unlike
App Router Route Handlers, they receive Node.js
IncomingMessage / ServerResponse objects, so you
can use Node.js-native tools like Multer directly — no need to convert a
Web API File to a buffer first.
This guide is specifically for the Pages Router (pages/
directory). If you use the App Router (app/ directory), see
Scanning file uploads in Next.js (App Router)
instead.
Disable Next.js's built-in body parser
By default, Next.js parses the request body before your handler runs.
Multer needs the raw stream, so you must disable the built-in parser by
exporting a config object from the route file:
// pages/api/upload.ts
export const config = {
api: {
bodyParser: false,
},
};
Without this, Multer will not receive any data and the request will appear empty.
Install
npm install pompelmi multer npm install --save-dev @types/multer
Multer helper for Next.js
Multer uses Express-style middleware callbacks. Next.js API Routes are plain Node.js handlers, not Express — so you need a small wrapper that promisifies the middleware:
// lib/multer.ts
import multer from 'multer';
import type { NextApiRequest, NextApiResponse } from 'next';
import os from 'os';
const upload = multer({
dest: os.tmpdir(),
limits: { fileSize: 50 * 1024 * 1024 },
});
type MiddlewareFn = (
req: NextApiRequest,
res: NextApiResponse,
next: (err?: unknown) => void
) => void;
export function runMiddleware(
req: NextApiRequest,
res: NextApiResponse,
fn: MiddlewareFn
): Promise<void> {
return new Promise((resolve, reject) => {
fn(req, res, (err?: unknown) => {
if (err) reject(err);
else resolve();
});
});
}
export const uploadSingle = upload.single('file') as unknown as MiddlewareFn;
Complete API Route
// pages/api/upload.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { scan, Verdict } from 'pompelmi';
import fs from 'fs';
import { runMiddleware, uploadSingle } from '@/lib/multer';
export const config = {
api: { bodyParser: false },
};
interface MulterRequest extends NextApiRequest {
file?: Express.Multer.File;
}
export default async function handler(
req: MulterRequest,
res: NextApiResponse
) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed.' });
}
try {
// Run Multer — writes the file to /tmp
await runMiddleware(req, res, uploadSingle);
} catch (err) {
return res.status(400).json({ error: 'File upload failed.' });
}
if (!req.file) {
return res.status(400).json({ error: 'No file provided.' });
}
const tmpPath = req.file.path;
try {
const verdict = await scan(tmpPath);
if (verdict === Verdict.Malicious) {
return res.status(400).json({ error: 'Malware detected. Upload rejected.' });
}
if (verdict === Verdict.ScanError) {
return res.status(422).json({
error: 'Scan could not complete. Upload rejected as a precaution.',
});
}
// File is clean — move to permanent storage
return res.status(200).json({ status: 'ok', name: req.file.originalname });
} catch (err) {
const message = err instanceof Error ? err.message : 'Scan failed';
return res.status(500).json({ error: message });
} finally {
if (fs.existsSync(tmpPath)) {
fs.unlinkSync(tmpPath);
}
}
}
POST to /api/upload with a file containing the
EICAR string should return HTTP 400 with "Malware detected".
TypeScript types for pompelmi
pompelmi does not ship type declarations. Add this declaration file to your project:
types/pompelmi.d.tsdeclare module 'pompelmi' {
interface ScanOptions {
host?: string;
port?: number;
timeout?: number;
}
const Verdict: Readonly<{
Clean: unique symbol;
Malicious: unique symbol;
ScanError: unique symbol;
}>;
function scan(filePath: string, options?: ScanOptions): Promise<symbol>;
export { scan, Verdict };
}
Add the types directory to your tsconfig.json
typeRoots or ensure it is included in the project files.
Next steps
- Using the App Router instead? See Scanning file uploads in Next.js (App Router).
- Uploading to S3 after scanning? See Scanning files before uploading to AWS S3.
- Running pompelmi in Docker? See Running pompelmi with ClamAV in Docker Compose.