Skip to main content
REST API Design Tutorial
CHAPTER 15 Beginner

Building REST APIs with Node.js and Express

Updated: May 14, 2026
25 min read

# CHAPTER 15

Building REST APIs with Node.js and Express

1. Introduction

Throughout this curriculum, we have utilized small conceptual snippets of JavaScript. Now, we will synthesize all REST constraints, routing conventions, and middleware architectures into a complete, professional backend application. Node.js, combined with the Express framework, is the most popular environment for building rapid, scalable REST APIs. In this chapter, we will architect a production-ready Express API structure from the ground up.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Architect a professional folder structure for an Express API.
  • Implement the Express Application instance and core middleware.
  • Separate concerns using Express Routers and Controllers.
  • Build standard CRUD endpoints returning JSend formatted JSON.
  • Implement a Global Error Handler.

3. Beginner-Friendly Explanation

Imagine building an Amazon Fulfillment Center.
  • The Server (server.js): The building itself. It opens the doors and turns on the lights (starts listening on Port 3000).
  • The App (app.js): The manager. It sets the rules: "Everyone must wear safety vests (CORS), and all boxes must be labeled (JSON Parser)."
  • The Router (routes/): The conveyor belts. They inspect the label on an incoming box and route it to the correct department (e.g., "Send this to the Users department").
  • The Controllers (controllers/): The workers. They receive the box, do the actual heavy lifting (database queries), and pack the result into a standardized JSON shipping box to send back.

4. Step 1: The Architecture

A flat file with 500 lines of routing code is unmaintainable. Professional Express APIs use the MVC (Model-View-Controller) pattern, stripped of the "View" because APIs return JSON, not HTML.
text
12345678910
rest-api/
  package.json
  server.js           <-- Bootstraps the server
  app.js              <-- Configures Express and Middleware
  routes/
    userRoutes.js     <-- Maps URLs to Controllers
  controllers/
    userController.js <-- Contains the Database Logic
  models/
    userModel.js      <-- Database Schema

5. Step 2: The Core Configuration (app.js)

We centralize all middleware configuration here. This keeps our testing environment clean later.
javascript
1234567891011121314151617181920
const express = require(&#039;express&#039;);
const cors = require(&#039;cors&#039;);
const userRoutes = require(&#039;./routes/userRoutes&#039;);

const app = express();

// 1. Core Middleware
app.use(cors()); // Allow cross-origin requests
app.use(express.json()); // Parse incoming JSON payloads

// 2. Mount the Routers
// All routes inside userRoutes will automatically be prefixed with /api/v1/users!
app.use(&#039;/api/v1/users&#039;, userRoutes);

// 3. Fallback Route (404)
app.all(&#039;*&#039;, (req, res) => {
    res.status(404).json({ status: "fail", message: `Can&#039;t find ${req.originalUrl}` });
});

module.exports = app;

6. Step 3: Bootstrapping the Server (server.js)

This file is incredibly simple. It imports the configured app and turns on the network listener.
javascript
123456
const app = require(&#039;./app&#039;);
const PORT = process.env.PORT || 3000;

app.listen(PORT, () => {
    console.log(`REST API Server running on port ${PORT}`);
});

7. Step 4: The Routing Layer (routes/userRoutes.js)

The Router explicitly maps HTTP Methods and URL endpoints to specific Controller functions. Notice how clean this is—there is zero database logic here.
javascript
123456789101112131415161718
const express = require(&#039;express&#039;);
const userController = require(&#039;../controllers/userController&#039;);

const router = express.Router();

// Maps to: GET /api/v1/users
router.get(&#039;/&#039;, userController.getAllUsers);

// Maps to: POST /api/v1/users
router.post(&#039;/&#039;, userController.createUser);

// Maps to: GET /api/v1/users/:id
router.get(&#039;/:id&#039;, userController.getUserById);

// Maps to: DELETE /api/v1/users/:id
router.delete(&#039;/:id&#039;, userController.deleteUser);

module.exports = router;

8. Step 5: The Controller Layer (controllers/userController.js)

The Controller handles the Request, interacts with the Model (Database), and shapes the final JSON Response. *(Note: To save space, we are simulating the database with an array).*
javascript
12345678910111213141516171819202122232425262728293031323334
let users = [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }];

exports.getAllUsers = (req, res) => {
    // Return standard JSend formatting
    res.status(200).json({
        status: "success",
        results: users.length,
        data: { users: users }
    });
};

exports.createUser = (req, res) => {
    const newUser = { id: 3, name: req.body.name };
    users.push(newUser);
    
    // 201 Created!
    res.status(201).json({
        status: "success",
        data: { user: newUser }
    });
};

exports.getUserById = (req, res) => {
    const user = users.find(u => u.id === parseInt(req.params.id));
    
    if (!user) {
        return res.status(404).json({ status: "fail", message: "User not found" });
    }
    
    res.status(200).json({
        status: "success",
        data: { user: user }
    });
};

9. Best Practices

  • Async/Await Error Wrapping: Real database calls are asynchronous (await db.find()). If a database crashes, the promise rejects, and Express will crash the entire server. You must wrap every controller function in a try/catch block, passing the error to the Global Error Handler using next(err). (Alternatively, use a package like express-async-errors to handle this automatically).

10. Common Mistakes

  • Fat Controllers: Beginners put 500 lines of complex math, API calls, and email-sending logic directly inside the Controller. Controllers should be "skinny." Their only job is to handle HTTP requests and responses. Complex logic should be extracted into separate "Service" files.

11. Exercises

  1. 1. Explain the architectural separation of concerns between userRoutes.js and userController.js. Why is the database logic removed from the routing file?

12. Coding Challenges

  • Challenge: Look at the routing layer in Step 7. Use the express.Router() object to add a new route definition for updating a user. It should map the PUT HTTP method, targeting a specific user ID, to a hypothetical controller function named updateUser.

13. MCQs with Answers

Question 1

In an Express.js REST API architecture, what is the primary purpose of the express.Router() object?

Question 2

When a controller successfully fetches a collection of data and returns it using res.status(200).json(...), which component of the MVC architecture is primarily responsible for formatting that output data?

14. Interview Questions

  • Q: Walk me through a professional folder architecture for a Node.js Express API. Detail the explicit responsibilities of the server.js, routes/, and controllers/ directories.
  • Q: Explain why asynchronous database operations inside an Express Controller must be wrapped in try/catch blocks, and how caught exceptions are forwarded to a Global Error Handling middleware.

15. FAQs

Q: Do I have to use Express for Node.js? A: No, but it is the industry standard. Other popular frameworks include Fastify (which is significantly faster than Express for massive data loads) and NestJS (a highly structured, Angular-like framework for massive enterprise applications).

16. Summary

In Chapter 15, we translated REST principles into a tangible, production-ready Node.js architecture. We discarded monolithic coding practices in favor of a strictly decoupled Router/Controller design pattern. We configured global middleware in app.js to parse JSON and enforce CORS, utilized express.Router() to elegantly map HTTP verbs to specific URL endpoints, and implemented Controllers to handle business logic and return standardized JSend JSON responses.

17. Next Chapter Recommendation

Node.js isn't the only backend heavyweight. How does the philosophy of API design translate to PHP? Proceed to Chapter 16: Building REST APIs with PHP and Laravel.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·