Skip to content

Routing

Lacis generates routes automatically from your routes/ directory. Every index.ts file inside that directory becomes a URL endpoint — no manual route registration needed.

Place route files under routes/. The directory hierarchy maps directly to URL paths.

  • Directoryroutes/
    • index.ts → GET /
    • Directoryusers/
      • index.ts → /users
      • Directory[id]/
        • index.ts → /users/:id
    • Directoryposts/
      • index.ts → /posts
      • Directory[slug]/
        • index.ts → /posts/:slug

Export named functions matching uppercase HTTP method names. Each export handles that method for the file’s URL.

// routes/users/index.ts → /users
import type { Request, Response } from 'lacis'
export async function GET(req: Request, res: Response) {
res.json({ users: [] })
}
export async function POST(req: Request, res: Response) {
const body = await req.json()
res.status(201).json({ created: body })
}
export async function PUT(req: Request, res: Response) {
const body = await req.json()
res.json({ updated: body })
}
export async function PATCH(req: Request, res: Response) {
const body = await req.json()
res.json({ patched: body })
}
export async function DELETE(req: Request, res: Response) {
res.status(204).send('')
}

Supported method exports: GET, POST, PUT, PATCH, DELETE.

Wrap a directory name in square brackets to make it a URL parameter. The parameter name is the text inside the brackets.

  • Directoryroutes/
    • Directoryusers/
      • Directory[id]/
        • index.ts → /users/:id
    • Directoryorgs/
      • Directory[orgId]/
        • Directoryteams/
          • Directory[teamId]/
            • index.ts → /orgs/:orgId/teams/:teamId

Access parameters via req.params:

// routes/users/[id]/index.ts → /users/:id
import type { Request, Response } from 'lacis'
export async function GET(req: Request, res: Response) {
const { id } = req.params
res.json({ id })
}

Nest directories to create nested URL paths. Each level can have its own index.ts with independent handlers.

  • Directoryroutes/
    • Directoryapi/
      • index.ts → /api
      • Directoryusers/
        • index.ts → /api/users
        • Directory[id]/
          • index.ts → /api/users/:id
          • Directoryposts/
            • index.ts → /api/users/:id/posts
// routes/api/users/[id]/posts/index.ts → /api/users/:id/posts
import type { Request, Response } from 'lacis'
export async function GET(req: Request, res: Response) {
const { id } = req.params
res.json({ userId: id, posts: [] })
}

Use a [...] directory name to match any remaining path segments.

  • Directoryroutes/
    • Directoryfiles/
      • Directory[…path]/
        • index.ts → /files/*
// routes/files/[...path]/index.ts
import type { Request, Response } from 'lacis'
export async function GET(req: Request, res: Response) {
const filePath = req.params.path
res.json({ path: filePath })
}

When a route exists but the request uses an unregistered HTTP method, Lacis returns 405 Method Not Allowed and sets an Allow header listing the supported methods automatically.

// routes/index.ts → GET /
import type { Request, Response } from 'lacis'
export async function GET(req: Request, res: Response) {
res.json({ name: 'My API', version: '1.0.0' })
}
// routes/users/index.ts → /users
import type { Request, Response } from 'lacis'
export async function GET(req: Request, res: Response) {
res.json({ users: [] })
}
export async function POST(req: Request, res: Response) {
const body = await req.json<{ name: string; email: string }>()
res.status(201).json({ id: crypto.randomUUID(), ...body })
}
// routes/users/[id]/index.ts → /users/:id
import type { Request, Response } from 'lacis'
export async function GET(req: Request, res: Response) {
const { id } = req.params
res.json({ id, name: 'Alice' })
}
export async function DELETE(req: Request, res: Response) {
res.status(204).send('')
}