Skip to content

Middleware

Middleware are functions that run before your route handlers. They can read and modify req/res, end the request, or call next() to pass control forward.


How Middleware Works

Request → middleware 1 → middleware 2 → route handler → Response

Register global middleware with app.use() before your routes:

ts
import mimi, { json, cors, requestLogger } from 'mimi.js';

const app = mimi();

app.use(json());          // 1st — parses body
app.use(cors());          // 2nd — adds CORS headers
app.use(requestLogger);   // 3rd — logs the request

app.get('/ping', (req, res) => res.json({ ok: true }));

app.listen(3000);

Order matters — middleware runs in registration order.


Built-in Middleware

mimi.js ships 7 production-ready middleware factories.

json(opts?)

Parses JSON request bodies into req.body. Skips GET, HEAD, OPTIONS automatically.

ts
import { json } from 'mimi.js';

app.use(json());
app.use(json({ limit: '5mb' }));
OptionTypeDefaultDescription
limitstring'1mb'Maximum request body size

Returns 400 Bad Request if the body is malformed JSON.


urlencoded(opts?)

Parses URL-encoded form bodies into req.body.

ts
import { urlencoded } from 'mimi.js';

app.use(urlencoded({ extended: true }));
OptionTypeDefaultDescription
extendedbooleantruetrue uses qs (nested objects); false uses URLSearchParams (flat)
limitstring'1mb'Maximum body size

cors(opts?)

Adds CORS headers. Handles preflight OPTIONS requests automatically.

ts
import { cors } from 'mimi.js';

app.use(cors());                                         // allow all origins
app.use(cors({ origin: 'https://myapp.com' }));          // specific origin
app.use(cors({
  origin: (origin) => origin.endsWith('.myapp.com') ? origin : false,
  credentials: true,
}));
OptionTypeDefault
originstring | string[] | (origin: string) => string | false'*'
methodsstring[]['GET','HEAD','PUT','PATCH','POST','DELETE']
allowedHeadersstring[]mirrors request headers
exposedHeadersstring[][]
credentialsbooleanfalse
maxAgenumber

security(opts?)

Sets security-related HTTP response headers. All enabled by default.

ts
import { security } from 'mimi.js';

app.use(security());
app.use(security({ contentSecurityPolicy: false, xFrameOptions: 'DENY' }));
OptionDefault
contentSecurityPolicy"default-src 'self'"
xFrameOptions'SAMEORIGIN'
xContentTypeOptionsenabled
xXssProtectionenabled
dnsPrefetchControlenabled
permittedCrossDomainPolicies'none'
downloadOptionsenabled
removePoweredByenabled

Pass false for any option to disable that header.


serveStatic(root, opts?)

Serves files from a directory. Handles GET and HEAD only.

ts
import { serveStatic } from 'mimi.js';
import path from 'path';

app.use(serveStatic(path.join(process.cwd(), 'public')));
app.use('/assets', serveStatic('./static', { maxAge: 86400 }));
OptionTypeDefault
maxAgenumber | string0
indexstring | false'index.html'
dotfiles'allow' | 'deny' | 'ignore''ignore'
etagbooleantrue
lastModifiedbooleantrue

requestLogger

Logs each request's method, URL, status, and elapsed time as structured JSON (pino). Not a factory — import directly.

ts
import { requestLogger } from 'mimi.js';
app.use(requestLogger);
json
{ "level": 30, "method": "GET", "url": "/users/1", "status": 200, "ms": 4 }

Control log level with the LOG_LEVEL environment variable (default info).


customParser

Parses application/custom bodies and sets req.body = {}. Useful as a base for custom content-type handlers.

ts
import { customParser } from 'mimi.js';
app.use(customParser);

Writing Custom Middleware

Any (req, res, next) function is valid middleware:

ts
import type { RequestHandler } from 'mimi.js';

const timing: RequestHandler = (req, res, next) => {
  const start = Date.now();
  res.on('finish', () => {
    console.log(`${req.method} ${req.url}${Date.now() - start}ms`);
  });
  next();
};

app.use(timing);

Attaching Data with req.locals

Use req.locals to pass data between middleware and route handlers without modifying req types:

ts
import type { RequestHandler } from 'mimi.js';

const loadUser: RequestHandler = async (req, res, next) => {
  const token = req.get('Authorization')?.replace('Bearer ', '');
  if (token) {
    req.locals.user = await getUserFromToken(token);
  }
  next();
};

app.use(loadUser);

app.get('/me', (req, res) => {
  const user = req.locals.user;
  if (!user) return res.status(401).json({ error: 'Not authenticated' });
  res.json({ user });
});

Scoped Middleware

Apply middleware only to specific routes:

ts
const adminOnly: RequestHandler = (req, res, next) => {
  if (req.locals.role !== 'admin') {
    return res.status(403).json({ error: 'Forbidden' });
  }
  next();
};

// Inline — just this route
app.get('/admin/stats', adminOnly, (req, res) => {
  res.json({ users: 1000 });
});

// Prefix — all /admin routes
app.use('/admin', adminOnly);

Conditional Middleware

Skip middleware for specific paths using an early return:

ts
const skipLogging: RequestHandler = (req, res, next) => {
  if (req.url === '/health') return next();
  requestLogger(req, res, next);
};

app.use(skipLogging);

Error Middleware

A 4-argument handler (err, req, res, next) is recognized as an error handler. Place it after all routes:

ts
import type { ErrorHandler } from 'mimi.js';

const errorHandler: ErrorHandler = (err, req, res, next) => {
  const status = (err as any).status ?? 500;
  res.status(status).json({ error: err.message, path: req.url });
};

app.use(errorHandler);

TIP

For global error handling, prefer app.setErrorHandler() — it's simpler and doesn't need to be placed last.

Released under the ISC License.