Table of Contents
- Introduction
- Fetch API Modes Overview
- Same-Origin Mode
- CORS Mode
- No-CORS Mode
- CORS Configurations
- Best Practices
Introduction
In Part 1, we explored the fundamentals of CORS and its basic implementation. Now, let's dive deeper into the different modes available in the Fetch API and how they interact with CORS policies.
Fetch API Modes Overview
The Fetch API provides several modes that control how cross-origin requests are handled:
same-origin(default)corsno-corsnavigate
Each mode serves a specific purpose and comes with its own set of rules and limitations.
Same-Origin Mode
The default mode in Fetch API is same-origin. As we discovered in Part 1, this is the most restrictive mode.
// Default same-origin mode fetch('http://localhost:3004/products')
Remember: A request is considered cross-origin if any of these differ:
- Protocol (http/https)
- Domain
- Port number
Even a different port on the same domain triggers a CORS error:
CORS Mode
When working with cross-origin requests, cors mode is your primary tool:
fetch('http://localhost:3004/products', { mode: 'cors' })
This mode enables:
- Sending the
Originheader - Reading the response if proper CORS headers are present
- Supporting preflight requests when necessary
With proper server configuration:
const cors = require('cors'); app.use(cors());
The request succeeds with proper CORS headers:
No-CORS Mode
The often misunderstood no-cors mode:
fetch('http://localhost:4000/customers', { mode: 'no-cors' })
While this mode appears to work (no CORS errors), it comes with significant limitations:
Key limitations:
- Response type is "opaque"
- Response body is null
- Cannot access response data in your code
- Status code is always 0
Use cases for no-cors:
- Caching responses
- Loading images or resources
- Analytics requests
- Cases where response data isn't needed
CORS Configurations
Managing Origins
For production applications, you'll want to restrict allowed origins:
const allowedOrigins = ["http://localhost:3000", "https://example.com"] app.use(cors({ origin: function (origin, callback) { if(allowedOrigins.indexOf(origin) !== -1){ callback(null, origin) } else { callback(null, null); } } }));
Handling Credentials
When working with authenticated requests:
// Client-side fetch('http://api.example.com/data', { mode: 'cors', credentials: 'include' }) // Server-side app.use(cors({ credentials: true, origin: 'http://localhost:3000' }));
Best Practices
-
Mode Selection
- Use
corsmode for cross-origin requests - Stick with default
same-originfor same-origin requests - Only use
no-corswhen response data isn't needed
- Use
-
Security
- Never use
*forAccess-Control-Allow-Originin production - Maintain a whitelist of allowed origins
- Be specific with allowed methods and headers
- Never use
-
Error Handling
fetch('http://api.example.com/data', { mode: 'cors' }) .then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .catch(error => { console.error('CORS or network error:', error); }); -
Development vs Production
- Use permissive CORS settings in development
- Implement strict origin checks in production
- Monitor CORS errors in production logs
What's Next?
In the next part of this series, we'll explore:
- Understanding
Access-Control-Allow-Methods - Working with
Access-Control-Allow-Headers - Managing credentials with
Access-Control-Allow-Credentials
Until then, remember that CORS is not just a hurdle to overcome but a crucial security feature that protects users when implemented correctly.
Related Resources
[Continue to Part 3: CORS: Headers, Methods, and Credentials...]