API Routes

Export named HTTP method handlers from files in server/ and they become fully typed API endpoints.

Creating a route

Create a .ts file in server/ and export named functions matching HTTP methods. The file path maps to the URL the same way pages do:

server/users/index.ts → GET /users, POST /userstypescript
import type { ApiRequest, ApiResponse } from 'nukejs'

export async function GET(req: ApiRequest, res: ApiResponse) {
    const users = await db.getUsers()
    res.json(users)
}

export async function POST(req: ApiRequest, res: ApiResponse) {
    const user = await db.createUser(req.body)
    res.json(user, 201)
}

Dynamic API routes

Use [param] in the filename. Route params land in req.params:

server/users/[id].ts → /users/:idtypescript
import type { ApiRequest, ApiResponse } from 'nukejs'

export async function GET(req: ApiRequest, res: ApiResponse) {
    const { id } = req.params as { id: string }
    const user = await db.getUser(id)

    if (!user) {
        res.json({ error: 'Not found' }, 404)
        return
    }

    res.json(user)
}

export async function PUT(req: ApiRequest, res: ApiResponse) {
    const { id } = req.params as { id: string }
    const updated = await db.updateUser(id, req.body)
    res.json(updated)
}

export async function DELETE(req: ApiRequest, res: ApiResponse) {
    await db.deleteUser(req.params.id as string)
    res.status(204).end()
}

Request object

PropertyTypeDescription
req.bodyanyParsed JSON body (or raw string). Up to 10 MB.
req.paramsRecord<string, string | string[]>Dynamic path segments
req.queryRecord<string, string>URL search params
req.methodstringHTTP method (GET, POST, …)
req.headersIncomingHttpHeadersRaw request headers

Response object

MethodDescription
res.json(data, status?)Send JSON. Default status 200.
res.status(code)Set status code, returns res for chaining.
res.setHeader(name, value)Set a response header.
res.end(body?)Send a raw response and close.

Query string parameters

server/search.ts → GET /search?q=nuke&page=2typescript
export async function GET(req: ApiRequest, res: ApiResponse) {
    const { q = '', page = '1' } = req.query
    const results = await search(q, parseInt(page))
    res.json({ results, page: parseInt(page) })
}

Calling API logic from pages

Because pages are server components, you can import and call your database layer directly — skipping the HTTP round-trip entirely:

app/pages/users.tsxtypescript
import { getUsers } from '../../lib/db' // import DB directly

export default async function UsersPage() {
    const users = await getUsers() // no fetch() needed

    return (
        <ul>
            {users.map(u => <li key={u.id}>{u.name}</li>)}
        </ul>
    )
}
ℹ️
API routes are for your frontend clientsServer-rendered pages can call database code directly. API routes are most useful for client components making fetch() calls, or for third-party integrations consuming your endpoints.