Express API CORS
Introduction
When developing modern web applications, you'll often have a frontend application on one domain making requests to your Express REST API on another domain. For example, your React application might be running on localhost:3000
, while your Express API runs on localhost:5000
. In production, your frontend might be hosted at example.com
while your API is at api.example.com
.
By default, browsers block these cross-origin HTTP requests due to the Same-Origin Policy, a critical security mechanism built into web browsers. This is where CORS (Cross-Origin Resource Sharing) comes in.
CORS is a mechanism that uses HTTP headers to tell browsers whether a specific web application can share resources with another web application from a different origin (domain, protocol, or port).
Why is CORS Necessary?
Without CORS, if your frontend JavaScript code tries to make a fetch request to an API on a different origin, the browser will block the request for security reasons. This security feature protects users from malicious scripts that might try to access sensitive data across domains.
Let's look at a common scenario:
// Frontend running on http://localhost:3000
fetch('http://localhost:5000/api/users')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
Without proper CORS configuration, this request would fail with an error like:
Access to fetch at 'http://localhost:5000/api/users' from origin 'http://localhost:3000'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is
present on the requested resource.
Implementing CORS in Express
Express makes it easy to implement CORS in your API using the cors
middleware package.
Step 1: Install the CORS package
npm install cors
Step 2: Basic Implementation
Here's how to enable CORS for all requests:
const express = require('express');
const cors = require('cors');
const app = express();
// Enable CORS for all routes
app.use(cors());
app.get('/api/users', (req, res) => {
res.json([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
]);
});
app.listen(5000, () => {
console.log('Server running on port 5000');
});
With this basic setup, your API now includes the necessary CORS headers in its responses, allowing requests from any origin.
CORS Configuration Options
The simple implementation above is fine for development, but for production applications, you'll typically want more control over which domains can access your API.
Configuring Specific Origins
const corsOptions = {
origin: 'https://example.com',
optionsSuccessStatus: 200 // some legacy browsers (IE11) choke on 204
};
app.use(cors(corsOptions));
Multiple Origins
const corsOptions = {
origin: ['https://example.com', 'https://www.example.com', 'https://app.example.com'],
optionsSuccessStatus: 200
};
app.use(cors(corsOptions));
Dynamic Origin Validation
You can also use a function to dynamically determine whether to allow a request based on the origin:
const corsOptions = {
origin: function (origin, callback) {
const allowedOrigins = ['https://example.com', 'https://www.example.com'];
if (allowedOrigins.includes(origin) || !origin) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
optionsSuccessStatus: 200
};
app.use(cors(corsOptions));
CORS for Specific Routes
You can also apply CORS to specific routes instead of all routes:
// Enable CORS for all routes
app.use(cors());
// Different CORS settings for a specific route
const specificCorsOptions = {
origin: 'https://special-client.com',
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true
};
app.get('/api/special-data', cors(specificCorsOptions), (req, res) => {
res.json({ message: 'This is special data!' });
});
Advanced CORS Options
The CORS middleware accepts various options for finer control:
Handling Credentials
If your API requests include credentials like cookies, you need to set the credentials
option:
const corsOptions = {
origin: 'https://example.com',
credentials: true,
optionsSuccessStatus: 200
};
app.use(cors(corsOptions));
Configuring Allowed Methods
Specify which HTTP methods are allowed:
const corsOptions = {
origin: 'https://example.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
optionsSuccessStatus: 200
};
app.use(cors(corsOptions));
Setting Allowed Headers
Control which headers can be used in requests:
const corsOptions = {
origin: 'https://example.com',
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
optionsSuccessStatus: 200
};
app.use(cors(corsOptions));
Exposing Headers
Specify which headers from the response should be accessible to the client:
const corsOptions = {
origin: 'https://example.com',
exposedHeaders: ['Content-Length', 'X-Custom-Header'],
optionsSuccessStatus: 200
};
app.use(cors(corsOptions));
Real-World Example: Building a Public API with Selective CORS
Let's create an Express API that serves different content with different CORS policies:
const express = require('express');
const cors = require('cors');
const app = express();
// Public API - accessible from anywhere
const publicApiCors = {
origin: '*',
methods: ['GET'],
};
// Partner API - only accessible from trusted domains
const partnerApiCors = {
origin: ['https://trusted-partner1.com', 'https://trusted-partner2.com'],
methods: ['GET', 'POST', 'PUT'],
credentials: true,
allowedHeaders: ['Content-Type', 'Authorization']
};
// Internal API - only accessible from our own domains
const internalApiCors = {
origin: ['https://admin.ourcompany.com', 'https://dashboard.ourcompany.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
credentials: true,
};
// Public data endpoint - anyone can access
app.get('/api/public/products', cors(publicApiCors), (req, res) => {
res.json([
{ id: 1, name: 'Basic Widget', price: 9.99 },
{ id: 2, name: 'Advanced Widget', price: 19.99 }
]);
});
// Partner-only endpoint - only trusted partners can access
app.get('/api/partners/discounts', cors(partnerApiCors), (req, res) => {
res.json([
{ id: 1, product_id: 1, partner_discount: 2.00 },
{ id: 2, product_id: 2, partner_discount: 5.00 }
]);
});
// Internal-only endpoint - only our company domains can access
app.get('/api/internal/sales', cors(internalApiCors), (req, res) => {
res.json({
total_sales: 52643.21,
monthly_growth: 12.7,
top_product_id: 2
});
});
app.listen(5000, () => {
console.log('Server running on port 5000');
});
Debugging CORS Issues
If you're facing CORS issues, here are some common problems and solutions:
-
Check the browser console: It often provides specific error messages about the CORS problem.
-
Verify origin configuration: Ensure that the origin in your CORS configuration matches exactly with the requesting site's origin. For example,
http://localhost:3000
is different fromhttp://localhost:3000/
(trailing slash). -
Check for missing headers: Some requests, particularly those with credentials, require specific headers.
-
Preflight requests: For complex requests, browsers send a preflight OPTIONS request first. Ensure your server handles OPTIONS requests correctly.
-
Using browser extensions: For development, you can use browser extensions to disable CORS restrictions temporarily, but never rely on this for production.
Common CORS Errors and Fixes
Error: "No 'Access-Control-Allow-Origin' header"
Fix: Ensure you're using the CORS middleware and have correctly configured the allowed origins.
app.use(cors({
origin: 'http://your-allowed-origin.com'
}));
Error: "Method not allowed by CORS"
Fix: Specify the allowed methods in your CORS configuration:
app.use(cors({
origin: 'http://your-allowed-origin.com',
methods: ['GET', 'POST', 'PUT', 'DELETE']
}));
Error related to credentials
Fix: Enable credentials in your CORS configuration and ensure your frontend request includes credentials:
// Backend
app.use(cors({
origin: 'http://your-allowed-origin.com',
credentials: true
}));
// Frontend
fetch('http://api.example.com/data', {
credentials: 'include'
})
Summary
CORS is a crucial security feature for web applications that controls how web pages in one domain can request resources from another domain. In Express.js:
- Use the
cors
middleware package to easily implement CORS in your API - Configure allowed origins, methods, and headers based on your security requirements
- Apply different CORS settings to different routes when needed
- Remember that proper CORS configuration is essential for both development and production environments
Understanding and correctly implementing CORS is essential for building secure, modern web applications where frontend and backend often exist on different domains.
Additional Resources
Exercises
-
Create an Express API with two endpoints: one accessible from any origin and another only accessible from a specific domain.
-
Modify an Express API to allow requests only from a list of trusted domains that you specify.
-
Create an API endpoint that allows different HTTP methods (GET, POST, PUT, DELETE) for different origins.
-
Implement a dynamic CORS validator that checks against a database of allowed domains instead of a hardcoded list.
-
Build a simple frontend and backend on different ports and implement CORS to allow them to communicate properly.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)