CRUD Routes Builder
Create the four base routes under apps/api/src/interfaces/http/routes/{entities}/ following the collections example already in the repo. Domain + repository must already exist; leave imports unresolved if missing. No update route.
Inputs to confirm
- •Singular + plural names (kebab case), especially irregular plurals.
- •Whether this is a sub-entity and its parent (parent folder +
parentIdparam name). - •Tag/summary/description strings (PascalCase plural tags; e.g.,
["Collections"],["Collection Members"]).
Naming & placement
- •Folder: lower kebab plural (e.g.,
collections). - •Files:
create-{entity},list-{entities},get-{entity},delete-{entity}. - •Parent index registers routes; sub-entity routes live in
{parent-entities}/{sub-entities}/and are registered from the parent withprefix: "/:parentId/sub-entities".
Route registration (index.ts)
ts
import type { FastifyInstance } from "fastify";
import { createEntityRoute } from "./create-entity";
import { listEntitiesRoute } from "./list-entities";
import { getEntityRoute } from "./get-entity";
import { deleteEntityRoute } from "./delete-entity";
// import { subRoutes } from "./sub-entities";
export async function entitiesRoutes(app: FastifyInstance) {
await app.register(listEntitiesRoute);
await app.register(getEntityRoute);
await app.register(createEntityRoute);
await app.register(deleteEntityRoute);
// await app.register(subRoutes, { prefix: "/:parentId/sub-entities" });
}
Create route (create-{entity}/)
- •
index.ts:route().post().auth().meta({ tags, summary, description }).schemas(schemas).handle(createEntityHandler). - •
schema.ts: body from domain fields ({ ...entityFields.foo }),response{ 201: entitySnapshotSchema }, exportschemas. - •
handler.ts:RouteHandler<typeof schemas>, grabctx.user/ctx.body, runcreateEntityErrorsif needed, callcreateEntityfromdb-access, return{ status: HttpStatus.Created, data: entity.toSnapshot() }. - •
db-access.ts: importgetPrisma,handleError,{ Entity }from@cookmate/domain/{entity}; build entity (set timestamps/owner fields), persist with prisma (map domain names to Prisma columns like ownerId -> userId), wrap withhandleError.
List route (list-{entities}/)
- •
select.ts: define Prismaselect,responseSchema(usuallyz.array(...)), andtransform; exportselectConfig(useSelectConfigtype from@/shared/types/select-config). - •
where.ts: exportlistEntitiesWhereConfigsviadefineWhereConfigs. - •
order-by.ts: exportlistEntitiesSortConfigviadefineSortConfig. - •
schema.ts:queryusesdefineListQuerySchema({ where, sort });responseusesselectConfig.schema; exportschemas. For sub-entities includeparamswith parentId. - •
handler.ts: parse query withparsePagination,parseWhereParams,parseSortParams, buildwherewithcombineWhere, then calllistEntitiesSelectandcountEntities. Return{ status: HttpStatus.OK, data: selectConfig.transform(...), metadata: { pagination } }.
Get route (get-{entity}/)
- •
select.ts: same pattern, butresponseSchemais a single object (not array).transformmay accept extra options (e.g.,isOwner). - •
schema.ts:paramsz.object({ {entityId}: z.uuid() }),response{ 200: selectConfig.schema }. - •
handler.ts: fetch via repositorygetEntitySelectwithselectConfig.select; rungetEntityErrorsif needed; return{ status: HttpStatus.OK, data: selectConfig.transform(result, options) }.
Delete route (delete-{entity}/)
- •
index.ts:route().delete("/:{entityId}"). - •
schema.ts:paramsUUID,response{ 204: z.null() }. - •
handler.ts: rundeleteEntityErrors, call localdeleteEntity(db-access), return{ status: HttpStatus.NoContent }. - •
db-access.ts:deleteEntity = handleError(async (id) => { await getPrisma().entity.delete({ where: { id } }); }).
Sub-entities
- •Folder:
{parent-entities}/{sub-entities}/, with its ownindex.tsregistering subroutes. - •Parent
index.tsregisters subroutes withprefix: "/:parentId/{sub-entities}"and parent params include{ parentId: z.uuid() }. - •Tags: PascalCase plural with parent context (e.g.,
["Collection Members"]).
References
- •apps/api/src/interfaces/http/routes/collections/**
- •apps/api/src/interfaces/http/routes/collections/members/**