Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/juanjh1/asimilation/llms.txt

Use this file to discover all available pages before exploring further.

Middleware functions execute before your route handlers, allowing you to add authentication, logging, validation, and other cross-cutting concerns to your application.

What is Middleware?

Middleware is a function that executes in the request/response cycle before your route handler. Middleware can:
  • Modify the request or response objects
  • Execute code before the route handler
  • Pass data to subsequent middleware or the route handler
  • Terminate the request early (e.g., for authentication failures)

Middleware Function Signature

Middleware functions have three parameters:
import { ArgumentedIncomingMessageAbc, ArgumentedServerResponseAbc } from '@asimilation/core';

function myMiddleware(
  req: ArgumentedIncomingMessageAbc,
  res: ArgumentedServerResponseAbc,
  next: (error?: Error) => void
) {
  // Middleware logic here
  
  // Call next() to pass control to the next middleware or route handler
  next();
}
Always call next() to continue the middleware pipeline. Forgetting to call next() will hang the request.

Adding Global Middleware

Global middleware runs on every request. Add it using MiddlewarePipeline:
import { MiddlewarePipeline } from '@asimilation/core';

MiddlewarePipeline.addMiddleware((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next();
});
Global middleware is managed by the MiddlewareManager singleton (see src/core/middleware-manager.ts:8-75).

Global Middleware Examples

import { MiddlewarePipeline } from '@asimilation/core';

MiddlewarePipeline.addMiddleware((req, res, next) => {
  const start = Date.now();
  
  console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
  
  // Continue to next middleware
  next();
  
  // This runs after the response
  const duration = Date.now() - start;
  console.log(`Request completed in ${duration}ms`);
});

Route-Specific Middleware

Apply middleware to specific routes using the handlers option:
import { url } from '@asimilation/core';

function authMiddleware(req, res, next) {
  const token = req.headers.authorization;
  
  if (!token) {
    res.sendJson({ error: 'Unauthorized' }, 401);
    return; // Don't call next() - terminate here
  }
  
  // Validate token and attach user to request
  (req as any).user = { id: 1, name: 'John' };
  next();
}

url.addPath('/admin/dashboard', (req, res) => {
  const user = (req as any).user;
  res.sendJson({ message: `Welcome ${user.name}` }, 200);
}, {
  methods: ['GET'],
  handlers: [authMiddleware] // Route-specific middleware
});
Route-specific middleware is defined in the PathKwargs type (see src/core/type.ts:16-19).

Multiple Route Middlewares

Chain multiple middlewares for a single route:
import { url } from '@asimilation/core';

function authenticate(req, res, next) {
  // Check authentication
  const token = req.headers.authorization;
  if (!token) {
    res.sendJson({ error: 'Unauthorized' }, 401);
    return;
  }
  (req as any).user = { id: 1, role: 'admin' };
  next();
}

function authorize(req, res, next) {
  // Check authorization
  const user = (req as any).user;
  if (user.role !== 'admin') {
    res.sendJson({ error: 'Forbidden' }, 403);
    return;
  }
  next();
}

function validateInput(req, res, next) {
  // Validate request body
  const body = (req as any).body;
  if (!body || !body.name) {
    res.sendJson({ error: 'Name is required' }, 400);
    return;
  }
  next();
}

url.addPath('/admin/users', (req, res) => {
  res.sendJson({ message: 'User created' }, 201);
}, {
  methods: ['POST'],
  handlers: [authenticate, authorize, validateInput]
});

Middleware Execution Order

Middleware executes in a specific order:
1

Global middleware

All global middleware added via MiddlewarePipeline.addMiddleware() runs first, in the order they were added.
2

Route-specific middleware

Route-specific middleware from the handlers array runs next, in array order.
3

Route handler

Finally, your route handler function executes.
import { MiddlewarePipeline, url } from '@asimilation/core';

// 1. Global middleware (runs first)
MiddlewarePipeline.addMiddleware((req, res, next) => {
  console.log('Global middleware 1');
  next();
});

MiddlewarePipeline.addMiddleware((req, res, next) => {
  console.log('Global middleware 2');
  next();
});

// 2. Route with specific middleware
url.addPath('/test', (req, res) => {
  console.log('Route handler');
  res.sendJson({ message: 'Done' }, 200);
}, {
  handlers: [
    (req, res, next) => {
      console.log('Route middleware 1');
      next();
    },
    (req, res, next) => {
      console.log('Route middleware 2');
      next();
    }
  ]
});

