Skip to content

Routes

Routes define your HTTP endpoints. Files go in config/routes/.

Example

json
{
  "routes": [
    {
      "method": "GET",
      "path": "/api/users",
      "response": "User[]"
    },
    {
      "method": "POST",
      "path": "/api/users",
      "body": {
        "email": "string",
        "name": "string"
      },
      "response": "User"
    },
    {
      "method": "GET",
      "path": "/api/users/:id",
      "params": { "id": "uuid" },
      "response": "User"
    },
    {
      "method": "DELETE",
      "path": "/api/users/:id",
      "params": { "id": "uuid" }
    }
  ]
}

Route fields

FieldTypeDescription
methodGET | POST | PUT | PATCH | DELETEHTTP method
pathstringURL path (use :param for dynamic segments)
paramsobjectURL path parameters
bodyobjectRequest body (POST/PUT/PATCH)
responsestringExpected response model name
redirectstringRedirect to another path
functionstringFunction to call
authbooleanRequire authentication
voterstringVoter to check authorization

Body validation

json
{
  "method": "POST",
  "path": "/api/users",
  "body": {
    "email": {
      "type": "string",
      "required": true,
      "validation": ["notEmpty", "isEmail"]
    }
  }
}

Authentication

Add "auth": true to require authentication. The generated handler calls getAuthUser and returns 401 if unauthenticated:

json
{ "method": "GET", "path": "/api/users/:id", "auth": true, "response": "User" }

Authorization (voters)

Add "voter": "<VoterName>" to apply an authorization voter. See the Voters page.

Function routing

Add "function": "<FunctionName>" to delegate the handler to a generated function instead of a raw ORM query. See the Functions page.

Event wiring

When a route mutates a model (POST/PUT/PATCH/DELETE), any matching event is automatically invoked after the database operation. See the Events page.

Generated output

Each unique path segment generates a file:

  • /api/userssrc/app/api/users/route.ts
  • /api/users/:idsrc/app/api/users/[id]/route.ts

Example generated route

For GET /api/users/:id with "auth": true and "response": "User":

Drizzle

ts
import { NextRequest, NextResponse } from 'next/server';
import { db } from '../../lib/db';
import { users } from '../../drizzle/schema';
import { eq } from 'drizzle-orm';
import { getAuthUser } from '../../lib/auth';

export async function GET(
  _req: NextRequest,
  { params }: { params: Promise<{ id: string }> },
) {
  const { id } = await params;
  const userId = getAuthUser(_req);
  if (!userId)
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  const data = await db
    .select()
    .from(users)
    .where(eq(users.id, id))
    .then((r) => r[0] ?? null);
  if (!data) return NextResponse.json({ error: 'Not found' }, { status: 404 });
  return NextResponse.json(data);
}

Prisma

ts
import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '../../lib/prisma';
import { getAuthUser } from '../../lib/auth';

export async function GET(
  _req: NextRequest,
  { params }: { params: Promise<{ id: string }> },
) {
  const { id } = await params;
  const userId = getAuthUser(_req);
  if (!userId)
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  const data = await prisma.user.findUnique({ where: { id } });
  if (!data) return NextResponse.json({ error: 'Not found' }, { status: 404 });
  return NextResponse.json(data);
}

Released under the Elastic License 2.0.