Skip to main content

Routing and Middleware in Express

Created: May 8, 2026 Larry Qu 7 min read

Advanced routing and middleware patterns are essential for building scalable Express applications. This article covers advanced techniques.

Introduction

Advanced routing and middleware provide:

  • Organized code structure
  • Reusable middleware
  • Complex routing patterns
  • Request processing pipelines
  • Clean architecture

Understanding advanced patterns helps you:

  • Build maintainable applications
  • Implement authentication
  • Handle cross-cutting concerns
  • Create flexible APIs
  • Scale applications

Advanced Routing

Route Parameters and Patterns

const express = require('express');
const app = express();

// ✅ Good: Route parameters
app.get('/users/:id', (req, res) => {
  const userId = req.params.id;
  res.json({ id: userId });
});

// ✅ Good: Multiple parameters
app.get('/users/:userId/posts/:postId', (req, res) => {
  const { userId, postId } = req.params;
  res.json({ userId, postId });
});

// ✅ Good: Optional parameters
app.get('/files/:name?', (req, res) => {
  const fileName = req.params.name || 'default';
  res.json({ file: fileName });
});

// ✅ Good: Regex patterns
app.get('/users/:id(\\d+)', (req, res) => {
  const userId = req.params.id;
  res.json({ id: userId });
});

// ✅ Good: Query parameters
app.get('/search', (req, res) => {
  const { q, page = 1, limit = 10 } = req.query;
  res.json({ query: q, page, limit });
});

// ✅ Good: Wildcard routes
app.get('/api/*', (req, res) => {
  res.json({ path: req.params[0] });
});

// ✅ Good: Route chaining
app.route('/users/:id')
  .get((req, res) => {
    res.json({ id: req.params.id });
  })
  .put((req, res) => {
    res.json({ updated: req.params.id });
  })
  .delete((req, res) => {
    res.json({ deleted: req.params.id });
  });

Router Objects

// routes/users.js
const express = require('express');
const router = express.Router();

// ✅ Good: Router middleware
router.use((req, res, next) => {
  console.log('User route accessed');
  next();
});

// ✅ Good: Router parameters
router.param('id', (req, res, next, id) => {
  console.log('User ID:', id);
  req.userId = id;
  next();
});

// ✅ Good: Router routes
router.get('/', (req, res) => {
  res.json([{ id: 1, name: 'John' }]);
});

router.get('/:id', (req, res) => {
  res.json({ id: req.userId, name: 'John' });
});

router.post('/', (req, res) => {
  res.status(201).json(req.body);
});

module.exports = router;

// routes/posts.js
const express = require('express');
const router = express.Router({ mergeParams: true });

router.get('/', (req, res) => {
  const userId = req.params.userId;
  res.json([{ id: 1, userId, title: 'Post 1' }]);
});

module.exports = router;

// app.js
const express = require('express');
const userRoutes = require('./routes/users');
const postRoutes = require('./routes/posts');

const app = express();

app.use('/users', userRoutes);
app.use('/users/:userId/posts', postRoutes);

app.listen(3000);

Middleware Patterns

Middleware Chains

const express = require('express');
const app = express();

// ✅ Good: Middleware chain
const authenticate = (req, res, next) => {
  const token = req.headers.authorization;
  if (!token) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  req.user = { id: 1, name: 'John' };
  next();
};

const authorize = (role) => {
  return (req, res, next) => {
    if (req.user.role !== role) {
      return res.status(403).json({ error: 'Forbidden' });
    }
    next();
  };
};

const validate = (schema) => {
  return (req, res, next) => {
    const { error } = schema.validate(req.body);
    if (error) {
      return res.status(400).json({ error: error.message });
    }
    next();
  };
};

// ✅ Good: Apply middleware chain
app.post(
  '/admin/users',
  authenticate,
  authorize('admin'),
  validate(userSchema),
  (req, res) => {
    res.json({ message: 'User created' });
  }
);

// ✅ Good: Conditional middleware
app.get('/data', (req, res, next) => {
  if (req.query.admin) {
    authenticate(req, res, next);
  } else {
    next();
  }
}, (req, res) => {
  res.json({ data: 'public data' });
});

Custom Middleware

// middleware/logger.js
function logger(req, res, next) {
  const start = Date.now();

  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(`${req.method} ${req.url} - ${res.statusCode} (${duration}ms)`);
  });

  next();
}

module.exports = logger;

// middleware/requestId.js
const { v4: uuidv4 } = require('uuid');

function requestId(req, res, next) {
  req.id = uuidv4();
  res.setHeader('X-Request-ID', req.id);
  next();
}

module.exports = requestId;

// middleware/cors.js
function cors(req, res, next) {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type');

  if (req.method === 'OPTIONS') {
    return res.sendStatus(200);
  }

  next();
}

module.exports = cors;

// middleware/errorHandler.js
function errorHandler(err, req, res, next) {
  console.error('Error:', err);

  const status = err.status || 500;
  const message = err.message || 'Internal server error';

  res.status(status).json({
    error: {
      status,
      message,
      requestId: req.id
    }
  });
}

