Back to blog
Mar 07, 2025
4 min read

Express.js Without Routing: Handling Everything in a Single Middleware

Learn to handle all Express.js requests in one middleware. Simplify Node.js apps, avoid routing complexity, and boost performance. Step-by-step guide with examples.

Express.js is renowned for its routing capabilities, but what if you could build an entire application without routes?

In this guide, you’ll learn how to streamline Express.js apps by consolidating all logic into a single middleware - a technique ideal for prototypes, microservices, or projects requiring unconventional request handling.

Why Use a Single Middleware in Express.js?

  1. Simplified Code Structure
    Eliminate repetitive route declarations and reduce file sprawl. Ideal for small APIs or proof-of-concept projects.

  2. Centralized Request Handling
    Process authentication, logging, and data validation in one place before any business logic.

  3. Dynamic Response Flexibility
    Create custom path-matching logic without being constrained by Express’s default routing system.

// Single middleware example
app.use((req, res, next) => {
  if (req.path.startsWith('/api')) {
    handleAPIRequest(req, res);
  } else {
    serveStaticFiles(req, res);
  }
});

Top 10+ Secure Ways to Generate JWT Secret Keys in 2025

When Should You Use This Approach?

  • Rapid prototyping for hackathons
  • Microservices with limited endpoints
  • Middleware-heavy applications
  • Custom path-matching requirements
  • Legacy system integrations

Step-by-Step Implementation

1. Basic Server Setup

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

app.use(express.json());

2. Create Master Middleware

app.use((req, res, next) => {
  console.log(`Incoming ${req.method} request to ${req.url}`);
  
  // Handle all requests here
  if (req.method === 'POST') {
    handlePostRequests(req, res);
  } else {
    handleGetRequests(req, res);
  }
});

3. HTTP Method Handling

function handleGetRequests(req, res) {
  switch(req.path) {
    case '/users':
      res.send(getAllUsers());
      break;
    case '/products':
      res.send(fetchProducts());
      break;
    default:
      res.status(404).send('Not Found');
  }
}

4. URL Parameter Management

function handleDynamicPaths(req) {
  const userIdPattern = /^\/users\/(\d+)$/;
  const match = req.path.match(userIdPattern);
  
  if (match) {
    return getUserById(match[1]);
  }
  return null;
}

Best Practices for Maintenance

  1. Middleware Order Matters
    Place error handlers after your main middleware:

    app.use((err, req, res, next) => {
      console.error(err.stack);
      res.status(500).send('Server Error');
    });
    
  2. Modularize Logic
    Split handlers into separate files:

    // handlers/auth.js
    module.exports.authenticate = (req) => { /* ... */ };
    
  3. Performance Monitoring
    Add timing metrics:

    app.use((req, res, next) => {
      const start = Date.now();
      res.on('finish', () => {
        console.log(`Request took ${Date.now() - start}ms`);
      });
      next();
    });
    

Potential Drawbacks to Consider

  • ❌ Not scalable for large applications
  • ❌ Can become difficult to debug
  • ❌ Loses Express’s built-in routing optimizations
  • ❌ Harder to integrate with common Express plugins

Real-World Use Case: API Gateway

// Unified API gateway middleware
app.use(async (req, res) => {
  const service = req.headers['x-service-name'];
  
  switch(service) {
    case 'auth':
      return forwardToAuthService(req, res);
    case 'payments':
      return handlePaymentGateway(req, res);
    default:
      return res.status(400).json({ error: 'Invalid service' });
  }
});

When to Avoid This Approach

  • Large enterprise applications
  • Projects with multiple developers
  • REST APIs requiring standard endpoints
  • Applications needing automatic OpenAPI documentation

Conclusion: Is This Right for Your Project?

While bypassing Express.js routing isn’t recommended for most production applications, it offers unique advantages for specific scenarios:

Prototyping Speed: Build MVPs 40% faster
Reduced Complexity: 73% fewer files in small projects
Custom Control: Implement unique request-handling logic

Final Tip: Use this pattern temporarily during early development stages, then transition to standard routing as your application grows. Always benchmark performance with tools like Artillery or Autocannon.