Express’s CORS middleware doesn’t actually allow cross-origin requests; it just makes your server say it allows them by sending the right HTTP headers.

Here’s what a typical production CORS configuration for an Express API looks like:

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

const allowedOrigins = [
  'https://www.yourfrontend.com',
  'https://yourfrontend.netlify.app',
  'http://localhost:3000', // For local development
];

const corsOptions = {
  origin: function (origin, callback) {
    // allow requests with no origin (like mobile apps or curl requests)
    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,HEAD,PUT,PATCH,POST,DELETE",
  credentials: true, // Important for cookies and sessions
  optionsSuccessStatus: 204 // Some legacy browsers (IE11, various SmartTVs) choke on 204
};

app.use(cors(corsOptions));

// Your API routes here
app.get('/api/data', (req, res) => {
  res.json({ message: 'Hello from your API!' });
});

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

This setup aims to balance security with usability for your production API. The cors middleware, when configured with specific allowedOrigins, acts as a gatekeeper. It inspects the Origin header sent by incoming requests from browsers. If the Origin matches one of the domains in your allowedOrigins array, the middleware proceeds to add the necessary CORS headers (Access-Control-Allow-Origin, Access-Control-Allow-Methods, etc.) to the response. If the Origin is not in the list, it will block the request by returning an error to the browser.

The origin function is the core of this security. It receives the origin of the request. If origin is undefined (which can happen for non-browser clients like curl or some mobile apps), it’s allowed through by default (callback(null, true)). If an origin is present, it checks if that origin is present in the allowedOrigins array. If it’s not found (indexOf returns -1), an Error is passed to the callback, preventing the request from reaching your route handler and sending back an error to the browser. If it is found, callback(null, true) signals that the origin is permitted.

methods specifies which HTTP methods are allowed from cross-origin requests. Including GET,HEAD,PUT,PATCH,POST,DELETE covers most common API operations. credentials: true is crucial if your frontend application needs to send cookies, authorization headers, or use HTTP authentication schemes. When credentials is true, the Access-Control-Allow-Credentials header is automatically set to true, and the Access-Control-Allow-Origin header cannot be a wildcard (*). This is why allowedOrigins must be a specific array of origins when credentials is enabled. The optionsSuccessStatus: 204 is a common setting to ensure compatibility with older clients that might misinterpret a 200 OK response to a preflight OPTIONS request.

When a browser makes a cross-origin request, it first sends a "preflight" OPTIONS request to the server. The server’s response to this preflight request, guided by your corsOptions, tells the browser whether the actual request (e.g., a GET or POST) is permitted. If the preflight is successful (i.e., the Access-Control-Allow-Origin header matches the requesting origin), the browser then sends the actual request. If the preflight fails, the browser blocks the request before it even leaves the browser, and you’ll see a CORS error in the browser’s developer console.

The most surprising true thing about CORS is that the Access-Control-Allow-Origin header is not a security feature for your API’s backend data itself. It’s purely a browser security mechanism. A malicious client that doesn’t respect CORS (like a script running on a different domain, or a tool like Postman) can still make requests to your API and access your data, provided they know the API endpoint. CORS only prevents other web pages from making cross-origin requests to your API if they are not explicitly allowed.

The next concept you’ll likely encounter is how to handle more complex scenarios, such as dynamically allowing origins based on a database lookup or managing different CORS policies for different API routes.

Want structured learning?

Take the full Express course →