fastify-router-patterns
Fastify route handler patterns — TypeBox validation, DI access, schema definitions, JWT guards, error handling conventions.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Fastify route handler patterns — TypeBox validation, DI access, schema definitions, JWT guards, error handling conventions.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Awilix dependency injection patterns for this Fastify project — auto-loading conventions, Cradle usage, partial application, type declarations.
CQS (Command Query Separation) patterns — mutations for writes with events, queries for reads without side effects.
Safe Drizzle ORM migration patterns for PostgreSQL. Auto-loads on migration files. Mandatory reading before writing or editing a migration.
Drizzle ORM query patterns for this project — base repository, soft delete, NON_PASSWORD_COLUMNS, query builder conventions.
E2E testing patterns using Node.js native test runner — createTestingApp, createDbHelper, factories, app.inject() for HTTP testing.
| name | fastify-router-patterns |
| description | Fastify route handler patterns — TypeBox validation, DI access, schema definitions, JWT guards, error handling conventions. |
| globs | ["src/modules/**/*.router.v1.ts","src/modules/**/*.schemas.ts","src/modules/**/*.contracts.ts"] |
| allowed-tools | Read, Write, Edit, Grep, Glob, Bash(pnpm:*) |
import type { FastifyPluginAsyncTypebox } from "@fastify/type-provider-typebox";
import usersSchemas from "./users.schemas.ts";
const usersRouterV1: FastifyPluginAsyncTypebox = async (app) => {
const { usersMutations, usersQueries } = app.diContainer.cradle;
app.get("/", {
schema: usersSchemas.getList,
async handler(req) {
const pagination = app.transformers.getPaginationQuery(req);
return usersQueries.findMany(pagination);
},
});
app.post("/", {
schema: usersSchemas.create,
async handler(req, rep) {
const user = await usersMutations.createOne(req.body);
rep.status(201);
return user;
},
});
};
export default usersRouterV1;
Key points:
FastifyPluginAsyncTypeboxapp.diContainer.cradle at the top*.schemas.tsapp.get("/profile", {
preHandler: app.auth([app.verifyJwt]),
schema: usersSchemas.getProfile,
handler: () => usersQueries.getOneProfile(),
});
import { USER_CREATE_INPUT_CONTRACT, USER_OUTPUT_CONTRACT } from "./users.contracts.ts";
import { SWAGGER_TAGS } from "#libs/constants/swagger-tags.constants.ts";
import { defaultHttpErrorCollection } from "#libs/errors/default-http-error-collection.ts";
import { mapHttpErrorsToSchemaErrorCollection } from "#libs/utils/schemas.ts";
const usersSchemas = {
create: {
tags: SWAGGER_TAGS.USERS,
body: USER_CREATE_INPUT_CONTRACT,
description: "Create a new user",
response: {
201: USER_OUTPUT_CONTRACT,
...mapHttpErrorsToSchemaErrorCollection(
pick([BadRequestException.name, ConflictException.name], defaultHttpErrorCollection),
),
},
summary: "Create user",
},
};
import { Type } from "@sinclair/typebox";
import type { Static } from "@sinclair/typebox";
import { createInsertSchema, createSelectSchema } from "drizzle-typebox";
export const USER_ENTITY_CONTRACT = createSelectSchema(users, { id: TypeUuid() });
export const USER_OUTPUT_CONTRACT = Type.Omit(USER_ENTITY_CONTRACT, ["deletedAt", "password"]);
export const USER_CREATE_INPUT_CONTRACT = Type.Omit(USER_INSERT_CONTRACT, ["id", "createdAt", "updatedAt", "deletedAt"]);
export type User = Static<typeof USER_OUTPUT_CONTRACT>;
export type UserCreateInput = Static<typeof USER_CREATE_INPUT_CONTRACT>;
Throw domain errors — they map to HTTP automatically via fastify-error-handler.ts:
import { ConflictException, ResourceNotFoundException } from "#libs/errors/domain.errors.ts";
// In mutations/queries — NOT in router
if (!user) throw new ResourceNotFoundException(`User with id: ${id} not found`);
if (existing) throw new ConflictException(`User with email: ${email} already exists`);
Available domain errors:
BadRequestException → 400UnauthorizedException → 401ForbiddenException → 403ResourceNotFoundException → 404ConflictException → 409UnprocessableEntityException → 422Routes are versioned via filename: *.router.v1.ts. Future versions: *.router.v2.ts.
*.schemas.ts)