Skip to main content

CORS Plugin

Enables Cross-Origin Resource Sharing (CORS) in your application. Handles preflight requests and sets appropriate CORS headers for both simple and complex requests.

When you only configure origin, Balda keeps its default CORS headers in place. That means preflight responses still send Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE, and Access-Control-Allow-Headers will echo the incoming Access-Control-Request-Headers unless you explicitly override allowedHeaders.

Quick Start​

import { Server } from "balda";

const server = new Server({
plugins: {
cors: {
origin: ["http://localhost:3000"],
},
},
});

With the configuration above, Balda still applies the default allowed methods and will automatically mirror requested preflight headers.

Configuration​

origin​

Configure allowed origins:

// Allow all origins (development only)
cors: {
origin: "*";
}

// Allow specific origins
cors: {
origin: ["https://myapp.com", "https://admin.myapp.com"];
}

// Allow origins with regex
cors: {
origin: ["https://myapp.com", /^https:\/\/.*\.myapp\.com$/];
}

methods​

Configure allowed HTTP methods:

// Default
cors: {
methods: ["GET", "HEAD", "PUT", "PATCH", "POST", "DELETE"];
}

// Custom
cors: {
methods: ["GET", "POST", "PUT", "DELETE"];
}

// String format
cors: {
methods: "GET,POST,PUT,DELETE";
}

allowedHeaders​

Configure allowed request headers:

If you omit allowedHeaders, Balda uses the browser's requested preflight headers by echoing Access-Control-Request-Headers.

// Default: echo requested preflight headers
cors: {
origin: ["https://myapp.com"];
}

// Specific headers
cors: {
allowedHeaders: ["Content-Type", "Authorization", "X-API-Key"];
}

// Allow all
cors: {
allowedHeaders: "*";
}

exposedHeaders​

Expose response headers to the client:

cors: {
exposedHeaders: ["X-Total-Count", "X-Page-Count"];
}

credentials​

Enable credentials (cookies, authorization headers):

cors: {
origin: ['https://myapp.com'], // Required with credentials
credentials: true
}

maxAge​

Cache preflight results (in seconds):

cors: {
maxAge: 86400; // 24 hours
}

Complete Configuration​

const server = new Server({
plugins: {
cors: {
origin: ["https://myapp.com"],
methods: ["GET", "POST", "PUT", "DELETE", "PATCH"],
allowedHeaders: ["Content-Type", "Authorization"],
exposedHeaders: ["X-Total-Count"],
credentials: true,
maxAge: 86400,
preflightContinue: false,
optionsSuccessStatus: 204,
},
},
});

Route-Level CORS​

Apply CORS to specific routes:

import { cors } from "balda";

@controller("/api")
export class ApiController {
@get("/public", {
middleware: [cors({ origin: "*" })],
})
async publicEndpoint(req: Request, res: Response) {
res.json({ message: "Public endpoint" });
}

@get("/private", {
middleware: [
cors({
origin: ["https://myapp.com"],
credentials: true,
}),
],
})
async privateEndpoint(req: Request, res: Response) {
res.json({ message: "Private endpoint" });
}
}

Environment-Based Configuration​

const isProduction = process.env.NODE_ENV === "production";

const server = new Server({
plugins: {
cors: {
origin: isProduction
? process.env.ALLOWED_ORIGINS?.split(",")
: ["http://localhost:3000"],
credentials: isProduction,
maxAge: isProduction ? 86400 : undefined,
},
},
});

Security Considerations​

❌ Wildcard with Credentials​

This doesn't work - credentials require specific origins:

cors: {
origin: '*',
credentials: true // Error!
}

✅ Specific Origins with Credentials​

cors: {
origin: ['https://myapp.com', 'https://admin.myapp.com'],
credentials: true // Works!
}

Production Configuration​

cors: {
origin: ['https://myapp.com'], // Specific origins only
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
maxAge: 86400
}

CORS Headers​

Request Headers (from browser)​

  • Origin - The origin of the request
  • Access-Control-Request-Method - HTTP method for actual request
  • Access-Control-Request-Headers - Headers for actual request

Response Headers (from server)​

  • Access-Control-Allow-Origin - Allowed origins
  • Access-Control-Allow-Methods - Allowed HTTP methods
  • Access-Control-Allow-Headers - Allowed headers
  • Access-Control-Expose-Headers - Headers exposed to client
  • Access-Control-Allow-Credentials - Whether credentials allowed
  • Access-Control-Max-Age - Preflight cache duration

Preflight Requests​

CORS automatically handles preflight OPTIONS requests:

OPTIONS /api/users
Origin: https://myapp.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization

→ Response:
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400