// Output for GET /test:
// Global middleware 1
// Global middleware 2
// Route middleware 1
// Route middleware 2
// Route handler
The middleware execution order is managed by the router (see src/core/router-manager.ts:122-145).

Async Middleware

Middleware can be asynchronous:
import { MiddlewarePipeline } from '@asimilation/core';
import { MiddlewareFunctionAsync } from 'asimilation/types';

const asyncMiddleware: MiddlewareFunctionAsync = async (req, res, next) => {
  try {
    // Async operation (e.g., database query)
    const user = await fetchUserFromDatabase();
    (req as any).user = user;
    next();
  } catch (error) {
    res.sendJson({ error: 'Database error' }, 500);
  }
};

MiddlewarePipeline.addMiddleware(asyncMiddleware);
Both MiddlewareFunction and MiddlewareFunctionAsync are supported (see src/core/type.ts:8-10).

Passing Data Between Middleware

Middleware can attach data to the request object for later use:
import { url } from '@asimilation/core';

function attachTimestamp(req, res, next) {
  (req as any).timestamp = Date.now();
  next();
}

function attachUser(req, res, next) {
  (req as any).user = { id: 1, name: 'Alice' };
  next();
}

url.addPath('/profile', (req, res) => {
  const timestamp = (req as any).timestamp;
  const user = (req as any).user;
  
  res.sendJson({
    user,
    requestTime: new Date(timestamp).toISOString()
  }, 200);
}, {
  handlers: [attachTimestamp, attachUser]
});
For TypeScript, consider extending the request interface to add type safety for custom properties.

Terminating the Request Early

Middleware can end the request without calling the route handler:
function rateLimiter(req, res, next) {
  const ip = req.socket.remoteAddress;
  const requestCount = getRateLimitCount(ip);
  
  if (requestCount > 100) {
    // Terminate here - don't call next()
    res.sendJson({ error: 'Rate limit exceeded' }, 429);
    return;
  }
  
  next(); // Continue to next middleware/handler
}

Error Handling in Middleware

Handle errors gracefully in middleware:
import { MiddlewarePipeline } from '@asimilation/core';

// Built-in error handling middleware
MiddlewarePipeline.addMiddleware((req, res, next) => {
  try {
    next();
  } catch (error) {
    console.error(error);
    res.sendJson({ message: 'Internal Server error' }, 500);
  }
});
Asimilation includes a default error-handling middleware (see src/default/middleware/error-handling.ts:5-18).

Common Middleware Patterns

function requireAuth(req, res, next) {
  const token = req.headers.authorization?.replace('Bearer ', '');
  
  if (!token) {
    res.sendJson({ error: 'No token provided' }, 401);
    return;
  }
  
  try {
    const decoded = verifyJWT(token);
    (req as any).user = decoded;
    next();
  } catch (error) {
    res.sendJson({ error: 'Invalid token' }, 401);
  }
}

How Middleware Works Internally

Asimilation uses a dispatcher pattern to run middleware:
  1. Middleware functions are stored in an array
  2. A recursive dispatcher function calls each middleware in order
  3. Each middleware must call next() to invoke the dispatcher with the next index
  4. The pipeline continues until all middleware has executed
See the implementation in src/core/middleware-manager.ts:23-44.

Best Practices

1

Always call next()

Unless you’re terminating the request, always call next():
function myMiddleware(req, res, next) {
  // Do work...
  next(); // Required!
}
2

Check response state

Before writing to the response, check if it’s already ended:
if (res.writableEnded) {
  return; // Response already sent
}
res.sendJson({ data: 'value' }, 200);
3

Order matters

Add middleware in the correct order. Authentication should come before authorization:
handlers: [authenticate, authorize, validate]
4

Keep middleware focused

Each middleware should have a single responsibility:
// Good: Focused middleware
function logRequest(req, res, next) { /* ... */ }
function parseBody(req, res, next) { /* ... */ }

// Less ideal: Does too much
function doEverything(req, res, next) { /* ... */ }

What’s Next?

Error Handling

Learn how to handle errors in your middleware and routes

API Reference

Explore the complete middleware API documentation

Build docs developers (and LLMs) love