CORS
Configure Cross-Origin Resource Sharing (CORS) for your API routes.
What is CORS?
Cross-Origin Resource Sharing (CORS) is a browser security feature that restricts web pages from making requests to a different origin than the one that served the page. CORS headers let servers specify which origins may access their resources.
Bini.js includes built-in CORS support for API routes, so you can expose APIs to other origins without extra setup.
Default Configuration
CORS is enabled by default for all API routes in dev and preview. The default configuration includes:
- Access-Control-Allow-Origin:
*(all origins) - Access-Control-Allow-Methods:
GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD - Access-Control-Allow-Headers:
Content-Type, Authorization, X-Request-ID - Access-Control-Max-Age:
86400(24 hours for preflight requests)
Disabling CORS
Disable CORS by setting cors: false in your biniroute() configuration:
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { biniroute } from 'bini-router'
export default defineConfig({
plugins: [
react(),
biniroute({
cors: false, // Disable CORS for all API routes
}),
],
})CORS with Hono
With Hono you can configure CORS per route or globally using Hono's cors middleware:
// src/app/api/users.ts
import { Hono } from 'hono'
import { cors } from 'hono/cors'
const app = new Hono()
// Global CORS for all routes in this file
app.use(
'*',
cors({
origin: 'https://myapp.com',
allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
allowHeaders: ['Content-Type', 'Authorization'],
maxAge: 86400,
})
)
app.get('/users', (c) => c.json({ users: [] }))
app.post('/users', async (c) => c.json({ created: await c.req.json() }, 201))
export default app// src/app/api/public.ts
import { Hono } from 'hono'
import { cors } from 'hono/cors'
const app = new Hono()
// Route-specific CORS
app.use(
'/public/*',
cors({
origin: '*', // Public API allows all origins
})
)
app.get('/public/data', (c) => c.json({ data: 'Public data' }))
// Protected route with strict CORS
app.use(
'/private/*',
cors({
origin: 'https://admin.myapp.com',
allowMethods: ['GET'],
credentials: true,
})
)
app.get('/private/admin', (c) => c.json({ data: 'Admin only' }))
export default app| Option | Type | Description |
|---|---|---|
| origin | string | string[] | "*" | Allowed origins (default: "*") |
| allowMethods | string[] | Allowed HTTP methods |
| allowHeaders | string[] | Allowed request headers |
| maxAge | number | Preflight cache duration in seconds |
| credentials | boolean | Allow credentials (cookies, auth) |
| exposeHeaders | string[] | Headers exposed to the browser |
Custom CORS Configuration
For more control, implement custom CORS handling in your API routes:
// src/app/api/custom.ts
import { Hono } from 'hono'
const app = new Hono()
// Custom CORS middleware
app.use('*', async (c, next) => {
const origin = c.req.header('Origin')
const allowedOrigins = ['https://myapp.com', 'https://staging.myapp.com']
if (origin && allowedOrigins.includes(origin)) {
c.header('Access-Control-Allow-Origin', origin)
c.header('Access-Control-Allow-Credentials', 'true')
}
// Handle preflight requests
if (c.req.method === 'OPTIONS') {
c.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
c.header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
c.header('Access-Control-Max-Age', '86400')
return c.text('', 204)
}
await next()
})
app.get('/custom/data', (c) => c.json({ data: 'Custom CORS' }))
export default appProduction Deployment
The same CORS configuration applies in production. Platform notes:
- bini-server (Node.js): Uses the CORS config from your
vite.config - Netlify Edge Functions: Uses the CORS headers set in your Hono app
- Vercel Edge: Uses the CORS headers set in your Hono app
- Cloudflare Workers: Uses the CORS headers set in your Hono app
*. Never combine wildcard CORS with credentials.