schema0-api-router
Installation
SKILL.md
API Router
STOP before writing any router.
- New routers MUST use
createDb()against a Drizzle table. users.tsandfiles.tsare NOT templates. They call platform endpoints purpose-built for theusersandfilesplatform features — there is no Drizzle table behind them. Your application's entities are application data and go through your own tables; do not copyusers.ts/files.tsfor any new entity.- If your entity has no Drizzle table yet, invoke
schema0-db-schemafirst to define one.
File Location
packages/api/src/
├── index.ts # Base procedures (publicProcedure, protectedProcedure)
├── context.ts # Request context type
└── routers/
├── index.ts # App router (register all routers here)
└── [entity].ts # Entity-specific CRUD routers
Prerequisites
Every router provides 5 bulk CRUD operations: selectAll, selectById, insertMany, updateMany, deleteMany.
Base Procedures
import { ORPCError, os } from "@orpc/server";
import type { Context } from "./context";
export const o = os.$context<Context>();
export const publicProcedure = o;
const requireAuth = o.middleware(async ({ context, next }) => {
if (!context.session?.user) {
throw new ORPCError("UNAUTHORIZED");
}
return next({ context: { session: context.session } });
});
export const protectedProcedure = publicProcedure.use(requireAuth);
Error Handling (ORPCError)
import { ORPCError } from "@orpc/server";
// CORRECT -- string code + options object
throw new ORPCError("NOT_FOUND", { status: 404, message: "Resource not found" });
throw new ORPCError("CONFLICT", { status: 409, message: "Already exists" });
// WRONG -- object with code property
throw new ORPCError({ code: "NOT_FOUND", message: "Not found" });
Nested Routers
// packages/api/src/routers/admin/users.ts
export const adminUsersRouter = { list: protectedProcedure.handler(async () => { ... }) };
// packages/api/src/routers/admin/index.ts
export const adminRouter = { users: adminUsersRouter };
// packages/api/src/routers/index.ts
export const appRouter = { admin: adminRouter };
// Client: orpc.admin.users.list.queryOptions()
Key Rules
- Use
.handler()for ALL procedures -- NEVER.query()or.mutation()(tRPC patterns). - Use
createDb()inside each handler -- never module-level singleton. - NEVER use
fetchCustomResourcesfor new routers -- only for built-infiles.tsandusers.ts. - Use
import { z } from "zod/v4"-- NEVERimport z from "zod". - Import schemas from
@template/db/schema-- NEVER definez.object()schemas inline. - Use
{entity}RouterOutputSchemafor.output()-- must match DB return types. - Bulk operations only --
insertMany,updateMany,deleteManyfor TanStack DB optimistic updates. - Data MUST be stored in database -- every mutation must hit DB and return persisted result.
- Register in
packages/api/src/routers/index.tsafter creating.
References
references/crud-template.md-- Full CRUD router code template (selectAll, selectById, insertMany, updateMany, deleteMany) + registration example
Related skills
More from schema0/skills
schema0-dev
>-
23schema0-mobile
Mobile platform patterns — React Native / Expo, worker architecture, ORPC client, and navigation
17schema0-rls
Row-level security setup — RLS policies, authenticated database connections, and user-scoped data access
17schema0-ai
AI SDK integration with ORPC — chat streaming, prompt-response, tool calling, and provider configuration
17schema0-testing
Testing guide for web and mobile platforms — bun:test, Jest, PGlite, 3-layer validation, and test templates
17schema0-web-crud
Web frontend CRUD features — query collections, table columns, dialogs, forms, views, sidebar, and orchestration
17