Paramclasses Academy - Blog

Tutorials Details

←
Post 34 of 40
→

How to Secure Node.js APIs with Rate Limiting, CORS and Helmet

How to Secure Node.js APIs with Rate Limiting, CORS and Helmet

October 30, 2026

✦ How to Secure Node.js APIs with Rate Limiting, CORS and Helmet

Node.js and Express.js are widely used for building REST APIs, web applications, and backend systems. APIs often handle sensitive information such as user accounts, login credentials, customer records, and payment-related data. Therefore, protecting APIs from unauthorized access and common attacks is an important part of backend development.

Three useful tools for improving Express.js API security are Rate Limiting, CORS, and Helmet.

In this practical tutorial, you will learn what these tools do, how to install them, and how to configure them in a Node.js application.

✦ 1. What Are Rate Limiting, CORS and Helmet?

Before writing code, let's understand these three security concepts.

❖ Rate Limiting

Rate limiting controls how many requests a client can send to an API within a specific time period.

For example, if a login API allows five requests in 15 minutes, a client that exceeds this limit will temporarily receive an error response.

It helps reduce:

  • Repeated login attempts.

  • Automated password guessing.

  • Excessive API requests.

  • Some forms of abusive traffic.

❖ CORS (Cross-Origin Resource Sharing)

CORS controls which browser-based websites are allowed to access resources from your API.

For example, your backend may run at:

http://localhost:3000

And your React frontend may run at:

http://localhost:5173

Because these are different origins, the browser may require CORS permission before allowing the frontend to read the API response.

❖ Helmet

Helmet is Express.js middleware that helps configure HTTP security headers.

These headers can improve browser-side protection against certain security risks.

Helmet can configure headers related to:

  • Content Security Policy.

  • MIME type handling.

  • Clickjacking protection.

  • Referrer information.

  • Other browser security policies.

✦ 2. Create a Node.js Project

First, create a new folder for your project.

Open the terminal or VS Code terminal and execute:

mkdir node-api-security
cd node-api-security

Initialize your Node.js project:

npm init -y

This command creates a package.json file containing your project's configuration and dependencies.

✦ 3. Install Required Packages

Install Express, Helmet, CORS, and Express Rate Limit.

npm install express helmet cors express-rate-limit

❖ Package Explanation

Package Purpose
express Creates the backend server and API routes
helmet Configures security-related HTTP headers
cors Configures browser cross-origin access
express-rate-limit Limits repeated API requests

✦ 4. Create the Project Structure

Create the following files:

node-api-security/
│
├── node_modules/
├── package.json
├── server.js
└── .gitignore

The server.js file will contain the Express application and security configuration.

✦ 5. Configure Helmet in Express.js

Helmet should be registered as middleware before your application routes.

Open server.js and add:

const express = require("express");
const helmet = require("helmet");

const app = express();

app.use(helmet());

app.get("/", (req, res) => {
    res.json({
        message: "Node.js API is running securely"
    });
});

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

Start the application:

node server.js

Open your browser:

http://localhost:3000

You should receive:

{
    "message": "Node.js API is running securely"
}

Helmet automatically applies several security-related HTTP headers. However, the correct configuration depends on your frontend and application requirements. For example, Content Security Policy may need additional configuration when serving a frontend application.

✦ 6. Configure CORS in Node.js

Suppose your React application runs on port 5173 and your Node.js backend runs on port 3000.

Install CORS if you have not already installed it:

npm install cors

Add the following configuration to server.js:

const cors = require("cors");

app.use(cors({
    origin: "http://localhost:5173",
    methods: ["GET", "POST", "PUT", "DELETE"],
    allowedHeaders: ["Content-Type", "Authorization"]
}));

Now, browser requests from the specified frontend origin can access your API responses when the CORS rules permit them.

❖ Allow Multiple Frontend Domains

If your application has separate development and production frontend domains, you can use an origin allowlist.

const allowedOrigins = [
    "http://localhost:5173",
    "https://example.com"
];

app.use(cors({
    origin: function (origin, callback) {
        if (!origin || allowedOrigins.includes(origin)) {
            return callback(null, true);
        }

        return callback(new Error("Origin not allowed by CORS"));
    }
}));

Replace https://example.com with your actual website domain.

If your application uses cookies for authentication, configure credentials: true and use a specific allowed origin rather than *. You must also configure the frontend request and cookie settings correctly.

Important: CORS is not an authentication system. It does not prevent non-browser clients from sending requests to your API. Always protect sensitive endpoints with authentication and authorization.

✦ 7. Implement Rate Limiting

Rate limiting is especially useful for login, signup, password reset, and other sensitive API endpoints.

Install the package:

npm install express-rate-limit

Import it into your application:

const rateLimit = require("express-rate-limit");

Create a rate limiter:

const apiLimiter = rateLimit({
    windowMs: 15 * 60 * 1000,
    limit: 100,
    standardHeaders: "draft-8",
    legacyHeaders: false,
    message: {
        success: false,
        message: "Too many requests. Please try again later."
    }
});

Apply the limiter to your API routes:

app.use("/api/", apiLimiter);

In this example:

  • windowMs defines a 15-minute time window.

  • limit allows up to 100 requests per client IP within that window.

  • message defines the response when the limit is exceeded.

❖ Create a Separate Login Rate Limiter

Login APIs need stricter limits than ordinary public endpoints.

const loginLimiter = rateLimit({
    windowMs: 15 * 60 * 1000,
    limit: 5,
    standardHeaders: "draft-8",
    legacyHeaders: false,
    message: {
        message: "Too many login attempts. Try again later."
    }
});

app.use("/api/login", loginLimiter);

