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.