10+ Express.js Middleware Interview Questions 2025: Execution Order, Error Handling & Types

·8 min read
expressinterview-questionsmiddlewarenodejsbackenderror-handling

Express.js powers over 6.3 million websites and handles billions of requests daily—all orchestrated through a deceptively simple concept: middleware. One forgotten next() call, and requests hang forever. One misplaced error handler, and your entire error strategy breaks.

Table of Contents

  1. Middleware Fundamentals Questions
  2. Middleware Execution Questions
  3. Error Handling Questions
  4. Middleware Types Questions
  5. Data Passing Questions
  6. Quick Reference

Middleware Fundamentals Questions

These questions test your understanding of what middleware is and how it works.

What is middleware in Express.js?

Middleware functions are functions that execute during the request-response cycle. They have access to the request object, response object, and a next function. Middleware can execute code, modify the request and response, end the cycle by sending a response, or call next() to pass control to the next middleware in the stack.

Express middleware works like a pipeline. Each request flows through a series of middleware functions in the order they're defined. Each function can process the request, modify it, or decide whether to pass it along.

How does the middleware pipeline work?

const express = require('express');
const app = express();
 
// 1. Logging middleware (runs on every request)
app.use((req, res, next) => {
    console.log(`${req.method} ${req.path}`);
    next(); // Pass to next middleware
});
 
// 2. Body parsing middleware
app.use(express.json());
 
// 3. Authentication middleware
const auth = (req, res, next) => {
    const token = req.headers.authorization;
    if (!token) {
        return res.status(401).json({error: 'No token'});
    }
    req.user = verifyToken(token);
    next();
};
 
// 4. Protected route with middleware
app.get('/api/profile', auth, (req, res) => {
    res.json({user: req.user});
});
 
// 5. Error handling middleware (MUST be last)
app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).json({error: 'Something went wrong'});
});

Walk through the flow:

  1. Request arrives at /api/profile
  2. Logging middleware runs, logs the request, calls next()
  3. express.json() parses the body, calls next()
  4. auth middleware checks token - if missing, sends 401 and stops
  5. If token is valid, attaches user to req and calls next()
  6. Route handler sends the response
  7. If any middleware throws an error, error handler catches it

Middleware Execution Questions

These questions test your understanding of middleware execution order and common pitfalls.

What happens if you don't call next() in middleware?

If you don't call next() and don't send a response, the request hangs indefinitely. The client eventually times out. You must either call next() to pass control to the next middleware, or send a response.

app.use((req, res, next) => {
    console.log('A');
    next();
});
 
app.use((req, res, next) => {
    console.log('B');
    // Oops, forgot to call next()
});
 
app.get('/test', (req, res) => {
    console.log('C');
    res.send('Hello');
});

When you request /test, it logs A, then B, then the request hangs forever. The route handler never runs because the second middleware never calls next().

Why does middleware order matter?

Middleware executes in definition order. If you define routes before express.json(), those routes won't have parsed body data. If you define error handlers before routes, they won't catch route errors. Authentication must come before protected routes.

// WRONG - route defined before body parser
app.post('/data', (req, res) => {
    console.log(req.body); // undefined!
    res.send('OK');
});
app.use(express.json());
 
// CORRECT
app.use(express.json());
app.post('/data', (req, res) => {
    console.log(req.body); // { ... }
    res.send('OK');
});

Error Handling Questions

These questions test your understanding of error middleware and async error patterns.

How does error handling middleware work in Express?

Error handling middleware is defined with four parameters: (err, req, res, next). When you call next(error) with an argument, Express skips all remaining regular middleware and routes, jumping directly to error handling middleware. Error middleware should be defined last, after all routes.

// Note the four parameters - this is what makes it error middleware
app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(err.status || 500).json({
        error: err.message
    });
});

Why don't async errors get caught by error middleware in Express 4.x?

Only synchronous errors are automatically caught by Express 4.x error handlers. Async errors (in async functions or Promises) are not caught—they crash the app or cause the request to hang.

app.get('/error', (req, res, next) => {
    throw new Error('Sync error'); // Caught by error handler
});
 