This example allows five login requests per IP during a 15-minute window.

For production applications with multiple server instances, consider using a shared rate-limit store. Also configure trusted proxies correctly so that client IP addresses are identified reliably.

✦ 8. Complete Secure Express.js API Example

Now let's combine Express, Helmet, CORS, and Rate Limiting in one practical application.

Create or replace your server.js file with this code:

const express = require("express");
const helmet = require("helmet");
const cors = require("cors");
const rateLimit = require("express-rate-limit");

const app = express();

const PORT = 3000;

// Parse JSON request bodies
app.use(express.json());

// Security headers
app.use(helmet());

// CORS configuration
app.use(cors({
    origin: "http://localhost:5173",
    methods: ["GET", "POST", "PUT", "DELETE"],
    allowedHeaders: ["Content-Type", "Authorization"]
}));

// General API rate limiter
const apiLimiter = rateLimit({
    windowMs: 15 * 60 * 1000,
    limit: 100,
    standardHeaders: "draft-8",
    legacyHeaders: false,
    message: {
        message: "Too many requests. Try again later."
    }
});

app.use("/api/", apiLimiter);

// Login rate limiter
const loginLimiter = rateLimit({
    windowMs: 15 * 60 * 1000,
    limit: 5,
    standardHeaders: "draft-8",
    legacyHeaders: false,
    message: {
        message: "Too many login attempts."
    }
});

// Home route
app.get("/", (req, res) => {
    res.json({
        message: "Welcome to Secure Node.js API"
    });
});

// Public API
app.get("/api/users", (req, res) => {
    res.json({
        success: true,
        users: [
            {
                id: 1,
                name: "Rahul"
            },
            {
                id: 2,
                name: "Amit"
            }
        ]
    });
});

// Login API demonstration
app.post("/api/login", loginLimiter, (req, res) => {
    res.json({
        success: true,
        message: "Login endpoint reached successfully"
    });
});

// Start server
app.listen(PORT, () => {
    console.log(`Server running at http://localhost:${PORT}`);
});

This example demonstrates security middleware configuration. The login route is only a demonstration; a real login system must validate credentials, verify password hashes, and issue a properly secured session or access token.

✦ 9. Test Your Secure APIs

Start your application:

node server.js

You can test the endpoints using Postman or Thunder Client.

❖ Test 1: Home API

Method: GET

http://localhost:3000/

Expected response:

{
    "message": "Welcome to Secure Node.js API"
}

❖ Test 2: Users API

Method: GET

http://localhost:3000/api/users

Expected response:

{
    "success": true,
    "users": [
        {
            "id": 1,
            "name": "Rahul"
        },
        {
            "id": 2,
            "name": "Amit"
        }
    ]
}

❖ Test 3: Login API

Method: POST

http://localhost:3000/api/login

Select Body → raw → JSON:

{
    "email": "test@example.com",
    "password": "12345678"
}

The demonstration route returns a success message. It does not actually authenticate the supplied credentials.

❖ Test 4: Rate Limit

Send repeated requests to:

http://localhost:3000/api/login

After the configured limit is exceeded within the time window, the API will return a rate-limit response.

✦ 10. Best Practices for Production APIs

For a real-world Node.js application, follow these additional practices:

  1. Use HTTPS: Encrypt communication between clients and your server.

  2. Validate input: Check user input before processing requests.

  3. Secure authentication: Use secure sessions or properly verified access tokens.

  4. Protect database queries: Use parameterized SQL queries.

  5. Hide sensitive information: Never expose database passwords, tokens, or internal error details.

  6. Keep packages updated: Regularly review dependencies and security advisories.

  7. Configure CORS carefully: Allow only the frontend origins that need access.

  8. Use suitable rate limits: Apply stricter limits to sensitive endpoints.

  9. Monitor API activity: Track unusual traffic and repeated failures.

  10. Test security settings: Verify headers, authentication, permissions, and error responses before deployment.

✦ 11. Difference Between Rate Limiting, CORS and Helmet

Feature Main Purpose
Rate Limiting Controls repeated API requests
CORS Controls browser-based cross-origin access
Helmet Configures HTTP security headers

These tools solve different security problems. Using all three provides useful layers of protection, but none of them replaces proper authentication, authorization, secure database queries, or application-level validation.

✦ Frequently Asked Questions (FAQs)

❖ Q1. What is Rate Limiting in Node.js?

Rate limiting controls the number of requests a client can make to an API within a defined time period. It helps reduce abusive traffic and repeated login attempts.

❖ Q2. What is CORS in Express.js?

CORS is a browser security mechanism that controls whether a web page from another origin can access an API response.

❖ Q3. Why should developers use Helmet?

Helmet helps configure HTTP security headers that improve browser-side security for Express.js applications.

❖ Q4. Can Rate Limiting prevent all API attacks?

No. It can reduce excessive requests and some automated attacks, but it cannot prevent every security threat. Authentication, authorization, validation, and monitoring are also necessary.

❖ Q5. Are Helmet and CORS enough to secure a Node.js API?

No. They are useful security layers, but a secure API also needs proper authentication, authorization, input validation, database protection, HTTPS, and secure configuration.

✦ Conclusion

Securing a Node.js API is an important responsibility for every backend developer. Rate Limiting helps control repeated requests, CORS manages browser-based cross-origin access, and Helmet helps configure important HTTP security headers.

By combining these tools with secure authentication, input validation, HTTPS, and safe database practices, developers can build more reliable Express.js applications.

At Param Classes, students can learn Node.js, Express.js, REST API development, database connectivity, and practical backend projects through hands-on training.

←
Post 34 of 40
→