Cross-Origin Resource Sharing (CORS) is a security mechanism that allows your API to be accessed from web pages on different domains. Express Zod API provides built-in CORS configuration.
Enabling CORS
CORS is disabled by default. You must explicitly enable it in your configuration:
import { createConfig } from "express-zod-api" ;
const config = createConfig ({
http: { listen: 8090 },
cors: true , // Enable CORS with default headers
});
CORS must be explicitly set to true or false. This ensures you make a conscious decision about cross-origin access to your API.
When you set cors: true, the following headers are sent:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Custom CORS Configuration
For more control, you can provide a function that returns custom CORS headers:
import { createConfig } from "express-zod-api" ;
const config = createConfig ({
cors : ({ defaultHeaders , request , endpoint , logger }) => ({
... defaultHeaders ,
"Access-Control-Allow-Origin" : "https://example.com" ,
"Access-Control-Max-Age" : "5000" ,
"Access-Control-Allow-Credentials" : "true" ,
}),
});
Configuration Parameters
The CORS function receives:
Parameter Type Description defaultHeadersRecord<string, string>Default CORS headers requestRequestExpress request object endpointAbstractEndpointThe matched endpoint loggerActualLoggerLogger instance
Common CORS Patterns
Allow Specific Origin
const config = createConfig ({
cors : ({ defaultHeaders }) => ({
... defaultHeaders ,
"Access-Control-Allow-Origin" : "https://myapp.com" ,
}),
});
Allow Multiple Origins
const allowedOrigins = [
"https://app.example.com" ,
"https://admin.example.com" ,
"http://localhost:3000" ,
];
const config = createConfig ({
cors : ({ defaultHeaders , request }) => {
const origin = request . headers . origin ;
if ( origin && allowedOrigins . includes ( origin )) {
return {
... defaultHeaders ,
"Access-Control-Allow-Origin" : origin ,
};
}
return defaultHeaders ;
},
});
Environment-Based Configuration
const config = createConfig ({
cors : ({ defaultHeaders }) => {
if ( process . env . NODE_ENV === "production" ) {
return {
... defaultHeaders ,
"Access-Control-Allow-Origin" : "https://myapp.com" ,
};
}
// Allow all origins in development
return defaultHeaders ;
},
});
Allow Credentials
For requests that include cookies or authentication:
const config = createConfig ({
cors : ({ defaultHeaders }) => ({
... defaultHeaders ,
"Access-Control-Allow-Origin" : "https://myapp.com" ,
"Access-Control-Allow-Credentials" : "true" ,
}),
});
When using Access-Control-Allow-Credentials: true, you cannot use Access-Control-Allow-Origin: *. You must specify an exact origin.
const config = createConfig ({
cors : ({ defaultHeaders }) => ({
... defaultHeaders ,
"Access-Control-Allow-Headers" : "Content-Type, Authorization, X-Api-Key" ,
}),
});
To make custom response headers available to the client:
const config = createConfig ({
cors : ({ defaultHeaders }) => ({
... defaultHeaders ,
"Access-Control-Expose-Headers" : "X-Total-Count, X-Page-Number" ,
}),
});
Async CORS Configuration
Your CORS function can be asynchronous for database lookups or external validation:
const config = createConfig ({
cors : async ({ defaultHeaders , request , logger }) => {
const origin = request . headers . origin ;
// Check if origin is allowed
const allowed = await db . allowedOrigins . exists ({ origin });
if ( allowed ) {
logger . info ( `CORS: Allowed origin ${ origin } ` );
return {
... defaultHeaders ,
"Access-Control-Allow-Origin" : origin ,
};
}
logger . warn ( `CORS: Rejected origin ${ origin } ` );
return defaultHeaders ;
},
});
Endpoint-Specific CORS
If you need different CORS rules for specific endpoints, use middleware or response customization:
import { Middleware } from "express-zod-api" ;
const publicMiddleware = new Middleware ({
handler : async ({ request , response }) => {
// Set CORS headers for public endpoints
response . setHeader ( "Access-Control-Allow-Origin" , "*" );
return {};
},
});
const publicEndpointsFactory = defaultEndpointsFactory
. addMiddleware ( publicMiddleware );
Preflight Requests
Express Zod API automatically handles OPTIONS preflight requests when CORS is enabled. You don’t need to configure anything special.
Testing CORS
Test CORS with curl:
# Preflight request
curl -X OPTIONS http://localhost:8090/api/users \
-H "Origin: https://example.com" \
-H "Access-Control-Request-Method: POST" \
-v
# Actual request
curl -X POST http://localhost:8090/api/users \
-H "Origin: https://example.com" \
-H "Content-Type: application/json" \
-d '{"name":"John"}' \
-v
Look for Access-Control-* headers in the response.
Header Description Access-Control-Allow-OriginWhich origins can access the resource Access-Control-Allow-MethodsWhich HTTP methods are allowed Access-Control-Allow-HeadersWhich request headers are allowed Access-Control-Allow-CredentialsWhether credentials can be sent Access-Control-Max-AgeHow long preflight results can be cached Access-Control-Expose-HeadersWhich response headers can be read by client
Security Considerations
Don't Use * in Production Avoid Access-Control-Allow-Origin: * in production. Specify exact origins.
Validate Origins When allowing multiple origins, validate them against a whitelist.
Be Careful with Credentials Only enable credentials for trusted origins. Never combine with *.
Limit Exposed Headers Only expose headers that clients need. Don’t expose sensitive information.
Complete Example
Here’s a production-ready CORS configuration:
import { createConfig } from "express-zod-api" ;
const allowedOrigins = process . env . ALLOWED_ORIGINS ?. split ( "," ) || [];
const config = createConfig ({
http: { listen: 8090 },
cors : ({ defaultHeaders , request , logger }) => {
const origin = request . headers . origin ;
// Development: allow all
if ( process . env . NODE_ENV === "development" ) {
return {
... defaultHeaders ,
"Access-Control-Allow-Credentials" : "true" ,
};
}
// Production: whitelist only
if ( origin && allowedOrigins . includes ( origin )) {
logger . debug ( `CORS: Allowed ${ origin } ` );
return {
... defaultHeaders ,
"Access-Control-Allow-Origin" : origin ,
"Access-Control-Allow-Credentials" : "true" ,
"Access-Control-Max-Age" : "3600" ,
};
}
logger . warn ( `CORS: Rejected ${ origin } ` );
return {}; // No CORS headers = request blocked
},
});
Troubleshooting
CORS Error in Browser Console
If you see:
Access to fetch at 'http://api.example.com' from origin 'http://app.example.com'
has been blocked by CORS policy
Solutions:
Ensure cors: true is set in config
Check that the origin is in your allowlist
Verify headers are being sent (use browser DevTools)
Credentials Not Working
Requirements for credentials:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin must be a specific origin (not *)
Client must send credentials: 'include' in fetch
Next Steps