Middlewares

Like any express application, middleware functions are a core concept in Dotpress. They allow you to run logic before your route handlers, whether it's for authentication, logging, request mutation, or any other purpose.

Global middlewares

Global middleware runs for every route. They can be registered in createApp options.

// index.ts
import { createApp } from 'dotpress'

await createApp({
  middlewares: [
    async ({ logger, requestId }) => {
      logger.info(`Request started: ${requestId}`);
    }
  ]
});

You can add as many as you want — they will run in order before every handler.

Route-level middlewares

You can define middleware specific to a single route, or route groups.

import { forbiddenError } from 'dotpress';

const requireAdmin = async ({ user }) => {
  if (!user?.roles?.includes('admin')) {
    throw forbiddenError('Admins only');
  }
};

defineRoute({
  path: '/admin',
  middlewares: [requireAdmin],
  handler: async () => {
    return { access: 'granted' };
  }
});

Notes:

  • Middleware functions receive the full RequestContext object.
  • Middlewares can return errors to block the handler or the next middleware.
  • If all middlewares complete successfully, the handler is executed.
  • Middlewares can mutate ctx: RequestContext object properties (e.g., attach a user, etc.).

Express middlewares

Or course, you also have the ability to install and use regular Express middlewares. Dotpress provides you with 2 hooks for that: useBeforeRoutes (for most middlewares) and useAfterRoutes (to be used to declare error handlers).

Usage example:

import { createApp } from 'dotpress'
import compression from 'compression'
import helmet from 'helmet'
import * as Sentry from '@sentry/node';

await createApp({
  useBeforeRoutes: (app) => {
    app.use(helmet())
    app.use(compression())
  },
  useAfterRoutes: (app) => {
    Sentry.setupExpressErrorHandler(app);
  }
});

Response filters

Response Filters are designed as plugin-level or application-level functions that allow you to transform the output of route handlers before it's returned to the client.

This feature could be useful for:

  • Wrapping all responses in a consistent format ({ data, meta })
  • Injecting metadata like request IDs or timestamps
  • Performing centralized output transformations (e.g. masking, localization)

Usage example:

import { registerResponseFilter } from 'dotpress'

registerResponseFilter((ctx, result) => {
  return {
    requestId: ctx.requestId,
    data: result
  }
})

Note that a response filter can be sync or async.