Middleware
Sometimes you need to run logic before a procedure handler executes — logging the request, checking rate limits, transforming input, or injecting additional context. Middleware chains let you compose these concerns without cluttering your handler code.
Using Middleware
Section titled “Using Middleware”Add middleware with .use():
import { procedure } from '@veloxts/velox';
const loggedProcedure = procedure() .use(async ({ ctx, next }) => { console.log('Request started'); const result = await next(); console.log('Request completed'); return result; }) .query(handler);Middleware Signature
Section titled “Middleware Signature”type Middleware = (opts: { ctx: Context; input: unknown; next: () => Promise<unknown>;}) => Promise<unknown>;Common Patterns
Section titled “Common Patterns”Logging
Section titled “Logging”const loggingMiddleware = async ({ ctx, next }) => { const start = Date.now(); const result = await next(); const duration = Date.now() - start; console.log(`${ctx.request.method} ${ctx.request.url} - ${duration}ms`); return result;};Timing
Section titled “Timing”const timingMiddleware = async ({ ctx, next }) => { const start = process.hrtime.bigint(); const result = await next(); const end = process.hrtime.bigint(); ctx.reply.header('X-Response-Time', `${Number(end - start) / 1e6}ms`); return result;};Error Wrapping
Section titled “Error Wrapping”const errorMiddleware = async ({ next }) => { try { return await next(); } catch (error) { // Log to error tracking service console.error('Procedure error:', error); throw error; }};Context Enhancement
Section titled “Context Enhancement”const tenantMiddleware = async ({ ctx, next }) => { const tenantId = ctx.request.headers['x-tenant-id']; ctx.tenant = await getTenant(tenantId); return next();};Chaining Middleware
Section titled “Chaining Middleware”Middleware runs in order:
const securedProcedure = procedure() .use(loggingMiddleware) // 1. Log request .use(authMiddleware) // 2. Verify auth .use(rateLimitMiddleware) // 3. Check rate limit .guard(authenticated) // 4. Require auth .query(handler); // 5. Execute handlerReusable Procedure Bases
Section titled “Reusable Procedure Bases”Create middleware-enhanced base procedures:
// Base with loggingexport const loggedProcedure = procedure().use(loggingMiddleware);
// Base with authexport const authedProcedure = procedure() .guard(authenticated) .use(loggingMiddleware);
// Use in proceduresexport const userProcedures = procedures('users', { listUsers: loggedProcedure .query(async ({ ctx }) => { const users = await ctx.db.user.findMany(); return resourceCollection(users, UserSchema.authenticated); }),
getProfile: authedProcedure .query(({ ctx }) => { return resource(ctx.user, UserSchema.authenticated); }),});Middleware vs Guards
Section titled “Middleware vs Guards”| Feature | Middleware | Guards |
|---|---|---|
| Purpose | Request processing | Authorization |
| Can modify context | Yes | No |
| Can modify response | Yes | No |
| Failure behavior | Throw or continue | Block request |
Use middleware for cross-cutting concerns (logging, timing). Use guards for authorization (authenticated, hasRole).
Post-Handler Hooks
Section titled “Post-Handler Hooks”.useAfter() is the counterpart to .use() — it runs after the handler succeeds:
procedure() .mutation(async ({ input, ctx }) => { return ctx.db.order.create({ data: input }); }) .useAfter(({ input, result, ctx }) => { console.log(`Order ${result.id} created by ${ctx.user.id}`); })Hooks run after events are emitted (if any). Errors are caught and logged — they never fail the response. The return value is ignored: hooks cannot modify the result. Multiple .useAfter() calls chain in registration order.
See Business Logic for the full picture including pipelines and events.
Related Content
Section titled “Related Content”- Business Logic - Post-handler hooks, pipelines, events
- Guards - Authorization
- Procedures - Procedure API