Building RESTful APIs with Node.js & Express

Introduction

Learn to build robust REST APIs with Node.js and Express, covering routing, middleware, error handling, and security essentials for maintainable apps.

Written At

2025-06-01

Updated At

2025-06-01

Reading time

7 minutes

Step 1: Project Setup & Basic Server

Why it matters: Proper structure from the beginning prevents technical debt.

What to do:

  1. Initialize project and install dependencies:
    bash
    npm init -y
    npm install express body-parser cors
    npm install --save-dev nodemon
  2. Create basic server:
    javascript
    const express = require('express');
    const app = express();
    
    app.use(express.json());
    
    app.get('/', (req, res) => {
      res.json({ message: 'API is running' });
    });
    
    const PORT = process.env.PORT || 3000;
    app.listen(PORT, () => {
      console.log(`Server running on port ${PORT}`);
    });

Example:

Folder structure for scalability:

bash
project/
├── src/
│   ├── controllers/
│   ├── routes/
│   ├── models/
│   ├── middleware/
│   └── app.js
├── .env
└── package.json

Step 2: Route Handling & Controllers

Why it matters: Separation of concerns makes your code more maintainable.

What to do:

  1. Create modular routes:
    javascript
    // routes/users.js
    const express = require('express');
    const router = express.Router();
    const { getUsers, createUser } = require('../controllers/users');
    
    router.route('/')
      .get(getUsers)
      .post(createUser);
    
    module.exports = router;
  2. Implement controller logic:
    javascript
    // controllers/users.js
    const getUsers = async (req, res) => {
      try {
        const users = await User.find({});
        res.status(200).json(users);
      } catch (error) {
        res.status(500).json({ message: error.message });
      }
    };

Example:

Registering routes in main app:

javascript
// app.js
const userRoutes = require('./routes/users');
app.use('/api/users', userRoutes);

Step 3: Error Handling & Security

Why it matters: Proper error handling prevents crashes and security vulnerabilities.

What to do:

  1. Implement error middleware:
    javascript
    // middleware/errorHandler.js
    const errorHandler = (err, req, res, next) => {
      const statusCode = err.statusCode || 500;
      res.status(statusCode).json({
        message: err.message,
        stack: process.env.NODE_ENV === 'production' ? '🥞' : err.stack
      });
    };
  2. Add security middleware:
    javascript
    app.use(helmet()); // Sets various HTTP headers
    app.use(cors({
      origin: process.env.CLIENT_URL
    }));
    app.use(rateLimit({
      windowMs: 15 * 60 * 1000, // 15 minutes
      max: 100 // limit each IP to 100 requests per window
    }));

Example:

Custom error class for consistent errors:

javascript
class ErrorResponse extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
  }
}

// Usage in controller
throw new ErrorResponse('User not found', 404);