Backend Development

REST APIs in Node.js: Best Practices and Common Pitfalls

A seamless communication between clients and servers. Build REST API in Node.js that developers love.js .js

Placeholder ImageCover Image
RG

Rahul Gupta

30 Dec - 4 min read

    Introduction

    Imagine you're ordering food at a restaurant. You (the client) ask the waiter (the API) for your dish (data), and the waiter takes your order, goes to the kitchen (server), and brings your food (response) back to you. This is how APIs (Application Programming Interface) work in the world of software.

    A REST API (Representational State Transfer API) is a specific type of API that follows a set of rules to enable smooth communication between a client and a server over the Internet. Rest API use HTTP method GET, POST, PUT, and DELETE to perform operations on resources.

    When discussing REST API in Node.js, we are talking about building servers that help different software applications communicate over the internet.

    Best Practices for Building RESTful APIs in Node.js

    1. Keep your routes organized:

    It’s important to keep your routes organized. A good practice is to separate your routes into different files based on their functionality.

    bash
    Keep your routes organized
    /my-api
    ├── /routes
    │ ├── userRoutes.js
    │ └── productRoutes.js
    ├── /controllers
    │ ├── userController.js
    │ └── productController.js
    ├── /models
    │ └── userModel.js
    └── app.js

    Here, each route file (e.g., userRoutes.js, productRoutes.js) contains the specific routes for each resource (like users and products). The controllers handle the logic behind each route.

    For example, in userRoutes.js, you could have something like:

    javascript
    ./src/routes/userRoutes.js
    const express = require('express');
    const router = express.Router();
    const userController = require('../controllers/userController');
    router.get('/', userController.getAllUsers);
    router.post('/', userController.createUser);
    module.exports = router;

    “This separation makes the code easier to read and maintain.”

    2. Use of HTTP Status Codes Properly:

    HTTP Status Codes are standardized numeric responses that a web server sends to a client These codes indicate whether the request was successful, failed, or requires further action.

    Here’s few most common status codes:

    200 OK: Everything went well.

    201 Created: A new resource was created.

    400 Bad Request: The client sent an invalid request.

    404 Not Found: The resource you’re trying to access doesn’t exist.

    500 Internal Server Error: Something went wrong on the server.

    Best Practices for Using HTTP Status Codes

    • Use Correct Codes
    • Provide Meaningful Error Messages.
    • Avoid Overloading Codes
    • Graceful Handling of Errors

    3. Use Middleware for Reusability

    Middleware is code in Express.js, that runs before the route handler. It can be used for various tasks, such as logging requests, checking authentication, or handling errors.

    For example, to log every request:

    javascript
    ./src/middleware.js
    const logger = (req, res, next) => {
    console.log(`${req.method} request to ${req.url}`);
    next(); // Pass control to the next middleware or route
    };
    app.use(logger);

    “By using middleware, you can keep your route handlers clean and reusable.”


    Authentication ensures that only authorized users or systems can access your API.

    Example: Using JWT for Authentication

    Installation:

    bash
    npm install jsonwebtoken bcrypt

    Step 1: The client logs in and gets a token.

    json
    POST /login
    POST /login
    Content-Type: application/json
    {
    "username": "user123",
    "password": "password123"
    }

    Server Response:

    json
    {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEyMzQ1fQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
    }

    Step 2: For every subsequent request, the client includes the token in the Authorization header.

    bash
    GET /user/profile
    Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEyMzQ1fQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

    Server Action: The server validates the token and processes the request only if it is valid.

    4. Rate Limiting

    Rate limiting restricts the number of requests a client can make in a specific time period, preventing misuse and protecting server resources.

    Installation:

    bash
    install express-rate-limit
    npm install express-rate-limit

    Example:

    javascript
    app.js
    const rateLimit = require('express-rate-limit');
    // Apply rate limiting middleware
    const limiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 100, // Limit each IP to 100 requests per windowMs
    message: "Too many requests from this IP, please try again later."
    });
    app.use('/api/', limiter); // Apply to all API routes

    If a user exceeds 100 requests in 15 minutes, they will receive an error response:

    json
    {
    "message": "Too many requests from this IP, please try again later."
    }

    5. Sanitize Input

    Sanitizing input ensures that malicious users cannot inject harmful data into your system, such as SQL or script injections.

    Libraries for Input Validation and Sanitization:

    • Express-validator for input validation:
    bash
    install express-validator
    npm install express-validator

    Example:

    javascript
    sanitizatin eg
    const { body, validationResult } = require('express-validator');
    app.post('/register',
    body('email').isEmail().withMessage('Invalid email'),
    body('password').isLength({ min: 6 }).withMessage('Password must be at least 6 characters'),
    (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
    }
    // Process valid input
    });

    Common Pitfalls (Mistakes to Avoid)

    New developers often make mistakes while building RESTful APIs. Let's review these common pitfalls and how to avoid them.

    1. Not Handling Errors Properly

    A common mistake is ignoring or poorly handling errors.

    javascript
    ./src/controllers/userController.js
    app.get('/user/:id', async (req, res) => {
    try {
    const user = await User.findById(req.params.id);
    if (!user) {
    return res.status(404).json({ message: 'User not found' });
    }
    res.status(200).json(user);
    } catch (error) {
    res.status(500).json({ message: 'Internal Server Error' });
    }
    });

    "Always handle errors, whether they are from the database or from invalid input."

    2. Missing Validate User Input

    A common mistake is not validating or sanitizing user input. This can lead to serious issues, such as crashes or security vulnerabilities.

    Use libraries like Joi or express-validator to validate input.

    Example with Joi:

    javascript
    ./src/controllers/userController.js
    app.get('/user/:id', async (req, res) => {
    try {
    // BAD PRACTICE: Not validating the 'id' parameter from the request.
    // 'req.params.id' might not be a valid MongoDB ObjectId, leading to issues with the query.
    const user = await User.findById(req.params.id);
    if (!user) {
    // BAD PRACTICE: If 'user' is not found, it's handled correctly, but we could have
    // a bad 'id' input that would result in errors, and this part of the code could be
    // more informative about input validation issues.
    return res.status(404).json({ message: 'User not found' });
    }
    // BAD PRACTICE: Returning the user without ensuring the data is valid or sanitized.
    // In some cases, 'user' might contain sensitive data that should be filtered out or
    // sanitized before being sent to the client.
    res.status(200).json(user);
    } catch (error) {
    // BAD PRACTICE: The error message is too generic.
    // It's important to log the actual error for debugging, or at least provide more
    // context to the client (without revealing sensitive details).
    res.status(500).json({ message: 'Internal Server Error' });
    }
    });
    });

    3. Not Optimizing Database Queries

    As your API grows, you’ll be querying your database more often. Not optimizing these queries can slow down your API.

    • Use pagination for large datasets.
    • Make sure your queries are efficient and indexed properly.

    For example, instead of fetching all users:

    javascript
    ./src/controllers/userController.js
    const users = await User.find().limit(10).skip(10); // Pagination

    Conclusion

    Building RESTful APIs in Node.js is a powerful way to create scalable web services with which clients can interact. By following best practices and avoiding common mistakes, you’ll create APIs that are efficient, secure, and easy to maintain.

    Cover Image

    Enjoyed your read ?. If you found any of this articles helpful please do not forget to give us a shoutout.