# Route Handlers + Drizzle Guide Use this guide when a feature should stop relying on mock data and start using the app's real server boundary. ## Default request flow The preferred flow in this repo is: `component -> service.ts -> /api route handler -> Drizzle -> database` This keeps: - React components UI-focused - route handlers responsible for auth, org resolution, and HTTP behavior - Drizzle access on the server only Avoid these paths as defaults: - `component -> mock-api` - `component -> Drizzle directly` - `client component -> external DB client` ## Build order ### 1. Schema first Define tables and relations before writing React code. Typical pieces: - feature table such as `orders` - optional `organizationId` foreign key - audit columns such as `createdAt` and `updatedAt` If the task includes org-aware access, make tenant ownership explicit in the schema. ### 2. API contract second In `src/features//api/types.ts`, define: - entity shape used by the UI - list filters - list response - detail response - mutation payloads Keep response types stable even if the schema is denormalized underneath. ### 3. Route handlers third Implement: - `src/app/api//route.ts` for `GET` list and `POST` create - `src/app/api//[id]/route.ts` for `GET`, `PATCH` or `PUT`, and `DELETE` Responsibilities: - authenticate the request - resolve active organization - check membership or role - parse request params and body - query or mutate via Drizzle - return consistent JSON ## Example list handler ```tsx import { and, count, desc, eq, ilike } from 'drizzle-orm'; import { NextRequest, NextResponse } from 'next/server'; import { auth } from '@/auth'; import { db } from '@/lib/db'; import { memberships, orders } from '@/db/schema'; export async function GET(request: NextRequest) { const session = await auth(); if (!session?.user?.id) { return NextResponse.json({ message: 'Unauthorized' }, { status: 401 }); } const { searchParams } = request.nextUrl; const page = Number(searchParams.get('page') ?? 1); const limit = Number(searchParams.get('limit') ?? 10); const search = searchParams.get('search') ?? ''; const offset = (page - 1) * limit; const membership = await db.query.memberships.findFirst({ where: eq(memberships.userId, session.user.id) }); if (!membership) { return NextResponse.json({ message: 'Organization membership required' }, { status: 403 }); } const whereClause = and( eq(orders.organizationId, membership.organizationId), search ? ilike(orders.customerName, `%${search}%`) : undefined ); const [items, totalRows] = await Promise.all([ db.select().from(orders).where(whereClause).orderBy(desc(orders.createdAt)).limit(limit).offset(offset), db.select({ value: count() }).from(orders).where(whereClause) ]); return NextResponse.json({ items, totalItems: totalRows[0]?.value ?? 0 }); } ``` ## Service layer pattern `service.ts` should call the local route handlers through `apiClient`. ```tsx import { apiClient } from '@/lib/api-client'; import type { Order, OrderFilters, OrdersResponse, OrderMutationPayload } from './types'; export async function getOrders(filters: OrderFilters): Promise { const searchParams = new URLSearchParams(); if (filters.page) searchParams.set('page', String(filters.page)); if (filters.limit) searchParams.set('limit', String(filters.limit)); if (filters.search) searchParams.set('search', filters.search); if (filters.status) searchParams.set('status', filters.status); if (filters.sort) searchParams.set('sort', filters.sort); return apiClient(`/orders?${searchParams.toString()}`); } export async function createOrder(values: OrderMutationPayload): Promise { return apiClient('/orders', { method: 'POST', body: JSON.stringify(values) }); } ``` ## Migration advice for this repo If the task is migrating existing code: - start with `products` or `users` because both already have route-handler shells - swap `service.ts` away from `fakeProducts` or `fakeUsers` - replace route-handler mock implementations with Drizzle queries - keep `queries.ts` and UI mostly stable where possible That minimizes UI churn and isolates the migration to the intended seam.