app.get('/async-error', async (req, res, next) => {
    throw new Error('Async error'); // NOT caught - app crashes!
});
 
app.use((err, req, res, next) => {
    res.status(500).json({error: err.message});
});

How do you handle async errors in Express 4.x?

Use try/catch blocks with next(err), or create an async wrapper function that catches Promise rejections and passes them to the error handler.

// Option 1: Try/catch with next(err)
app.get('/async-error', async (req, res, next) => {
    try {
        throw new Error('Async error');
    } catch (err) {
        next(err); // Manually pass to error handler
    }
});
 
// Option 2: Async wrapper function
const asyncHandler = (fn) => (req, res, next) => {
    Promise.resolve(fn(req, res, next)).catch(next);
};
 
app.get('/async-error', asyncHandler(async (req, res) => {
    throw new Error('Async error');
}));

Express 5.x handles async errors automatically without wrappers.

Middleware Types Questions

These questions test your knowledge of the five middleware types in Express.

What are the five types of middleware in Express?

Express has five middleware types: application-level, router-level, built-in, third-party, and error-handling middleware. Each serves a specific purpose in the request-response cycle.

What is application-level middleware?

Application-level middleware is bound to the app instance using app.use() or app.METHOD(). It can run on every request or only on specific paths.

// Runs on every request
app.use((req, res, next) => {
    req.requestTime = Date.now();
    next();
});
 
// Runs only on specific path
app.use('/api', (req, res, next) => {
    console.log('API request');
    next();
});

What is router-level middleware?

Router-level middleware works like application-level middleware but is bound to an express.Router() instance. This allows you to create modular, mountable route handlers.

const router = express.Router();
 
router.use((req, res, next) => {
    console.log('Router middleware');
    next();
});
 
router.get('/users', (req, res) => {
    res.json([]);
});
 
app.use('/api', router);

What are the built-in middleware functions in Express?

Express provides three built-in middleware functions: express.json() for parsing JSON bodies, express.urlencoded() for parsing form data, and express.static() for serving static files.

app.use(express.json());           // Parse JSON bodies
app.use(express.urlencoded({extended: true})); // Parse form data
app.use(express.static('public')); // Serve static files

What are common third-party middleware packages?

Popular third-party middleware includes cors for cross-origin requests, helmet for security headers, and morgan for request logging.

const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
 
app.use(cors());          // Enable CORS
app.use(helmet());        // Security headers
app.use(morgan('dev'));   // Request logging

Data Passing Questions

These questions test your understanding of how data flows through middleware.

How do you pass data between middleware functions?

Attach data to the request object. Authentication middleware commonly sets req.user, and subsequent middleware or route handlers can access it. This is safer than using global variables or closures.

app.use((req, res, next) => {
    req.customData = {timestamp: Date.now()};
    next();
});
 
app.get('/test', (req, res) => {
    console.log(req.customData); // { timestamp: ... }
    res.send('OK');
});

What is the difference between app.use() and app.get()?

app.use() matches all HTTP methods and partial paths—app.use('/api') matches /api, /api/users, /api/anything. app.get() only matches GET requests and exact paths. Use app.use() for middleware that should run on multiple routes, and app.get() for specific route handlers.

How do you skip to the next route in Express?

Call next('route') to skip remaining middleware in the current route and move to the next matching route. This only works in middleware loaded using app.METHOD() or router.METHOD().

app.get('/user/:id',
    (req, res, next) => {
        if (req.params.id === '0') {
            return next('route'); // Skip to next route
        }
        next();
    },
    (req, res) => {
        res.send('Regular user');
    }
);
 
app.get('/user/:id', (req, res) => {
    res.send('Special user 0');
});

Quick Reference

ConceptWhat to Remember
Middleware signature(req, res, next) => {}
Error middleware(err, req, res, next) => {} (4 params)
Pass controlCall next()
Pass errorCall next(error)
Skip routeCall next('route')
Request hangs ifNo next() and no response
Order ruleAuth before routes, errors last
Pass dataAttach to req object

Ready to ace your interview?

Get 550+ interview questions with detailed answers in our comprehensive PDF guides.

View PDF Guides