API Routes Overview

Learn how to create backend API endpoints in Bini.js using plain functions or Hono.

Bini.js allows you to create API endpoints directly in your project. Place files in src/app/api/ and they automatically become API routes at /api/*. The filename maps directly to the route — hello.ts becomes /api/hello, users.ts becomes /api/users.

Important: Every API route file must have a default export. Bini.js uses the default export to handle requests. Named exports will not work.

File Structure

The filename (without extension) becomes the last segment of the URL path under /api/:

src/app/
├── api/
│   ├── hello.ts           → /api/hello
│   ├── users.ts           → /api/users
│   ├── posts/
│   │   ├── index.ts       → /api/posts
│   │   └── [id].ts        → /api/posts/:id
│   └── [...catch].ts      → /api/* (catch-all)
├── layout.tsx
└── page.tsx
There is no root /api route. Every API file maps to a named path — use posts/index.ts if you need a route at /api/posts.

Plain Function Handler

The simplest way to create an API route is to default export a handler function that checks the HTTP method:

src/app/api/hello.ts
// src/app/api/hello.ts → /api/hello
export default function handler(request: Request) {
  if (request.method === 'GET') {
    return Response.json({ message: 'Hello World' })
  }
  if (request.method === 'POST') {
    return Response.json({ message: 'Created' }, { status: 201 })
  }
  return Response.json({ error: 'Method not allowed' }, { status: 405 })
}

The handler receives the native Request object. Check request.method to handle different HTTP verbs.

For APIs with multiple endpoints or complex logic, Hono is recommended over plain functions.

Hono Integration

For more complex APIs, use Hono. Create a Hono app and default export it. Write routes without the /api prefix — bini-router strips it before your handler sees the request.

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

const app = new Hono()

app.get('/users', (c) => {
  return c.json({ users: ['alice', 'bob', 'charlie'] })
})

app.post('/users', async (c) => {
  const body = await c.req.json()
  return c.json({ created: body }, { status: 201 })
})

app.get('/users/:id', (c) => {
  const id = c.req.param('id')
  return c.json({ id, name: `User ${id}` })
})

export default app
Hono is the recommended approach for complex APIs. It provides routing, middleware, validation, and better TypeScript support.

Hono Middleware

Hono provides built-in middleware for common tasks:

src/app/api/secure.ts
// src/app/api/secure.ts → /api/secure
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'

const app = new Hono()

app.use('*', cors())
app.use('*', logger())

app.get('/secure', (c) => c.json({ message: 'Public endpoint' }))

export default app
MiddlewarePurpose
corsCross-Origin Resource Sharing
loggerRequest logging
jwtJWT authentication
prettyJSONPretty JSON responses
timeoutRequest timeout

Dynamic API Routes

Use square brackets for dynamic segments, just like page routes:

src/app/api/posts/[id].ts
// src/app/api/posts/[id].ts → /api/posts/:id
import { Hono } from 'hono'

const app = new Hono()

app.get('/posts/:id', (c) => {
  const id = c.req.param('id')
  return c.json({ id, title: `Post ${id}` })
})

export default app

For plain function handlers with dynamic routes, parameters are passed via the x-bini-params header:

src/app/api/posts/[id].ts
// src/app/api/posts/[id].ts → /api/posts/:id
export default function handler(request: Request) {
  const paramsHeader = request.headers.get('x-bini-params')
  const params = paramsHeader ? JSON.parse(paramsHeader) : {}
  const id = params.id
  
  return Response.json({ id, title: `Post ${id}` })
}

Catch-all API Routes

Use [...catch] to handle all unmatched API routes:

src/app/api/[...catch].ts
// src/app/api/[...catch].ts → /api/*
export default function handler(request: Request) {
  const url = new URL(request.url)
  return Response.json({
    error: 'Not Found',
    path: url.pathname,
    method: request.method,
  }, { status: 404 })
}

Environment Variables

API routes use getEnv(c, key) and requireEnv(c, key) from bini-env. Both read directly from the Hono request context — they work across every runtime without code changes.

Always pass c explicitly. Cast it once at the top as const ctx = c as any, then use ctx throughout.
src/app/api/email.ts
// src/app/api/email.ts → /api/email
import { Hono } from 'hono'
import { getEnv, requireEnv } from 'bini-env'

const app = new Hono()

app.post('/email', async (c) => {
  const ctx = c as any
  
  const smtpHost = requireEnv(ctx, 'SMTP_HOST')
  const smtpPass = requireEnv(ctx, 'SMTP_PASS')
  const smtpPort = parseInt(getEnv(ctx, 'SMTP_PORT') ?? '587')
  
  return c.json({ success: true })
})

export default app

The pattern in three steps:

const ctx = c as any              // cast once
requireEnv(ctx, 'KEY')            // throws if missing
getEnv(ctx, 'KEY') ?? 'default'   // optional with default

Request & Response

API routes use standard Web APIs for requests and responses:

export default async function handler(request: Request) {
  const json = await request.json()
  const auth = request.headers.get('Authorization')
  const { searchParams } = new URL(request.url)
  const page = searchParams.get('page')
  
  return Response.json({ data: json, page })
}

CORS

CORS is enabled by default for all API routes:

vite.config.ts
// vite.config.ts
import { defineConfig } from 'vite'
import { biniroute } from 'bini-router'

export default defineConfig({
  plugins: [
    biniroute({
      cors: true,  // Default: true
    }),
  ],
})

With Hono, configure CORS per route:

import { Hono } from 'hono'
import { cors } from 'hono/cors'

const app = new Hono()
app.use('*', cors({ origin: 'https://myapp.com' }))
export default app

Deployment

API routes work across all deployment platforms. To deploy, run:

npm run deploy

This will prompt you to select your hosting platform:

  • Node.js — Runs via bini-server
  • Netlify — Edge Functions (Deno)
  • Vercel — Edge Runtime
  • Cloudflare — Workers
  • Deno — Deno Deploy
Run npm run deploy and select your platform. bini-deploy will generate the appropriate entry files and configuration for your chosen platform.