Skip to main content

Overview

Middleware functions in Express are functions that have access to the request object (req), response object (res), and the next middleware function in the application’s request-response cycle. TailStack comes pre-configured with essential middleware for production-ready applications.

Built-in Middleware Stack

The middleware stack is configured in app.ts:
packages/core/source/Server/src/app.ts
import express from 'express';
import cookieParser from 'cookie-parser';
import { corsMiddleware } from './middlewares/cors';
import routes from './routes';

const app = express();

// Middlewares
app.use(corsMiddleware);
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());

// Routes
app.use('/api', routes);

Middleware Execution Order

1

CORS Middleware

Handles cross-origin requests, must run first to set headers before other processing
2

JSON Parser

Parses incoming JSON request bodies (Content-Type: application/json)
3

URL-Encoded Parser

Parses URL-encoded bodies from forms (Content-Type: application/x-www-form-urlencoded)
4

Cookie Parser

Parses Cookie header and populates req.cookies
5

Routes

Application routes are processed after all middleware
Middleware order matters! Always place CORS middleware first and route handlers last.

CORS Middleware

TailStack includes a custom CORS middleware:
packages/core/source/Server/src/middlewares/cors.ts
import { Request, Response, NextFunction } from 'express';
import { config } from '../config';

export const corsMiddleware = (req: Request, res: Response, next: NextFunction) => {
  res.header('Access-Control-Allow-Origin', config.corsOrigin);
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
  
  if (req.method === 'OPTIONS') {
    return res.sendStatus(200);
  }
  
  next();
};

CORS Configuration

The CORS middleware:
  • Allows requests from the origin specified in CORS_ORIGIN environment variable
  • Supports GET, POST, PUT, DELETE, and OPTIONS methods
  • Allows common headers including Authorization for authentication
  • Handles preflight OPTIONS requests automatically

CORS Constants

CORS configuration is centralized in constants:
packages/node/src/constant/cors.ts
export const CORS_METHODS = 'GET, POST, PUT, DELETE, OPTIONS';
export const CORS_HEADERS = 'Origin, X-Requested-With, Content-Type, Accept, Authorization';

Configure CORS Origin

Set the allowed origin in your environment variables:
.env
# Development
CORS_ORIGIN=http://localhost:5173

# Production
CORS_ORIGIN=https://yourdomain.com
For multiple origins, consider using the cors npm package which supports origin arrays and dynamic origin validation.

Body Parser Middleware

JSON Parser

app.use(express.json());
Parses JSON payloads and makes them available in req.body:
// POST /api/users
// Body: {"name": "John", "email": "[email protected]"}

const { name, email } = req.body;

URL-Encoded Parser

app.use(express.urlencoded({ extended: true }));
Parses URL-encoded form data:
// POST /api/users
// Content-Type: application/x-www-form-urlencoded
// Body: name=John&[email protected]

const { name, email } = req.body;
The extended: true option allows parsing rich objects and arrays using the qs library.
app.use(cookieParser());
Parses cookies from the Cookie header:
// Access cookies
const sessionId = req.cookies.sessionId;

// Set cookies
res.cookie('sessionId', '123456', {
  httpOnly: true,
  secure: process.env.NODE_ENV === 'production',
  maxAge: 24 * 60 * 60 * 1000, // 24 hours
});

Creating Custom Middleware

Create custom middleware for cross-cutting concerns:

Authentication Middleware

src/middlewares/auth.ts
import { Request, Response, NextFunction } from 'express';

export const authMiddleware = async (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  try {
    const token = req.headers.authorization?.split(' ')[1];
    
    if (!token) {
      res.status(401).json({ error: 'Unauthorized', message: 'No token provided' });
      return;
    }
    
    // Verify token (implement your own logic)
    const user = await verifyToken(token);
    req.user = user;
    
    next();
  } catch (error) {
    res.status(401).json({ error: 'Unauthorized', message: 'Invalid token' });
  }
};

Request Logging Middleware

src/middlewares/logger.ts
import { Request, Response, NextFunction } from 'express';

export const loggerMiddleware = (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  const start = Date.now();
  
  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(`${req.method} ${req.path} ${res.statusCode} - ${duration}ms`);
  });
  
  next();
};

Rate Limiting Middleware

src/middlewares/rateLimit.ts
import rateLimit from 'express-rate-limit';

export const rateLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per windowMs
  message: 'Too many requests from this IP, please try again later.',
  standardHeaders: true,
  legacyHeaders: false,
});

Applying Middleware

Global Middleware

Apply to all routes:
src/app.ts
import { loggerMiddleware } from './middlewares/logger';

app.use(loggerMiddleware);

Route-Specific Middleware

Apply to specific routes:
src/routes/users.routes.ts
import { Router } from 'express';
import { authMiddleware } from '../middlewares/auth';
import { UserController } from '../controller/user.controller';

const router = Router();

// Public route
router.post('/login', UserController.login);

// Protected routes
router.get('/profile', authMiddleware, UserController.getProfile);
router.put('/profile', authMiddleware, UserController.updateProfile);

export default router;

Multiple Middleware

Chain multiple middleware functions:
router.post(
  '/users',
  authMiddleware,
  validateUser,
  checkPermissions,
  UserController.createUser
);

Error Handling Middleware

Error-handling middleware has four arguments:
src/middlewares/errorHandler.ts
import { Request, Response, NextFunction } from 'express';

export const errorHandler = (
  err: Error,
  req: Request,
  res: Response,
  next: NextFunction
) => {
  console.error('Error:', err);
  
  res.status(500).json({
    error: 'Internal Server Error',
    message: process.env.NODE_ENV === 'production' 
      ? 'Something went wrong' 
      : err.message,
  });
};
Register error handlers last:
src/app.ts
import { errorHandler } from './middlewares/errorHandler';

// Routes
app.use('/api', routes);

// Error handler (must be last)
app.use(errorHandler);

Security Middleware

For production applications, consider adding:

Helmet

Sets security-related HTTP headers:
import helmet from 'helmet';

app.use(helmet());

Express Rate Limit

Prevents abuse:
import rateLimit from 'express-rate-limit';

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
});

app.use('/api', limiter);

Compression

Compresses response bodies:
import compression from 'compression';

app.use(compression());

Best Practices

Middleware executes in the order it’s registered. Place CORS first, error handlers last.
Don’t forget to call next() to pass control to the next middleware, unless you’re ending the request.
Pass errors to next(error) to trigger error-handling middleware.
Wrap async middleware in try/catch blocks to handle promise rejections.
Each middleware should do one thing well (Single Responsibility Principle).

Next Steps

Routing

Learn how to apply middleware to specific routes

Configuration

Configure CORS origins and other middleware settings

Server Setup

See how middleware is integrated into the app

Build docs developers (and LLMs) love