HomeBytesheetsRoadmapsUI TemplatesUI ToolsStack Discussion
4 Topics
Chapter•240 Minutes

Building a REST API with Node.js and Express

Put the concepts together by implementing a production-oriented REST API.

Project Structure

Express REST API Structure

A maintainable Node.js API can separate routing, controllers, services, repositories, validation, middleware, configuration, and infrastructure.

Example:

src/
  routes/
  controllers/
  services/
  repositories/
  middleware/
  validators/
  errors/
  config/
  app.js
  server.js

The exact structure is less important than maintaining clear boundaries. Avoid turning routes.js into a file containing database queries, authentication, validation, business rules, and response formatting.

Express Router
const router = express.Router();

router.get('/users/:id', authenticate, userController.getById);
router.post('/users', authenticate, validate(createUserSchema), userController.create);
router.patch('/users/:id', authenticate, validate(updateUserSchema), userController.update);
router.delete('/users/:id', authenticate, userController.remove);
💡 Routing remains focused on composing middleware and mapping HTTP requests to controllers.

Middleware Pipeline

#Middleware forms a pipeline through which requests pass.

Typical middleware includes request IDs, logging, security headers, body parsing, CORS, authentication, validation, authorization, and error handling.

Order matters. Authentication must execute before authorization. Request parsing must occur before body validation. Error middleware must be registered correctly so errors reach it.

Express Middleware Pipeline

Complete CRUD Endpoint Design

CRUD API

A complete users API can expose:

  • POST /users
  • GET /users
  • GET /users/:id
  • PUT /users/:id
  • PATCH /users/:id
  • DELETE /users/:id

Each endpoint should define request validation, authentication requirements, authorization rules, status codes, response schemas, error cases, logging, metrics, and database behavior.

Do not consider an endpoint complete merely because the happy path works.

Controller Example
async function getUser(req, res, next) {
  try {
    const user = await userService.getById(req.params.id, req.auth);

    if (!user) {
      return res.status(404).json({
        type: 'user-not-found',
        title: 'User not found',
        status: 404
      });
    }

    res.status(200).json(user);
  } catch (error) {
    next(error);
  }
}
💡 The controller delegates business behavior to the service and maps a known absence to a documented HTTP response.

Production Hardening

A production API should include environment-based configuration, secrets management, structured logging, request IDs, health endpoints, graceful shutdown, timeouts, validation, rate limiting, secure headers, dependency monitoring, metrics, tracing, automated tests, and deployment automation.

Never place secrets directly in source code. Never expose internal stack traces to clients. Do not assume process-level memory is durable or shared across replicas.

A production-ready API is not defined by the number of endpoints. It is defined by predictable behavior under valid input, invalid input, high load, partial failure, and malicious input.

Definition of Done

An API feature is production-ready only when its contract, security, validation, failure behavior, observability, tests, and deployment behavior are understood.