RecipesHandling CORS

Handling CORS with Kaito

Kaito does not include any CORS handling out of the box. This is by design to keep the library lightweight and unopinionated. You can easily implement CORS handling in your server by using the before & transform lifecycle methods.

const ALLOWED_ORIGINS = ['http://localhost:3000', 'https://app.example.com'];
 
export const router = create({
	getContext,
	before: async req => {
		if (req.method === 'OPTIONS') {
			return new Response(null, {status: 204}); // Return early to skip the router. This response will be passed to `.transform()`
		}
	},
 
	transform: async (request, response) => {
		const origin = request.headers.get('origin');
 
		// Include CORS headers if the origin is allowed
		if (origin && ALLOWED_ORIGINS.includes(origin)) {
			response.headers.set('Access-Control-Allow-Origin', origin);
			response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
			response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
			response.headers.set('Access-Control-Max-Age', '86400');
			response.headers.set('Access-Control-Allow-Credentials', 'true');
		}
	},
});

Experimental CORS API

Kaito also provides an experimental CORS API that can simplify the setup. ⚠️ This API is experimental and may change or be removed at any time. We recommend using the manual implementation above for production applications.

import {create} from '@kaito-http/core';
import {experimental_createCORSTransform} from '@kaito-http/core/cors';
 
// The origin matcher supports exact matches and protocol-specific wildcard subdomains
const ALLOWED_ORIGINS = [
	// Exact matches - protocol required
	'https://example.com', // matches only https://example.com
	'http://localhost:3000', // matches only http://localhost:3000
 
	// Wildcard subdomain matches - protocol required
	'https://*.myapp.com', // matches https://dashboard.myapp.com
	// does NOT match https://myapp.com or http://dashboard.myapp.com
 
	// To match both HTTP and HTTPS, include both
	'https://*.staging.com', // matches https://app.staging.com
	'http://*.staging.com', // matches http://app.staging.com
 
	// To match both subdomain and root domain, include both
	'https://*.production.com', // matches https://app.production.com
	'https://production.com', // matches https://production.com
];
 
const cors = experimental_createCORSTransform(ALLOWED_ORIGINS);
 
export const router = create({
	getContext,
	before: async req => {
		if (req.method === 'OPTIONS') {
			// Return early to skip the router
			// This response will still be passed to `.transform()`
			// so our CORS headers will be applied
			return new Response(null, {status: 204});
		}
	},
	transform: async (request, response) => {
		cors(request, response);
	},
});

The experimental CORS API includes a powerful origin matcher that supports both exact matches and wildcard subdomains. Important rules to note:

  • Protocol (http:// or https://) must be specified for all patterns
  • Wildcard patterns (e.g., https://*.example.com) will ONLY match subdomains, NOT the root domain
  • To match both HTTP and HTTPS for a domain, include both patterns
  • To match both subdomains and root domain, include both patterns

Examples:

  • https://example.com - Matches exactly this origin with HTTPS
  • https://*.example.com - Matches HTTPS subdomains like https://app.example.com, but NOT https://example.com or http://app.example.com
  • http://localhost:3000 - Matches exactly this origin with HTTP

References