Menu
CoddyTech

Express.js Interview Questions

Express examples are static because this page has no npm packages; the last question uses http and runs.

10 questions
  1. What is Express.js, and why use it instead of the http module?

    Express is a minimal web framework on Node's http module that adds routing, middleware, body parsing and helpers such as res.json.

    const express = require('express');
    const app = express();
    
    app.use(express.json()); // parse JSON bodies into req.body
    
    app.get('/users/:id', (req, res) => {
      res.json({ id: req.params.id });
    });
    
    app.listen(3000, () => console.log('listening on 3000'));
    

    Alternatives: Fastify (faster, schema validation), NestJS (dependency injection, on Express or Fastify) and Koa.

  2. What is middleware in Express?

    A function (req, res, next) that runs during a request, in registration order. It can change req and res, end the response, or call next(). Logging, auth and body parsing are middleware.

    function requestTimer(req, res, next) {
      const start = Date.now();
      res.on('finish', () => {
        console.log(req.method, req.url, res.statusCode, Date.now() - start, 'ms');
      });
      next();
    }
    
    function requireAuth(req, res, next) {
      if (!req.headers.authorization) {
        return res.status(401).json({ error: 'missing token' });
      }
      next();
    }
    
    app.use(requestTimer);                        // every route
    app.get('/orders', requireAuth, listOrders);  // one route
    

    Classic bugs: neither calling next() nor responding hangs the request; calling next() after responding lets a later handler respond again, which throws "Cannot set headers after they are sent to the client".

  3. What is the difference between req.params, req.query and req.body?

    req.params holds route segments (/users/:id), req.query the query string (?page=2), req.body the parsed body. Params and query values are strings, so convert and validate them, and req.body is undefined without a parser such as express.json().

  4. How do you handle errors in Express?

    Add a middleware with four parameters (err, req, res, next) after all routes; errors passed to next(err) or thrown in a handler go there.

    app.get('/users/:id', async (req, res) => {
      const user = await db.findUser(req.params.id); // may reject
      if (!user) {
        const err = new Error('user not found');
        err.status = 404;
        throw err;
      }
      res.json(user);
    });
    
    // last; the four parameters are how Express recognizes it
    app.use((err, req, res, next) => {
      console.error(err);
      res.status(err.status ?? 500).json({ error: err.status ? err.message : 'internal error' });
    });
    

    Express 5 forwards a rejected async handler there too. Express 4 does not: the request hangs and, on Node 15 and later, the process crashes, so catch and call next(err).

  5. What is express.Router and how do you structure routes?

    express.Router() is a mini app with its own routes and middleware, mounted under a prefix to split an API into files.

    // routes/users.js
    const express = require('express');
    const router = express.Router();
    
    router.get('/', listUsers);     // GET /api/users
    router.get('/:id', getUser);    // GET /api/users/42
    router.post('/', createUser);   // POST /api/users
    
    module.exports = router;
    
    // app.js
    app.use('/api/users', require('./routes/users'));
    

    Keep routes thin: they parse input and call a service, which holds the business logic, so it stays testable without HTTP.

  6. What is the difference between app.use and app.get?

    app.get matches only GET (and HEAD) on the exact path. app.use matches every method and every path under the prefix, and strips the prefix from req.url. Mount routers and middleware with use, endpoints with get or post.

    app.use('/api', apiLogger);       // GET /api/users, POST /api/orders/7, ...
    app.get('/api/users', listUsers); // only GET /api/users
    
  7. What is CORS, and how do you enable it in a Node.js API?

    CORS is a browser rule: a page may read a response from another origin only if the API's headers allow it. Only browsers enforce it, so it does not protect your API. For JSON bodies, custom headers or PUT, the browser first sends an OPTIONS preflight.

    const cors = require('cors');
    
    app.use(cors({
      origin: ['https://app.example.com'], // never '*' when sending cookies
      credentials: true,
    }));
    
  8. How do you create an HTTP server without Express?

    With node:http: createServer takes a (req, res) handler for every request, and routing and parsing are yours.

    JavaScript
    const http = require('node:http');
    
    const server = http.createServer((req, res) => {
      const url = new URL(req.url, 'http://localhost');
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ path: url.pathname, x: url.searchParams.get('x') }));
    });
    
    server.listen(0, () => {
      const { port } = server.address();
      http.get(`http://127.0.0.1:${port}/hello?x=1`, (res) => {
        let body = '';
        res.setEncoding('utf8');
        res.on('data', (chunk) => (body += chunk));
        res.on('end', () => {
          console.log(res.statusCode, res.headers['content-type'], body);
          server.close();
        });
      });
    });

    Port 0 asks the OS for any free port, the usual trick in tests.

  9. What is the difference between res.send, res.json and res.end in Express?

    res.end is Node's raw method: it ends the response with an optional string or Buffer and sets no Content-Type. res.send is Express: it sets the type from the value (a string is text/html, a Buffer application/octet-stream, an object JSON) plus Content-Length and an ETag. res.json always serializes and sends application/json.

    app.get('/raw', (req, res) => res.end('ok'));            // no Content-Type
    app.get('/page', (req, res) => res.send('<p>ok</p>'));   // text/html
    app.get('/api', (req, res) => res.json({ ok: true }));   // application/json
    app.delete('/x', (req, res) => res.status(204).end());   // no body
    

    So res.send(JSON.stringify(data)) goes out as HTML; use res.json(data).

  10. How do you implement JWT authentication in an Express API?

    At login, check the password and sign a short-lived token; on each request, a middleware reads Authorization: Bearer <token>, verifies it and sets req.user, or answers 401.

    const jwt = require('jsonwebtoken');
    const SECRET = process.env.JWT_SECRET;
    
    app.post('/login', async (req, res) => {
      const user = await users.checkPassword(req.body.email, req.body.password);
      if (!user) return res.status(401).json({ error: 'invalid credentials' });
      res.json({ token: jwt.sign({ sub: user.id }, SECRET, { expiresIn: '15m' }) });
    });
    
    function requireUser(req, res, next) {
      const [scheme, token] = (req.headers.authorization ?? '').split(' ');
      try {
        if (scheme !== 'Bearer') throw new Error('missing token');
        req.user = jwt.verify(token, SECRET, { algorithms: ['HS256'] });
        next();
      } catch {
        res.status(401).json({ error: 'invalid or expired token' });
      }
    }
    
    app.get('/me', requireUser, (req, res) => res.json({ id: req.user.sub }));
    

    A JWT is signed, not encrypted: anyone can read its payload. It cannot be revoked before it expires without a denylist, hence short expiry plus a refresh token.

Coddy programming languages illustration

Learn to code with Coddy

Get started