Express.js CORS

Cross-Origin Resource Sharing (CORS) is a vital security mechanism implemented by web browsers. By default, browsers block scripts (like JavaScript's fetch or XMLHttpRequest) from making requests to a different domain than the one that served the web page. This is known as the Same-Origin Policy. In Express.js, configuring CORS allows your server to tell the browser, "I trust this specific external domain, so please let it access my data." Without CORS configuration, your frontend running on localhost:3000 won't be able to talk to your backend on localhost:5000.

Developer Tip: You will likely encounter your first CORS error when your frontend and backend are hosted on different ports or subdomains during development. It’s a rite of passage for web developers!

 

Key Features of CORS

  • Cross-Origin Requests: It acts as a gateway, allowing or denying requests based on the origin domain, protocol (http vs https), and port.
  • Custom Headers: CORS allows you to whitelist specific headers (like x-api-key or Authorization) that the client is permitted to send.
  • Flexible Configuration: You can set rules globally for the whole app, or fine-tune them for specific sensitive endpoints.
  • Browser Compliance: It follows the official W3C standards, ensuring your API works seamlessly across Chrome, Firefox, Safari, and Edge.
Best Practice: Always be specific with your origins in production. Instead of allowing everything, only whitelist the exact domains where your frontend application is hosted.

 

Configuring CORS in Express.js

Installing the cors Package
While you can manually set headers, the cors middleware is the industry standard because it handles edge cases and complex preflight logic automatically.

npm install cors

Enabling CORS for All Routes
If you are building a public API or are in the early stages of development, you can enable CORS for every single route with just one line of code.

Example:

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

// This allows ANY domain to access your routes. 
// Great for testing, but use caution in production.
app.use(cors()); 

app.get('/', (req, res) => {
    res.send('CORS enabled for all origins!');
});

app.listen(3000, () => {
    console.log('Server running on port 3000');
});
Watch Out: Using app.use(cors()) without arguments sets the origin to * (wildcard). This means any website in the world can make requests to your API, which can lead to security vulnerabilities if your API handles sensitive user data.

Restricting Access to Specific Origins
In a real-world scenario, you want to restrict access to your trusted frontend application only. You can pass a configuration object to the cors() function.

Example:

const corsOptions = {
    origin: 'https://www.my-awesome-app.com', // Only allow this domain
    methods: ['GET', 'POST'], // Only allow these HTTP verbs
    allowedHeaders: ['Content-Type', 'Authorization'], // Only allow these headers
    optionsSuccessStatus: 200 // Some legacy browsers (IE11) choke on 204
};

app.use(cors(corsOptions));
Common Mistake: Forgetting that https://example.com and http://example.com are considered different origins because the protocol is different. Always include the correct protocol and port.

Enabling CORS for Specific Routes
Sometimes you have a public endpoint (like a product catalog) and a private one (like an admin dashboard). You can apply CORS as middleware to individual routes.

Example:

// Public route - accessible from anywhere
app.get('/api/products', cors(), (req, res) => {
    res.json({ product: 'Laptop', price: 999 });
});

// Private route - standard Same-Origin policy applies
app.get('/api/admin-stats', (req, res) => {
    res.json({ totalSales: 50000 });
});

Handling Preflight Requests
When a browser tries to send a "non-simple" request (like a DELETE request or one with custom headers), it first sends an OPTIONS request to the server. This is called a "preflight" check. The cors package handles this for you, but you can explicitly enable it for specific complex routes.

Example:

// Handle preflight for a specific complex route
app.options('/api/delete-user', cors()); 
app.delete('/api/delete-user', cors(), (req, res) => {
    res.send('User deleted');
});

Custom Middleware for CORS
If you want to understand what's happening under the hood, here is how you would manually set the headers without the cors package. This is useful for debugging but usually less robust than the middleware.

Example:

app.use((req, res, next) => {
    res.header('Access-Control-Allow-Origin', 'https://example.com');
    res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
    res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    
    // Intercept preflight OPTIONS method
    if (req.method === 'OPTIONS') {
        return res.sendStatus(200);
    }
    next();
});

 

Example Application with CORS

This is a complete, production-ready pattern. It uses an environment variable to set the allowed origin, which is a common practice in modern DevOps workflows.

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

// In production, you would set this via process.env.ALLOWED_ORIGIN
const whitelist = ['https://my-frontend.com', 'http://localhost:3000'];

const corsOptions = {
    origin: function (origin, callback) {
        // allow requests with no origin (like mobile apps or curl requests)
        if (!origin) return callback(null, true);
        
        if (whitelist.indexOf(origin) !== -1) {
            callback(null, true);
        } else {
            callback(new Error('Not allowed by CORS'));
        }
    },
    methods: ['GET', 'POST'],
    credentials: true // allow cookies to be sent cross-origin
};

app.use(cors(corsOptions));

app.get('/api/data', (req, res) => {
    res.json({ 
        status: 'success', 
        message: 'This data is protected by origin-specific CORS' 
    });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
    console.log(`Server running on port ${PORT}`);
});

 

Summary

CORS is not just a hurdle to jump over; it is a critical layer of defense for your Express.js applications. By using the cors middleware, you can precisely control who accesses your API and how they interact with it. Whether you are enabling it globally for a public service or restricting it to a single frontend domain for a private app, understanding how to configure these headers correctly will save you hours of debugging and keep your user data significantly safer.