CORS

Learn how to configure Cross-Origin Resource Sharing (CORS) for your API routes.

What is CORS?

Cross-Origin Resource Sharing (CORS) is a security feature implemented by browsers that restricts web pages from making requests to a different domain than the one that served the web page. CORS headers allow servers to specify which origins are permitted to access their resources.

Bini.js includes built-in CORS support for API routes, making it easy to build APIs that can be accessed from different origins.

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)
This default configuration works for most development and production scenarios. You can customize it to restrict origins or configure specific headers.

Disabling CORS

Disable CORS by setting cors: false in your biniroute() configuration:

vite.config.ts
// 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
    }),
  ],
})
Disabling CORS is useful for internal APIs or when you're handling CORS at the infrastructure level (e.g., via a reverse proxy or CDN).

CORS with Hono

When using Hono for your API routes, you can configure CORS per route or globally using Hono's cors middleware:

src/app/api/users.ts
// 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
// 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
OptionTypeDescription
originstring | string[] | "*"Allowed origins (default: "*")
allowMethodsstring[]Allowed HTTP methods
allowHeadersstring[]Allowed request headers
maxAgenumberPreflight cache duration in seconds
credentialsbooleanAllow credentials (cookies, auth)
exposeHeadersstring[]Headers exposed to the browser

Custom CORS Configuration

For more granular control, you can implement custom CORS handling in your API routes:

src/app/api/custom.ts
// src/app/api/custom.ts
import { Hono } from 'hono'

const app = new Hono()

// Custom CORS middleware
app.use('*', async (c, next) => {
  // Check if the request is from a known origin
  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 app
Custom CORS handling gives you full control over CORS headers and allows you to implement advanced scenarios like dynamic origin validation.

Production Deployment

When deploying to production, the same CORS configuration applies. For platform-specific configuration:

  • bini-server (Node.js): Uses the same CORS configuration from your vite.config.ts
  • 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
For production, consider restricting CORS to specific origins rather than using * to improve security.