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.
Directory structure
Section titled “Directory structure”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
HTTP method exports
Section titled “HTTP method exports”Export named functions matching uppercase HTTP method names. Each export handles that method for the file’s URL.
// routes/users/index.ts → /usersimport 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.
Dynamic segments
Section titled “Dynamic segments”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/:idimport type { Request, Response } from 'lacis'
export async function GET(req: Request, res: Response) { const { id } = req.params res.json({ id })}Nested routes
Section titled “Nested routes”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/postsimport type { Request, Response } from 'lacis'
export async function GET(req: Request, res: Response) { const { id } = req.params res.json({ userId: id, posts: [] })}Wildcard routes
Section titled “Wildcard routes”Use a [...] directory name to match any remaining path segments.
Directoryroutes/
Directoryfiles/
Directory[…path]/
- index.ts → /files/*
// routes/files/[...path]/index.tsimport type { Request, Response } from 'lacis'
export async function GET(req: Request, res: Response) { const filePath = req.params.path res.json({ path: filePath })}Method Not Allowed
Section titled “Method Not Allowed”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.
Complete example
Section titled “Complete example”// 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 → /usersimport 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/:idimport 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('')}