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:
npm install cors
or using Yarn:
yarn add cors
Basic CORS Implementation
Here's a simple Express application with CORS enabled:
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 *
:
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:
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:
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:
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:
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:
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:
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
-
Never use wildcard origins in production: Instead of
origin: '*'
, always specify the exact origins that should have access to your API. -
Limit HTTP methods: Only allow the methods that your API actually needs to support.
-
Validate origins dynamically: For applications with many allowed origins or dynamic subdomains, implement proper validation logic.
-
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.
-
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:
<!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
-
Create an Express API with two endpoints: one that allows requests from any origin and another that only allows requests from 'http://localhost:3000'.
-
Implement a CORS configuration that allows requests from multiple specific domains and includes custom headers.
-
Build a simple frontend application that makes both simple and preflight requests to your CORS-enabled API, and verify that the communication works correctly.
-
Modify your CORS configuration to validate origins dynamically based on a pattern (e.g., allow all subdomains of 'example.com').
-
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! :)