Building Scalable APIs with Node.js and Express
Back to BlogBackend

Building Scalable APIs with Node.js and Express

MH

Mahedi H Sharif

Full Stack Developer

December 15, 20248 min read

Introduction

Building scalable APIs is a crucial skill for any backend developer. In this comprehensive guide, we'll explore the best practices and patterns for creating production-ready RESTful APIs using Node.js and Express.

Setting Up the Project

First, let's set up our project structure. A well-organized codebase is the foundation of a maintainable application.

bash
mkdir my-api && cd my-api
npm init -y
npm install express mongoose dotenv cors helmet
npm install -D typescript @types/node @types/express

Project Structure

Here's the recommended folder structure for a scalable API:

code
src/
├── config/
├── controllers/
├── middleware/
├── models/
├── routes/
├── services/
├── utils/
└── app.ts

Creating the Express Server

Let's start by setting up our Express server with essential middleware:

typescript
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import { errorHandler } from './middleware/errorHandler';

const app = express();

// Security middleware
app.use(helmet());
app.use(cors());
app.use(express.json());

// Routes
app.use('/api/v1/users', userRoutes);
app.use('/api/v1/products', productRoutes);

// Error handling
app.use(errorHandler);

export default app;

Error Handling

Proper error handling is essential for a production API. Here's a robust error handling pattern:

typescript
class AppError extends Error {
  statusCode: number;
  isOperational: boolean;

  constructor(message: string, statusCode: number) {
    super(message);
    this.statusCode = statusCode;
    this.isOperational = true;
    Error.captureStackTrace(this, this.constructor);
  }
}

export const errorHandler = (err, req, res, next) => {
  const statusCode = err.statusCode || 500;
  res.status(statusCode).json({
    success: false,
    message: err.message,
    ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
  });
};

Authentication with JWT

Implementing secure authentication is crucial. Here's how to set up JWT authentication:

typescript
import jwt from 'jsonwebtoken';

export const generateToken = (userId: string) => {
  return jwt.sign({ id: userId }, process.env.JWT_SECRET!, {
    expiresIn: '7d'
  });
};

export const protect = async (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  
  if (!token) {
    return next(new AppError('Not authorized', 401));
  }

  const decoded = jwt.verify(token, process.env.JWT_SECRET!);
  req.user = await User.findById(decoded.id);
  next();
};

Rate Limiting

Protect your API from abuse with rate limiting:

typescript
import rateLimit from 'express-rate-limit';

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // limit each IP to 100 requests per windowMs
  message: 'Too many requests, please try again later.'
});

app.use('/api', limiter);

Validation

Input validation prevents bad data from entering your system:

typescript
import { body, validationResult } from 'express-validator';

export const validateUser = [
  body('email').isEmail().normalizeEmail(),
  body('password').isLength({ min: 8 }),
  body('name').trim().notEmpty(),
  
  (req, res, next) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    next();
  }
];

Conclusion

Building scalable APIs requires attention to detail in areas like error handling, authentication, validation, and rate limiting. By following these patterns, you'll create robust APIs that can handle production traffic.

Remember to always:

  • Use proper error handling
  • Implement authentication and authorization
  • Validate all inputs
  • Add rate limiting
  • Document your API
  • Write tests
  • Happy coding!

    Tags

    Node.jsExpressAPIBackendREST
    MH

    Mahedi H Sharif

    Full Stack Developer

    Passionate about building scalable web applications and sharing knowledge with the developer community.