Skip to content
This repository was archived by the owner on Jul 17, 2026. It is now read-only.

Mounting and routing

Eugene Lazutkin edited this page Apr 19, 2026 · 1 revision

Mounting and routing

The adapter matches routes against req.path. Any strategy that gets the right path into req.path before the adapter runs will work — Express's app.use(prefix, middleware) is the idiomatic choice.

Why mount with a prefix?

The adapter's matchRoute call treats the path as rooted at the collection:

  • / → list / create / bulk-delete
  • /-by-names → collection method
  • /earth → item
  • /earth/-clone → item method

If you mount the adapter top-level with app.use(createExpressAdapter(planets)), a request for /planets/earth doesn't match — the adapter sees /planets/earth as a two-segment path (unknown shape) and hands back to next(). That's usually not what you want.

Express's prefix-mount rewrites req.path so the adapter sees /earth:

app.use('/planets', createExpressAdapter(planets));
// GET /planets/earth → adapter sees GET /earth

Unlike Koa's ecosystem (where koa-mount is a separate package), Express's app.use(path, …) strips the mount prefix natively — no extra dependency required.

Multiple collections

Mount as many adapters as you need. Each gets its own prefix and Adapter instance:

const planets = new Adapter({client, table: 'planets', keyFields: ['name']});
const moons   = new Adapter({client, table: 'moons',   keyFields: ['planet', 'name']});
const probes  = new Adapter({client, table: 'probes',  keyFields: ['id']});

const app = express();
app.use(express.json());
app.use('/planets', createExpressAdapter(planets));
app.use('/moons',   createExpressAdapter(moons, {
  keyFromPath: raw => {
    const [planet, name] = raw.split(':');
    return {planet, name};
  }
}));
app.use('/probes',  createExpressAdapter(probes));
app.listen(3000);

Because the adapter hands back to next() on unknown routes, mount order doesn't matter within an app that doesn't share prefixes.

Using express.Router()

express.Router() composes sub-routers. Mount the adapter inside a router when you already group related routes that way:

import express from 'express';

const apiV1 = express.Router();
apiV1.get('/health', (req, res) => res.json({ok: true}));
apiV1.use('/planets', createExpressAdapter(planets));

app.use('/api/v1', apiV1);
// GET /api/v1/planets/earth → adapter sees GET /earth

app.use('/api/v1', router) strips /api/v1, and the router's own '/planets' mount strips /planets. The adapter sees paths relative to the collection root.

Scoping body parsers per adapter

Global express.json() runs before everything. If one adapter needs a larger body cap than the rest, scope the parser:

// Global — 256 KiB default
app.use(express.json({limit: '256kb'}));
app.use('/planets', createExpressAdapter(planets));

// Bulk endpoint — mount BEFORE the global parser, stream its own body
// (adapter's maxBodyBytes kicks in because req.body is undefined here).
app.use('/bulk', createExpressAdapter(bulk, {maxBodyBytes: 16 * 1024 * 1024}));

See Body parsing for the complete decision flow.

Mounting adjacent middleware

Other middleware can run before or after the adapter. Because the adapter calls next() on unknown routes, anything mounted afterward still sees requests that don't match the route pack.

app.use('/planets', createExpressAdapter(planets));
app.use('/planets', (req, res, next) => {
  // Hit for /planets/special-endpoint (unknown shape, adapter delegated)
  if (req.path === '/special-endpoint') {
    return res.json({custom: true});
  }
  next();
});

Use this to add per-collection endpoints that aren't part of the standard route pack.

Response logging / metrics

Standard Express middleware composes naturally — the adapter writes via res.status() and res.json(), so downstream loggers / metrics middleware can observe via res.on('finish', …) or the morgan pattern:

app.use((req, res, next) => {
  const start = Date.now();
  res.on('finish', () => {
    console.log(`${req.method} ${req.originalUrl} ${res.statusCode} ${Date.now() - start}ms`);
  });
  next();
});
app.use('/planets', createExpressAdapter(planets));

This is the main reason the adapter uses res.status().json() instead of writing to the raw socket directly — everything in the Express ecosystem that inspects the response sees the expected state.

Compression, ETag, CORS

All standard Express middleware works:

import compression from 'compression';
import cors from 'cors';

app.use(compression());
app.use(cors());
app.use(express.json());
app.use('/planets', createExpressAdapter(planets));

The adapter sets JSON bodies via res.json() and lets Express's response pipeline handle serialization, ETags (when enabled), compression, and content-type negotiation.

Clone this wiki locally