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)
This default works for most development and production scenarios. Customize it to restrict origins or allow specific headers when needed.

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 CORS is handled at the infrastructure level (reverse proxy or CDN).

CORS with Hono

With Hono 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 control, 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) => {
  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 full control over headers and supports advanced cases like dynamic origin validation.

Production 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
In production, prefer specific origins over *. Never combine wildcard CORS with credentials.
Was this helpful?