Skip to main content

Express CORS Middleware

Introduction

When building modern web applications, you'll often need your frontend (running on one domain) to communicate with your backend API (potentially running on a different domain). However, web browsers implement a security feature called the Same-Origin Policy that restricts how documents or scripts from one origin can interact with resources from another origin.

Cross-Origin Resource Sharing (CORS) is a mechanism that allows servers to specify who can access their resources and how. In Express.js applications, implementing CORS is made simple with the cors middleware package.

In this tutorial, you'll learn:

  • What CORS is and why it matters
  • How to install and implement the CORS middleware in Express
  • Various configuration options to customize CORS behavior
  • Best practices for secure CORS implementation

What is CORS and Why Do We Need It?

The Same-Origin Policy Problem

By default, browsers restrict cross-origin HTTP requests initiated from scripts for security reasons. For example, if your frontend JavaScript application running on https://myapp.com tries to make an AJAX request to your API at https://api.myapp.com, the browser will block this request unless the API server explicitly allows it.

Without proper CORS headers, you might see errors like this in your browser console:

Access to fetch at 'https://api.example.com/data' from origin 'https://myapp.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

The CORS middleware for Express solves this problem by adding the appropriate HTTP headers to your API responses.

Installing the CORS Middleware

First, you need to install the cors package using npm or yarn:

bash
npm install cors

or using Yarn:

bash
yarn add cors

Basic CORS Implementation

Here's a simple Express application with CORS enabled:

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

// Create Express app
const app = express();

// Enable CORS for all routes
app.use(cors());

// Define a route
app.get('/api/data', (req, res) => {
res.json({ message: 'This response is accessible from any origin!' });
});

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

With this basic setup, any website can make requests to your API. The server will include the following headers in its responses:

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE

Configuring CORS Options

Allow Specific Origins

In production, it's often better to limit access to specific origins rather than using the wildcard *:

javascript
const corsOptions = {
origin: 'https://myapp.com',
optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
};

app.use(cors(corsOptions));

Allow Multiple Origins

You can also specify an array of allowed origins:

javascript
const corsOptions = {
origin: ['https://myapp.com', 'https://admin.myapp.com', 'http://localhost:3000'],
optionsSuccessStatus: 200
};

app.use(cors(corsOptions));

Dynamic Origin Validation

For more complex scenarios, you can use a function to determine if an origin should be allowed:

javascript
const corsOptions = {
origin: function (origin, callback) {
const allowedOrigins = ['https://myapp.com', 'https://admin.myapp.com'];
// Allow requests with no origin (like mobile apps, curl requests)
if (!origin || allowedOrigins.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
optionsSuccessStatus: 200
};

app.use(cors(corsOptions));

Configuring Additional Options

The CORS middleware offers several other configuration options:

javascript
const corsOptions = {
origin: 'https://myapp.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'], // Allowed HTTP methods
allowedHeaders: ['Content-Type', 'Authorization'], // Allowed headers
exposedHeaders: ['X-Custom-Header'], // Headers that browsers are allowed to access
credentials: true, // Allows cookies to be sent with requests
maxAge: 86400 // How long the results of a preflight request can be cached (in seconds)
};

app.use(cors(corsOptions));

Enabling CORS for Specific Routes

Instead of enabling CORS for all routes, you can apply it selectively:

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

// Public API route with CORS enabled
app.get('/api/public', cors(), (req, res) => {
res.json({ message: 'This is public data!' });
});

// Private API route with no CORS (same-origin only)
app.get('/api/private', (req, res) => {
res.json({ message: 'This data is only accessible from the same origin!' });
});

app.listen(3000);

Handling Preflight Requests

For certain HTTP methods and when sending certain headers, browsers will first send an OPTIONS request (called a "preflight" request) to check if the actual request is allowed. The CORS middleware automatically handles these preflight requests:

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

const corsOptions = {
origin: 'https://myapp.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
};

// Enable pre-flight requests for all routes
app.options('*', cors(corsOptions));

// Your routes
app.put('/api/update-user', cors(corsOptions), (req, res) => {
// Update user logic
res.json({ success: true });
});

app.listen(3000);

Real-World Example: Building a Secured API

Here's a more complete example of an Express API with CORS properly configured:

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

// Define your environment-specific configurations
const isProduction = process.env.NODE_ENV === 'production';
const allowedOrigins = isProduction
? ['https://myapp.com', 'https://www.myapp.com']
: ['http://localhost:3000'];

// Configure CORS
app.use(cors({
origin: function (origin, callback) {
// Allow requests with no origin (like mobile apps)
if (!origin) return callback(null, true);

if (allowedOrigins.indexOf(origin) === -1) {
const msg = 'The CORS policy for this site does not allow access from the specified Origin.';
return callback(new Error(msg), false);
}
return callback(null, true);
},
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 3600
}));

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

// Define routes
app.get('/api/products', (req, res) => {
// Return product data
res.json([
{ id: 1, name: 'Product 1' },
{ id: 2, name: 'Product 2' }
]);
});

app.post('/api/orders', (req, res) => {
// Process new order
console.log('Order received:', req.body);
res.status(201).json({
success: true,
orderId: 'ORD12345'
});
});

// Error handling middleware
app.use((err, req, res, next) => {
if (err.message.includes('CORS')) {
return res.status(403).json({ error: 'CORS error', message: err.message });
}

res.status(500).json({ error: 'Server error', message: err.message });
});

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

CORS Security Best Practices

  1. Never use wildcard origins in production: Instead of origin: '*', always specify the exact origins that should have access to your API.

  2. Limit HTTP methods: Only allow the methods that your API actually needs to support.

  3. Validate origins dynamically: For applications with many allowed origins or dynamic subdomains, implement proper validation logic.

  4. Set appropriate maxAge: This reduces the number of preflight requests, but don't set it too high, as it makes CORS policy changes slower to propagate.

  5. Be careful with credentials: Only enable credentials: true when you actually need to send cookies or authentication headers.

Testing Your CORS Configuration

You can test if your CORS setup works correctly by creating a simple HTML page that makes a request to your API:

html
<!DOCTYPE html>
<html>
<head>
<title>CORS Test</title>
</head>
<body>
<h1>CORS Test</h1>
<button id="testButton">Test API Request</button>
<div id="result"></div>

<script>
document.getElementById('testButton').addEventListener('click', async () => {
try {
const response = await fetch('http://localhost:3000/api/data', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});

const data = await response.json();
document.getElementById('result').innerText = JSON.stringify(data, null, 2);
} catch (error) {
document.getElementById('result').innerText = `Error: ${error.message}`;
}
});
</script>
</body>
</html>

Summary

CORS is a crucial security mechanism for web applications that need to communicate across different origins. In Express.js, the cors middleware package makes it straightforward to configure and control cross-origin requests:

  • Install the cors middleware using npm or yarn
  • Apply the middleware globally or selectively to specific routes
  • Configure allowed origins, methods, headers, and other options
  • Implement proper security practices to restrict access as appropriate

By properly implementing CORS, you can build secure web APIs that can be safely accessed by client applications from different origins.

Additional Resources

Exercises

  1. Create an Express API with two endpoints: one that allows requests from any origin and another that only allows requests from 'http://localhost:3000'.

  2. Implement a CORS configuration that allows requests from multiple specific domains and includes custom headers.

  3. Build a simple frontend application that makes both simple and preflight requests to your CORS-enabled API, and verify that the communication works correctly.

  4. Modify your CORS configuration to validate origins dynamically based on a pattern (e.g., allow all subdomains of 'example.com').

  5. Implement different CORS configurations for development and production environments in your Express application.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)