Express CORS per Route — Fine-Grained Cross-Origin Control for Specific Endpoints
In this tutorial, you will learn about Express CORS per Route. We cover key concepts, practical examples, and best practices to help you master this topic.
Express allows configuring CORS at the route level using the cors package applied individually, enabling different origins, methods, and headers for different API endpoints within the same application.
What You'll Learn
- Applying CORS middleware to specific routes
- Using different CORS configs per endpoint
- Combining global and per-route CORS settings
Why It Matters
Different endpoints have different security requirements. A public API endpoint might allow any origin, while an admin endpoint needs strict origin control. DodaTech's Express API uses per-route CORS to allow public read endpoints wide access while restricting write operations.
flowchart TD
A["Request to /api/public"] --> B["CORS: Allow any origin"]
A2["Request to /api/admin"] --> C["CORS: Strict origin check"]
A3["Request to /api/webhook"] --> D["CORS: Specific partner origin"]
B --> E["Handle request"]
C --> F["Handle request"]
D --> G["Handle request"]
style B fill:#86efac,stroke:#16a34a
style C fill:#fef08a,stroke:#ca8a04
style D fill:#dbeafe,stroke:#2563eb
Code Examples
// Express per-route CORS configuration
const express = require('express');
const cors = require('cors');
const app = express();
// Public endpoint: any origin
app.get('/api/public/data',
cors({ origin: '*' }),
(req, res) => {
res.json({ message: 'Public data' });
}
);
// Authenticated endpoint: specific origin with credentials
const authCors = cors({
origin: 'https://app.example.com',
credentials: true,
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type', 'Authorization']
});
app.get('/api/user/profile', authCors, (req, res) => {
res.json({ name: 'Alice', email: 'alice@example.com' });
});
// Admin endpoint: restricted to admin domain
const adminCors = cors({
origin: ['https://admin.example.com', 'https://dashboard.internal'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Admin-Key']
});
app.delete('/api/admin/users/:id', adminCors, (req, res) => {
res.json({ deleted: req.params.id });
});
// Using a CORS config map for cleaner code
const corsConfigs = {
public: { origin: '*' },
auth: { origin: 'https://app.example.com', credentials: true },
admin: {
origin: ['https://admin.example.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
credentials: true
},
webhook: {
origin: ['https://stripe.com', 'https://api.github.com'],
methods: ['POST']
}
};
function routeCors(configName) {
return cors(corsConfigs[configName]);
}
app.get('/api/public', routeCors('public'), handler);
app.post('/api/auth/login', routeCors('auth'), handler);
app.post('/api/webhooks/stripe', routeCors('webhook'), handler);
# Test per-route CORS
curl -I -H "Origin: https://unknown.com" \
https://api.example.com/api/public/data
curl -I -H "Origin: https://unknown.com" \
https://api.example.com/api/user/profile
# Second request should be blocked
Common Mistakes
1. Applying Global CORS with Wrong Defaults
A global app.use(cors()) with permissive defaults may override stricter per-route configs.
2. Not Ordering Route-Specific CORS Before Global CORS
Place specific middleware before the global config to avoid accidental overrides.
3. Forgetting OPTIONS Handling for Non-Standard Methods
Each route must handle OPTIONS. The cors package handles this automatically.
4. Overlapping Route Patterns
A broader route pattern with permissive CORS may match before a specific route with strict CORS.
5. Not Testing Each Route Individually
Test CORS behavior for every route, not just the first one that works.
Practice Questions
- How do you apply CORS to a single Express route?
- Can different routes have different allowed origins?
- What happens if a route has no CORS middleware?
- How do you handle OPTIONS for different CORS configs?
- Should you use global or per-route CORS?
Answers:
- Pass cors(config) as middleware before the route handler.
- Yes. Each route can have its own cors() configuration.
- The browser blocks the response for cross-origin requests on that route.
- The cors package handles OPTIONS automatically when applied to a route.
- Use global for common defaults, per-route for endpoints with specific requirements.
Challenge: Refactor an Express API with a mix of public, authenticated, admin, and Webhook endpoints. Implement per-route CORS configuration, add a test suite that verifies each endpoint's CORS behavior, and generate a CORS policy documentation page.
FAQ
Mini Project
Build an Express API with three endpoint categories: public (open CORS), authenticated (specific origin with credentials), and admin (multiple specific origins). Implement per-route CORS, add automated tests with a headless browser that verifies each endpoint's CORS behavior, and generate a CORS documentation page.
What's Next
Learn FastAPI CORS middleware configuration for Python async APIs, then explore Django CORS with django-cors-headers.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro