Skip to main content

Building REST APIs with Node.js

Created: May 8, 2026 Larry Qu 7 min read

REST APIs are the foundation of modern web applications. This article covers REST API development with Node.js.

Introduction

REST APIs provide:

  • Standardized endpoints
  • HTTP methods
  • Status codes
  • Request/response formats
  • Scalability

Understanding REST APIs helps you:

  • Build web services
  • Create scalable backends
  • Integrate with frontends
  • Design clean APIs
  • Handle data exchange

REST API Fundamentals

HTTP Methods and Status Codes

const express = require('express');
const app = express();

app.use(express.json());

// ✅ Good: GET - Retrieve resources
app.get('/api/users', (req, res) => {
  res.status(200).json([
    { id: 1, name: 'John' },
    { id: 2, name: 'Jane' }
  ]);
});

// ✅ Good: GET - Retrieve single resource
app.get('/api/users/:id', (req, res) => {
  const user = { id: req.params.id, name: 'John' };
  res.status(200).json(user);
});

// ✅ Good: POST - Create resource
app.post('/api/users', (req, res) => {
  const newUser = { id: 3, ...req.body };
  res.status(201).json(newUser);
});

// ✅ Good: PUT - Update resource
app.put('/api/users/:id', (req, res) => {
  const updatedUser = { id: req.params.id, ...req.body };
  res.status(200).json(updatedUser);
});

// ✅ Good: PATCH - Partial update
app.patch('/api/users/:id', (req, res) => {
  const user = { id: req.params.id, name: 'John', ...req.body };
  res.status(200).json(user);
});

// ✅ Good: DELETE - Remove resource
app.delete('/api/users/:id', (req, res) => {
  res.status(204).send();
});

// ✅ Good: Status codes
// 200 OK - Success
// 201 Created - Resource created
// 204 No Content - Success, no content
// 400 Bad Request - Invalid input
// 401 Unauthorized - Authentication required
// 403 Forbidden - Access denied
// 404 Not Found - Resource not found
// 500 Internal Server Error - Server error

API Response Format

// ✅ Good: Consistent response format
const sendResponse = (res, status, data, message) => {
  res.status(status).json({
    success: status < 400,
    status,
    message,
    data
  });
};

app.get('/api/users', (req, res) => {
  const users = [{ id: 1, name: 'John' }];
  sendResponse(res, 200, users, 'Users retrieved successfully');
});

app.post('/api/users', (req, res) => {
  const newUser = { id: 1, ...req.body };
  sendResponse(res, 201, newUser, 'User created successfully');
});

// ✅ Good: Error response format
app.get('/api/users/:id', (req, res) => {
  const user = null;
  if (!user) {
    return sendResponse(res, 404, null, 'User not found');
  }
  sendResponse(res, 200, user, 'User retrieved successfully');
});

// ✅ Good: Pagination response
app.get('/api/users', (req, res) => {
  const page = req.query.page || 1;
  const limit = req.query.limit || 10;
  const users = [{ id: 1, name: 'John' }];

  res.json({
    success: true,
    data: users,
    pagination: {
      page,
      limit,
      total: 100,
      pages: Math.ceil(100 / limit)
    }
  });
});

API Design Patterns

RESTful Endpoints

const express = require('express');
const app = express();

app.use(express.json());

// ✅ Good: Resource-based endpoints
// Users
app.get('/api/users', (req, res) => { /* List users */ });
app.post('/api/users', (req, res) => { /* Create user */ });
app.get('/api/users/:id', (req, res) => { /* Get user */ });
app.put('/api/users/:id', (req, res) => { /* Update user */ });
app.delete('/api/users/:id', (req, res) => { /* Delete user */ });

// Posts
app.get('/api/posts', (req, res) => { /* List posts */ });
app.post('/api/posts', (req, res) => { /* Create post */ });
app.get('/api/posts/:id', (req, res) => { /* Get post */ });
app.put('/api/posts/:id', (req, res) => { /* Update post */ });
app.delete('/api/posts/:id', (req, res) => { /* Delete post */ });

// ✅ Good: Nested resources
app.get('/api/users/:userId/posts', (req, res) => {
  const userId = req.params.userId;
  res.json([{ id: 1, userId, title: 'Post 1' }]);
});

app.post('/api/users/:userId/posts', (req, res) => {
  const userId = req.params.userId;
  res.status(201).json({ id: 1, userId, ...req.body });
});

// ✅ Good: Query parameters for filtering
app.get('/api/users', (req, res) => {
  const { role, status, page = 1, limit = 10 } = req.query;
  // Filter users based on query parameters
  res.json([]);
});

// ✅ Good: Sorting and pagination
app.get('/api/users', (req, res) => {
  const { sort = 'createdAt', order = 'desc', page = 1, limit = 10 } = req.query;
  const offset = (page - 1) * limit;
  // Apply sorting and pagination
  res.json([]);
});

API Versioning

const express = require('express');
const app = express();

// ✅ Good: URL versioning
app.get('/api/v1/users', (req, res) => {
  res.json([{ id: 1, name: 'John' }]);
});

app.get('/api/v2/users', (req, res) => {
  res.json([{ id: 1, name: 'John', email: '[email protected]' }]);
});