module.exports = errorHandler;

// app.js
const express = require('express');
const logger = require('./middleware/logger');
const requestId = require('./middleware/requestId');
const cors = require('./middleware/cors');
const errorHandler = require('./middleware/errorHandler');

const app = express();

app.use(logger);
app.use(requestId);
app.use(cors);
app.use(express.json());

app.get('/', (req, res) => {
  res.json({ message: 'Hello' });
});

app.use(errorHandler);

app.listen(3000);

Authentication and Authorization

JWT Authentication

// middleware/auth.js
const jwt = require('jsonwebtoken');

const authenticate = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];

  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (err) {
    res.status(401).json({ error: 'Invalid token' });
  }
};

const authorize = (roles) => {
  return (req, res, next) => {
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ error: 'Forbidden' });
    }
    next();
  };
};

module.exports = { authenticate, authorize };

// routes/auth.js
const express = require('express');
const jwt = require('jsonwebtoken');
const router = express.Router();

router.post('/login', (req, res) => {
  const { email, password } = req.body;

  // Verify credentials
  const user = { id: 1, email, role: 'user' };

  const token = jwt.sign(user, process.env.JWT_SECRET, {
    expiresIn: '1h'
  });

  res.json({ token });
});

module.exports = router;

// routes/protected.js
const express = require('express');
const { authenticate, authorize } = require('../middleware/auth');
const router = express.Router();

router.get('/profile', authenticate, (req, res) => {
  res.json({ user: req.user });
});

router.get('/admin', authenticate, authorize(['admin']), (req, res) => {
  res.json({ message: 'Admin panel' });
});

module.exports = router;

Request Validation

Input Validation Middleware

// middleware/validate.js
const { body, validationResult } = require('express-validator');

const validate = (req, res, next) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }
  next();
};

module.exports = validate;

// routes/users.js
const express = require('express');
const { body } = require('express-validator');
const validate = require('../middleware/validate');
const router = express.Router();

router.post(
  '/',
  [
    body('email').isEmail().normalizeEmail(),
    body('password').isLength({ min: 8 }),
    body('name').trim().notEmpty()
  ],
  validate,
  (req, res) => {
    res.status(201).json(req.body);
  }
);

router.put(
  '/:id',
  [
    body('email').optional().isEmail(),
    body('password').optional().isLength({ min: 8 })
  ],
  validate,
  (req, res) => {
    res.json({ id: req.params.id, ...req.body });
  }
);

module.exports = router;

Error Handling Middleware

Comprehensive Error Handling

// middleware/errorHandler.js
class AppError extends Error {
  constructor(message, status) {
    super(message);
    this.status = status;
  }
}

const errorHandler = (err, req, res, next) => {
  console.error('Error:', err);

  const status = err.status || 500;
  const message = err.message || 'Internal server error';

  res.status(status).json({
    error: {
      status,
      message,
      ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
    }
  });
};

const asyncHandler = (fn) => {
  return (req, res, next) => {
    Promise.resolve(fn(req, res, next)).catch(next);
  };
};

module.exports = { AppError, errorHandler, asyncHandler };

// routes/users.js
const express = require('express');
const { AppError, asyncHandler } = require('../middleware/errorHandler');
const router = express.Router();

router.get('/:id', asyncHandler(async (req, res) => {
  const user = await getUser(req.params.id);
  if (!user) {
    throw new AppError('User not found', 404);
  }
  res.json(user);
}));

module.exports = router;

// app.js
const express = require('express');
const { errorHandler } = require('./middleware/errorHandler');
const app = express();

app.use(express.json());

// Routes
app.use('/users', require('./routes/users'));

// Error handling
app.use((req, res) => {
  res.status(404).json({ error: 'Route not found' });
});

app.use(errorHandler);

app.listen(3000);

Best Practices

  1. Order middleware correctly:
    // ✅ Good: Correct order
    app.use(express.json());
    app.use(authenticate);
    app.use(routes);
    app.use(errorHandler);
    
    // ❌ Bad: Wrong order
    app.use(errorHandler);
    app.use(routes);
    app.use(authenticate);
    ```javascript
    
  2. Use async/await with error handling:
    // ✅ Good: Async handler
    const asyncHandler = (fn) => (req, res, next) => {
      Promise.resolve(fn(req, res, next)).catch(next);
    };
    
    // ❌ Bad: No error handling
    app.get('/', async (req, res) => {
      const data = await fetchData();
      res.json(data);
    });
    ```javascript
    
  3. Separate concerns:
    // ✅ Good: Separate middleware
    app.use(authenticate);
    app.use(authorize);
    app.use(validate);
    
    // ❌ Bad: Mixed concerns
    app.use((req, res, next) => {
      // Authentication, authorization, validation all here
    });
    

Summary

Advanced routing and middleware are essential. Key takeaways:

  • Use route parameters and patterns
  • Create reusable middleware
  • Implement authentication and authorization
  • Validate input properly
  • Handle errors comprehensively
  • Organize code by concern
  • Use async/await with error handling
  • Build scalable applications

Next Steps

Resources

Comments

Share this article

Scan to read on mobile