// ✅ Good: Header versioning
app.get('/api/users', (req, res) => {
  const version = req.headers['api-version'] || '1';
  if (version === '2') {
    res.json([{ id: 1, name: 'John', email: '[email protected]' }]);
  } else {
    res.json([{ id: 1, name: 'John' }]);
  }
});

// ✅ Good: Router versioning
const v1Router = express.Router();
const v2Router = express.Router();

v1Router.get('/users', (req, res) => {
  res.json([{ id: 1, name: 'John' }]);
});

v2Router.get('/users', (req, res) => {
  res.json([{ id: 1, name: 'John', email: '[email protected]' }]);
});

app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);

Complete API Example

User API Implementation

// controllers/userController.js
const User = require('../models/User');

const getUsers = async (req, res, next) => {
  try {
    const { page = 1, limit = 10, role } = req.query;
    const offset = (page - 1) * limit;

    let query = {};
    if (role) query.role = role;

    const users = await User.find(query)
      .limit(limit)
      .skip(offset);

    const total = await User.countDocuments(query);

    res.json({
      success: true,
      data: users,
      pagination: {
        page,
        limit,
        total,
        pages: Math.ceil(total / limit)
      }
    });
  } catch (err) {
    next(err);
  }
};

const getUserById = async (req, res, next) => {
  try {
    const user = await User.findById(req.params.id);
    if (!user) {
      return res.status(404).json({
        success: false,
        message: 'User not found'
      });
    }
    res.json({ success: true, data: user });
  } catch (err) {
    next(err);
  }
};

const createUser = async (req, res, next) => {
  try {
    const user = new User(req.body);
    await user.save();
    res.status(201).json({ success: true, data: user });
  } catch (err) {
    next(err);
  }
};

const updateUser = async (req, res, next) => {
  try {
    const user = await User.findByIdAndUpdate(
      req.params.id,
      req.body,
      { new: true, runValidators: true }
    );
    if (!user) {
      return res.status(404).json({
        success: false,
        message: 'User not found'
      });
    }
    res.json({ success: true, data: user });
  } catch (err) {
    next(err);
  }
};

const deleteUser = async (req, res, next) => {
  try {
    const user = await User.findByIdAndDelete(req.params.id);
    if (!user) {
      return res.status(404).json({
        success: false,
        message: 'User not found'
      });
    }
    res.status(204).send();
  } catch (err) {
    next(err);
  }
};

module.exports = {
  getUsers,
  getUserById,
  createUser,
  updateUser,
  deleteUser
};

// routes/users.js
const express = require('express');
const { body } = require('express-validator');
const validate = require('../middleware/validate');
const authenticate = require('../middleware/auth');
const userController = require('../controllers/userController');

const router = express.Router();

router.get('/', userController.getUsers);

router.post(
  '/',
  [
    body('name').notEmpty(),
    body('email').isEmail(),
    body('password').isLength({ min: 8 })
  ],
  validate,
  userController.createUser
);

router.get('/:id', userController.getUserById);

router.put(
  '/:id',
  authenticate,
  [
    body('name').optional().notEmpty(),
    body('email').optional().isEmail()
  ],
  validate,
  userController.updateUser
);

router.delete('/:id', authenticate, userController.deleteUser);

module.exports = router;

// app.js
const express = require('express');
const userRoutes = require('./routes/users');
const errorHandler = require('./middleware/errorHandler');

const app = express();

app.use(express.json());
app.use('/api/users', userRoutes);
app.use(errorHandler);

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

API Documentation

OpenAPI/Swagger Documentation

// ✅ Good: Swagger setup
const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');

const options = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: 'User API',
      version: '1.0.0'
    },
    servers: [
      { url: 'http://localhost:3000' }
    ]
  },
  apis: ['./routes/*.js']
};

const specs = swaggerJsdoc(options);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));

// ✅ Good: Document endpoints
/**
 * @swagger
 * /api/users:
 *   get:
 *     summary: Get all users
 *     responses:
 *       200:
 *         description: List of users
 */
app.get('/api/users', (req, res) => {
  res.json([]);
});

Best Practices

  1. Use consistent naming:
    // ✅ Good: Consistent naming
    app.get('/api/users', ...);
    app.get('/api/posts', ...);
    
    // ❌ Bad: Inconsistent naming
    app.get('/api/users', ...);
    app.get('/api/get-posts', ...);
    ```javascript
    
  2. Use proper status codes:
    // ✅ Good: Proper status codes
    res.status(201).json(newUser);  // Created
    res.status(204).send();          // No content
    res.status(400).json(error);     // Bad request
    
    // ❌ Bad: Always 200
    res.json(newUser);
    ```javascript
    
  3. Validate input:
    // ✅ Good: Validate input
    app.post('/api/users', validate, (req, res) => {
      // Process validated data
    });
    
    // ❌ Bad: No validation
    app.post('/api/users', (req, res) => {
      // Process unvalidated data
    });
    

Summary

REST APIs are essential. Key takeaways:

  • Use HTTP methods correctly
  • Design resource-based endpoints
  • Use proper status codes
  • Implement pagination and filtering
  • Version your APIs
  • Validate input
  • Document endpoints
  • Handle errors properly

Next Steps

Resources

Comments

Share this article

Scan to read on mobile