autoworkflow 3.1.5 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/.claude/commands/analyze.md +19 -0
  2. package/.claude/commands/audit.md +26 -0
  3. package/.claude/commands/build.md +39 -0
  4. package/.claude/commands/commit.md +25 -0
  5. package/.claude/commands/fix.md +23 -0
  6. package/.claude/commands/plan.md +18 -0
  7. package/.claude/commands/suggest.md +23 -0
  8. package/.claude/commands/verify.md +18 -0
  9. package/.claude/hooks/post-bash-router.sh +20 -0
  10. package/.claude/hooks/post-commit.sh +140 -0
  11. package/.claude/hooks/post-edit.sh +190 -17
  12. package/.claude/hooks/pre-edit.sh +221 -0
  13. package/.claude/hooks/session-check.sh +90 -0
  14. package/.claude/settings.json +56 -6
  15. package/.claude/settings.local.json +5 -1
  16. package/.claude/skills/actix.md +337 -0
  17. package/.claude/skills/alembic.md +504 -0
  18. package/.claude/skills/angular.md +237 -0
  19. package/.claude/skills/api-design.md +187 -0
  20. package/.claude/skills/aspnet-core.md +377 -0
  21. package/.claude/skills/astro.md +245 -0
  22. package/.claude/skills/auth-clerk.md +327 -0
  23. package/.claude/skills/auth-firebase.md +367 -0
  24. package/.claude/skills/auth-nextauth.md +359 -0
  25. package/.claude/skills/auth-supabase.md +368 -0
  26. package/.claude/skills/axum.md +386 -0
  27. package/.claude/skills/blazor.md +456 -0
  28. package/.claude/skills/chi.md +348 -0
  29. package/.claude/skills/code-review.md +133 -0
  30. package/.claude/skills/csharp.md +296 -0
  31. package/.claude/skills/css-modules.md +325 -0
  32. package/.claude/skills/cypress.md +343 -0
  33. package/.claude/skills/debugging.md +133 -0
  34. package/.claude/skills/diesel.md +392 -0
  35. package/.claude/skills/django.md +301 -0
  36. package/.claude/skills/docker.md +319 -0
  37. package/.claude/skills/doctrine.md +473 -0
  38. package/.claude/skills/documentation.md +182 -0
  39. package/.claude/skills/dotnet.md +409 -0
  40. package/.claude/skills/drizzle.md +293 -0
  41. package/.claude/skills/echo.md +321 -0
  42. package/.claude/skills/eloquent.md +256 -0
  43. package/.claude/skills/emotion.md +426 -0
  44. package/.claude/skills/entity-framework.md +370 -0
  45. package/.claude/skills/express.md +316 -0
  46. package/.claude/skills/fastapi.md +329 -0
  47. package/.claude/skills/fastify.md +299 -0
  48. package/.claude/skills/fiber.md +315 -0
  49. package/.claude/skills/flask.md +322 -0
  50. package/.claude/skills/gin.md +342 -0
  51. package/.claude/skills/git.md +116 -0
  52. package/.claude/skills/github-actions.md +353 -0
  53. package/.claude/skills/go.md +377 -0
  54. package/.claude/skills/gorm.md +409 -0
  55. package/.claude/skills/graphql.md +478 -0
  56. package/.claude/skills/hibernate.md +379 -0
  57. package/.claude/skills/hono.md +306 -0
  58. package/.claude/skills/java.md +400 -0
  59. package/.claude/skills/jest.md +313 -0
  60. package/.claude/skills/jpa.md +282 -0
  61. package/.claude/skills/kotlin.md +347 -0
  62. package/.claude/skills/kubernetes.md +363 -0
  63. package/.claude/skills/laravel.md +414 -0
  64. package/.claude/skills/mcp-browser.md +320 -0
  65. package/.claude/skills/mcp-database.md +219 -0
  66. package/.claude/skills/mcp-fetch.md +241 -0
  67. package/.claude/skills/mcp-filesystem.md +204 -0
  68. package/.claude/skills/mcp-github.md +217 -0
  69. package/.claude/skills/mcp-memory.md +240 -0
  70. package/.claude/skills/mcp-search.md +218 -0
  71. package/.claude/skills/mcp-slack.md +262 -0
  72. package/.claude/skills/micronaut.md +388 -0
  73. package/.claude/skills/mongodb.md +319 -0
  74. package/.claude/skills/mongoose.md +355 -0
  75. package/.claude/skills/mysql.md +281 -0
  76. package/.claude/skills/nestjs.md +335 -0
  77. package/.claude/skills/nextjs-app-router.md +260 -0
  78. package/.claude/skills/nextjs-pages.md +172 -0
  79. package/.claude/skills/nuxt.md +202 -0
  80. package/.claude/skills/openapi.md +489 -0
  81. package/.claude/skills/performance.md +199 -0
  82. package/.claude/skills/php.md +398 -0
  83. package/.claude/skills/playwright.md +371 -0
  84. package/.claude/skills/postgresql.md +257 -0
  85. package/.claude/skills/prisma.md +293 -0
  86. package/.claude/skills/pydantic.md +304 -0
  87. package/.claude/skills/pytest.md +313 -0
  88. package/.claude/skills/python.md +272 -0
  89. package/.claude/skills/quarkus.md +377 -0
  90. package/.claude/skills/react.md +230 -0
  91. package/.claude/skills/redis.md +391 -0
  92. package/.claude/skills/refactoring.md +143 -0
  93. package/.claude/skills/remix.md +246 -0
  94. package/.claude/skills/rest-api.md +490 -0
  95. package/.claude/skills/rocket.md +366 -0
  96. package/.claude/skills/rust.md +341 -0
  97. package/.claude/skills/sass.md +380 -0
  98. package/.claude/skills/sea-orm.md +382 -0
  99. package/.claude/skills/security.md +167 -0
  100. package/.claude/skills/sequelize.md +395 -0
  101. package/.claude/skills/spring-boot.md +416 -0
  102. package/.claude/skills/sqlalchemy.md +269 -0
  103. package/.claude/skills/sqlx-rust.md +408 -0
  104. package/.claude/skills/state-jotai.md +346 -0
  105. package/.claude/skills/state-mobx.md +353 -0
  106. package/.claude/skills/state-pinia.md +431 -0
  107. package/.claude/skills/state-redux.md +337 -0
  108. package/.claude/skills/state-tanstack-query.md +434 -0
  109. package/.claude/skills/state-zustand.md +340 -0
  110. package/.claude/skills/styled-components.md +403 -0
  111. package/.claude/skills/svelte.md +238 -0
  112. package/.claude/skills/sveltekit.md +207 -0
  113. package/.claude/skills/symfony.md +437 -0
  114. package/.claude/skills/tailwind.md +279 -0
  115. package/.claude/skills/terraform.md +394 -0
  116. package/.claude/skills/testing-library.md +371 -0
  117. package/.claude/skills/trpc.md +426 -0
  118. package/.claude/skills/typeorm.md +368 -0
  119. package/.claude/skills/vitest.md +330 -0
  120. package/.claude/skills/vue.md +202 -0
  121. package/.claude/skills/warp.md +365 -0
  122. package/README.md +163 -52
  123. package/package.json +1 -1
  124. package/system/triggers.md +256 -17
@@ -0,0 +1,329 @@
1
+ # FastAPI Skill
2
+
3
+ ## Project Structure
4
+ \`\`\`
5
+ app/
6
+ ├── main.py # FastAPI app
7
+ ├── config.py # Settings
8
+ ├── database.py # DB connection
9
+ ├── dependencies.py # Shared dependencies
10
+ ├── models/ # SQLAlchemy models
11
+ │ └── user.py
12
+ ├── schemas/ # Pydantic schemas
13
+ │ └── user.py
14
+ ├── routers/ # API routes
15
+ │ ├── __init__.py
16
+ │ └── users.py
17
+ ├── services/ # Business logic
18
+ │ └── user.py
19
+ └── tests/
20
+ └── test_users.py
21
+ \`\`\`
22
+
23
+ ## App Setup
24
+ \`\`\`python
25
+ # main.py
26
+ from fastapi import FastAPI
27
+ from fastapi.middleware.cors import CORSMiddleware
28
+ from contextlib import asynccontextmanager
29
+
30
+ from app.database import engine, Base
31
+ from app.routers import users, auth
32
+
33
+ @asynccontextmanager
34
+ async def lifespan(app: FastAPI):
35
+ # Startup
36
+ async with engine.begin() as conn:
37
+ await conn.run_sync(Base.metadata.create_all)
38
+ yield
39
+ # Shutdown
40
+ await engine.dispose()
41
+
42
+ app = FastAPI(
43
+ title="My API",
44
+ version="1.0.0",
45
+ lifespan=lifespan,
46
+ )
47
+
48
+ # CORS
49
+ app.add_middleware(
50
+ CORSMiddleware,
51
+ allow_origins=["http://localhost:3000"],
52
+ allow_credentials=True,
53
+ allow_methods=["*"],
54
+ allow_headers=["*"],
55
+ )
56
+
57
+ # Routes
58
+ app.include_router(auth.router, prefix="/auth", tags=["auth"])
59
+ app.include_router(users.router, prefix="/users", tags=["users"])
60
+
61
+ @app.get("/health")
62
+ async def health():
63
+ return {"status": "ok"}
64
+ \`\`\`
65
+
66
+ ## Schemas (Pydantic)
67
+ \`\`\`python
68
+ # schemas/user.py
69
+ from pydantic import BaseModel, EmailStr, Field
70
+ from datetime import datetime
71
+
72
+ class UserBase(BaseModel):
73
+ email: EmailStr
74
+ name: str = Field(min_length=1, max_length=100)
75
+
76
+ class UserCreate(UserBase):
77
+ password: str = Field(min_length=8)
78
+
79
+ class UserUpdate(BaseModel):
80
+ name: str | None = None
81
+ bio: str | None = None
82
+
83
+ class UserResponse(UserBase):
84
+ id: int
85
+ created_at: datetime
86
+
87
+ model_config = {"from_attributes": True}
88
+
89
+ class UserWithPosts(UserResponse):
90
+ posts: list["PostResponse"] = []
91
+ \`\`\`
92
+
93
+ ## Routes
94
+ \`\`\`python
95
+ # routers/users.py
96
+ from fastapi import APIRouter, Depends, HTTPException, status, Query
97
+ from sqlalchemy.ext.asyncio import AsyncSession
98
+
99
+ from app.database import get_db
100
+ from app.schemas.user import UserCreate, UserResponse, UserUpdate
101
+ from app.services.user import UserService
102
+ from app.dependencies import get_current_user
103
+
104
+ router = APIRouter()
105
+
106
+ @router.get("/", response_model=list[UserResponse])
107
+ async def list_users(
108
+ skip: int = Query(0, ge=0),
109
+ limit: int = Query(10, ge=1, le=100),
110
+ db: AsyncSession = Depends(get_db),
111
+ ):
112
+ service = UserService(db)
113
+ return await service.get_users(skip=skip, limit=limit)
114
+
115
+ @router.get("/me", response_model=UserResponse)
116
+ async def get_me(current_user: User = Depends(get_current_user)):
117
+ return current_user
118
+
119
+ @router.get("/{user_id}", response_model=UserResponse)
120
+ async def get_user(
121
+ user_id: int,
122
+ db: AsyncSession = Depends(get_db),
123
+ ):
124
+ service = UserService(db)
125
+ user = await service.get_user(user_id)
126
+ if not user:
127
+ raise HTTPException(status_code=404, detail="User not found")
128
+ return user
129
+
130
+ @router.post("/", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
131
+ async def create_user(
132
+ user_data: UserCreate,
133
+ db: AsyncSession = Depends(get_db),
134
+ ):
135
+ service = UserService(db)
136
+ return await service.create_user(user_data)
137
+
138
+ @router.patch("/{user_id}", response_model=UserResponse)
139
+ async def update_user(
140
+ user_id: int,
141
+ user_data: UserUpdate,
142
+ db: AsyncSession = Depends(get_db),
143
+ current_user: User = Depends(get_current_user),
144
+ ):
145
+ if current_user.id != user_id:
146
+ raise HTTPException(status_code=403, detail="Not authorized")
147
+ service = UserService(db)
148
+ return await service.update_user(user_id, user_data)
149
+ \`\`\`
150
+
151
+ ## Dependency Injection
152
+ \`\`\`python
153
+ # dependencies.py
154
+ from fastapi import Depends, HTTPException, status
155
+ from fastapi.security import OAuth2PasswordBearer
156
+ from jose import JWTError, jwt
157
+ from sqlalchemy.ext.asyncio import AsyncSession
158
+
159
+ from app.database import get_db
160
+ from app.config import settings
161
+
162
+ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")
163
+
164
+ async def get_current_user(
165
+ token: str = Depends(oauth2_scheme),
166
+ db: AsyncSession = Depends(get_db),
167
+ ) -> User:
168
+ credentials_exception = HTTPException(
169
+ status_code=status.HTTP_401_UNAUTHORIZED,
170
+ detail="Could not validate credentials",
171
+ headers={"WWW-Authenticate": "Bearer"},
172
+ )
173
+
174
+ try:
175
+ payload = jwt.decode(token, settings.jwt_secret, algorithms=["HS256"])
176
+ user_id: str = payload.get("sub")
177
+ if user_id is None:
178
+ raise credentials_exception
179
+ except JWTError:
180
+ raise credentials_exception
181
+
182
+ user = await db.get(User, int(user_id))
183
+ if user is None:
184
+ raise credentials_exception
185
+ return user
186
+
187
+ # Optional auth
188
+ async def get_current_user_optional(
189
+ token: str | None = Depends(oauth2_scheme),
190
+ db: AsyncSession = Depends(get_db),
191
+ ) -> User | None:
192
+ if not token:
193
+ return None
194
+ try:
195
+ return await get_current_user(token, db)
196
+ except HTTPException:
197
+ return None
198
+
199
+ # Role-based access
200
+ def require_role(role: str):
201
+ async def role_checker(user: User = Depends(get_current_user)):
202
+ if user.role != role:
203
+ raise HTTPException(status_code=403, detail="Insufficient permissions")
204
+ return user
205
+ return role_checker
206
+
207
+ # Usage
208
+ @router.delete("/{user_id}")
209
+ async def delete_user(
210
+ user_id: int,
211
+ admin: User = Depends(require_role("admin")),
212
+ ):
213
+ ...
214
+ \`\`\`
215
+
216
+ ## Authentication (OAuth2 + JWT)
217
+ \`\`\`python
218
+ # routers/auth.py
219
+ from fastapi import APIRouter, Depends, HTTPException
220
+ from fastapi.security import OAuth2PasswordRequestForm
221
+ from datetime import datetime, timedelta
222
+
223
+ router = APIRouter()
224
+
225
+ @router.post("/token")
226
+ async def login(
227
+ form_data: OAuth2PasswordRequestForm = Depends(),
228
+ db: AsyncSession = Depends(get_db),
229
+ ):
230
+ user = await authenticate_user(db, form_data.username, form_data.password)
231
+ if not user:
232
+ raise HTTPException(
233
+ status_code=401,
234
+ detail="Incorrect email or password",
235
+ )
236
+
237
+ access_token = create_access_token(
238
+ data={"sub": str(user.id)},
239
+ expires_delta=timedelta(hours=1),
240
+ )
241
+
242
+ return {
243
+ "access_token": access_token,
244
+ "token_type": "bearer",
245
+ }
246
+
247
+ def create_access_token(data: dict, expires_delta: timedelta) -> str:
248
+ expire = datetime.utcnow() + expires_delta
249
+ to_encode = data.copy()
250
+ to_encode.update({"exp": expire})
251
+ return jwt.encode(to_encode, settings.jwt_secret, algorithm="HS256")
252
+ \`\`\`
253
+
254
+ ## Error Handling
255
+ \`\`\`python
256
+ from fastapi import FastAPI, Request
257
+ from fastapi.responses import JSONResponse
258
+
259
+ class AppException(Exception):
260
+ def __init__(self, status_code: int, detail: str):
261
+ self.status_code = status_code
262
+ self.detail = detail
263
+
264
+ @app.exception_handler(AppException)
265
+ async def app_exception_handler(request: Request, exc: AppException):
266
+ return JSONResponse(
267
+ status_code=exc.status_code,
268
+ content={"error": exc.detail},
269
+ )
270
+
271
+ @app.exception_handler(Exception)
272
+ async def global_exception_handler(request: Request, exc: Exception):
273
+ return JSONResponse(
274
+ status_code=500,
275
+ content={"error": "Internal server error"},
276
+ )
277
+
278
+ # Usage
279
+ raise AppException(status_code=400, detail="Invalid input")
280
+ \`\`\`
281
+
282
+ ## Background Tasks
283
+ \`\`\`python
284
+ from fastapi import BackgroundTasks
285
+
286
+ async def send_email(email: str, message: str):
287
+ # Simulated email sending
288
+ await asyncio.sleep(5)
289
+ print(f"Email sent to {email}")
290
+
291
+ @router.post("/users/")
292
+ async def create_user(
293
+ user: UserCreate,
294
+ background_tasks: BackgroundTasks,
295
+ db: AsyncSession = Depends(get_db),
296
+ ):
297
+ new_user = await service.create_user(user)
298
+ background_tasks.add_task(send_email, new_user.email, "Welcome!")
299
+ return new_user
300
+ \`\`\`
301
+
302
+ ## Middleware
303
+ \`\`\`python
304
+ from fastapi import Request
305
+ import time
306
+
307
+ @app.middleware("http")
308
+ async def add_timing_header(request: Request, call_next):
309
+ start_time = time.time()
310
+ response = await call_next(request)
311
+ duration = time.time() - start_time
312
+ response.headers["X-Response-Time"] = f"{duration:.3f}s"
313
+ return response
314
+ \`\`\`
315
+
316
+ ## ❌ DON'T
317
+ - Use sync operations in async routes
318
+ - Skip response_model (loses validation)
319
+ - Put DB logic in route handlers
320
+ - Forget to handle exceptions
321
+
322
+ ## ✅ DO
323
+ - Always define response_model
324
+ - Use Depends() for shared logic
325
+ - Use async for I/O operations
326
+ - Use services for business logic
327
+ - Use OAuth2 + JWT for auth
328
+ - Use background tasks for slow operations
329
+ - Handle errors with exception handlers
@@ -0,0 +1,299 @@
1
+ # Fastify Skill
2
+
3
+ ## Project Structure
4
+ \`\`\`
5
+ src/
6
+ ├── index.ts # Entry point
7
+ ├── app.ts # Fastify app factory
8
+ ├── plugins/
9
+ │ ├── auth.ts # Auth plugin
10
+ │ └── database.ts # DB connection
11
+ ├── routes/
12
+ │ ├── index.ts # Route registration
13
+ │ └── users/
14
+ │ ├── index.ts # User routes
15
+ │ └── schemas.ts # Route schemas
16
+ └── types/
17
+ └── index.d.ts # Type extensions
18
+ \`\`\`
19
+
20
+ ## App Setup with TypeBox
21
+ \`\`\`typescript
22
+ import Fastify from 'fastify';
23
+ import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox';
24
+ import cors from '@fastify/cors';
25
+ import helmet from '@fastify/helmet';
26
+
27
+ export async function buildApp() {
28
+ const app = Fastify({
29
+ logger: {
30
+ level: process.env.LOG_LEVEL || 'info',
31
+ },
32
+ }).withTypeProvider<TypeBoxTypeProvider>();
33
+
34
+ // Plugins
35
+ await app.register(cors, { origin: true });
36
+ await app.register(helmet);
37
+
38
+ // Custom plugins
39
+ await app.register(import('./plugins/database'));
40
+ await app.register(import('./plugins/auth'));
41
+
42
+ // Routes
43
+ await app.register(import('./routes'), { prefix: '/api' });
44
+
45
+ return app;
46
+ }
47
+ \`\`\`
48
+
49
+ ## Routes with Type Safety
50
+ \`\`\`typescript
51
+ // routes/users/index.ts
52
+ import { FastifyPluginAsync } from 'fastify';
53
+ import { Type, Static } from '@sinclair/typebox';
54
+
55
+ // Define schemas with TypeBox
56
+ const UserSchema = Type.Object({
57
+ id: Type.String(),
58
+ email: Type.String({ format: 'email' }),
59
+ name: Type.String(),
60
+ });
61
+
62
+ const CreateUserSchema = Type.Object({
63
+ email: Type.String({ format: 'email' }),
64
+ name: Type.String({ minLength: 1 }),
65
+ password: Type.String({ minLength: 8 }),
66
+ });
67
+
68
+ const ParamsSchema = Type.Object({
69
+ id: Type.String(),
70
+ });
71
+
72
+ type User = Static<typeof UserSchema>;
73
+ type CreateUser = Static<typeof CreateUserSchema>;
74
+
75
+ const usersRoutes: FastifyPluginAsync = async (app) => {
76
+ // GET /users
77
+ app.get('/', {
78
+ schema: {
79
+ response: {
80
+ 200: Type.Array(UserSchema),
81
+ },
82
+ },
83
+ }, async (request, reply) => {
84
+ const users = await app.db.user.findMany();
85
+ return users;
86
+ });
87
+
88
+ // GET /users/:id
89
+ app.get<{ Params: Static<typeof ParamsSchema> }>('/:id', {
90
+ schema: {
91
+ params: ParamsSchema,
92
+ response: {
93
+ 200: UserSchema,
94
+ 404: Type.Object({ error: Type.String() }),
95
+ },
96
+ },
97
+ }, async (request, reply) => {
98
+ const user = await app.db.user.findUnique({
99
+ where: { id: request.params.id },
100
+ });
101
+
102
+ if (!user) {
103
+ return reply.status(404).send({ error: 'User not found' });
104
+ }
105
+
106
+ return user;
107
+ });
108
+
109
+ // POST /users
110
+ app.post<{ Body: CreateUser }>('/', {
111
+ schema: {
112
+ body: CreateUserSchema,
113
+ response: {
114
+ 201: UserSchema,
115
+ },
116
+ },
117
+ }, async (request, reply) => {
118
+ const user = await app.db.user.create({
119
+ data: request.body,
120
+ });
121
+
122
+ return reply.status(201).send(user);
123
+ });
124
+ };
125
+
126
+ export default usersRoutes;
127
+ \`\`\`
128
+
129
+ ## Plugins
130
+ \`\`\`typescript
131
+ // plugins/database.ts
132
+ import { FastifyPluginAsync } from 'fastify';
133
+ import fp from 'fastify-plugin';
134
+ import { PrismaClient } from '@prisma/client';
135
+
136
+ declare module 'fastify' {
137
+ interface FastifyInstance {
138
+ db: PrismaClient;
139
+ }
140
+ }
141
+
142
+ const databasePlugin: FastifyPluginAsync = async (app) => {
143
+ const prisma = new PrismaClient();
144
+
145
+ await prisma.$connect();
146
+
147
+ app.decorate('db', prisma);
148
+
149
+ app.addHook('onClose', async () => {
150
+ await prisma.$disconnect();
151
+ });
152
+ };
153
+
154
+ // Use fp() to make decorators available to sibling plugins
155
+ export default fp(databasePlugin, { name: 'database' });
156
+ \`\`\`
157
+
158
+ ## Authentication Plugin
159
+ \`\`\`typescript
160
+ // plugins/auth.ts
161
+ import { FastifyPluginAsync, FastifyRequest } from 'fastify';
162
+ import fp from 'fastify-plugin';
163
+ import jwt from '@fastify/jwt';
164
+
165
+ declare module 'fastify' {
166
+ interface FastifyInstance {
167
+ authenticate: (request: FastifyRequest) => Promise<void>;
168
+ }
169
+ }
170
+
171
+ declare module '@fastify/jwt' {
172
+ interface FastifyJWT {
173
+ payload: { id: string; email: string };
174
+ user: { id: string; email: string };
175
+ }
176
+ }
177
+
178
+ const authPlugin: FastifyPluginAsync = async (app) => {
179
+ await app.register(jwt, {
180
+ secret: process.env.JWT_SECRET!,
181
+ });
182
+
183
+ app.decorate('authenticate', async (request: FastifyRequest) => {
184
+ await request.jwtVerify();
185
+ });
186
+ };
187
+
188
+ export default fp(authPlugin, { name: 'auth' });
189
+
190
+ // Usage in routes
191
+ app.get('/me', {
192
+ preHandler: [app.authenticate],
193
+ }, async (request) => {
194
+ return request.user;
195
+ });
196
+ \`\`\`
197
+
198
+ ## Hooks
199
+ \`\`\`typescript
200
+ // Lifecycle hooks
201
+ app.addHook('onRequest', async (request, reply) => {
202
+ request.startTime = Date.now();
203
+ });
204
+
205
+ app.addHook('onResponse', async (request, reply) => {
206
+ const duration = Date.now() - request.startTime;
207
+ request.log.info({ duration }, 'Request completed');
208
+ });
209
+
210
+ // Route-level hooks
211
+ app.get('/protected', {
212
+ preHandler: async (request, reply) => {
213
+ // Auth check before handler
214
+ if (!request.headers.authorization) {
215
+ return reply.status(401).send({ error: 'Unauthorized' });
216
+ }
217
+ },
218
+ }, async (request) => {
219
+ return { data: 'secret' };
220
+ });
221
+
222
+ // Validation hook (runs after schema validation)
223
+ app.addHook('preValidation', async (request) => {
224
+ // Modify request before validation
225
+ });
226
+ \`\`\`
227
+
228
+ ## Error Handling
229
+ \`\`\`typescript
230
+ // Custom error handler
231
+ app.setErrorHandler((error, request, reply) => {
232
+ request.log.error(error);
233
+
234
+ // Validation errors
235
+ if (error.validation) {
236
+ return reply.status(400).send({
237
+ error: 'Validation failed',
238
+ details: error.validation,
239
+ });
240
+ }
241
+
242
+ // Custom errors
243
+ if (error.statusCode) {
244
+ return reply.status(error.statusCode).send({
245
+ error: error.message,
246
+ });
247
+ }
248
+
249
+ // Generic errors
250
+ reply.status(500).send({
251
+ error: 'Internal server error',
252
+ });
253
+ });
254
+
255
+ // 404 handler
256
+ app.setNotFoundHandler((request, reply) => {
257
+ reply.status(404).send({
258
+ error: 'Not found',
259
+ path: request.url,
260
+ });
261
+ });
262
+ \`\`\`
263
+
264
+ ## Decorators
265
+ \`\`\`typescript
266
+ // Add to FastifyInstance
267
+ app.decorate('config', {
268
+ apiVersion: '1.0.0',
269
+ environment: process.env.NODE_ENV,
270
+ });
271
+
272
+ // Add to FastifyRequest
273
+ app.decorateRequest('startTime', 0);
274
+
275
+ // Add to FastifyReply
276
+ app.decorateReply('success', function (data: any) {
277
+ return this.send({ success: true, data });
278
+ });
279
+
280
+ // Usage
281
+ app.get('/health', async (request, reply) => {
282
+ return reply.success({ status: 'ok', version: app.config.apiVersion });
283
+ });
284
+ \`\`\`
285
+
286
+ ## ❌ DON'T
287
+ - Forget to use fp() for plugins with decorators
288
+ - Skip schema validation (it's a key Fastify feature)
289
+ - Block the event loop in handlers
290
+ - Ignore the logger (use request.log)
291
+
292
+ ## ✅ DO
293
+ - Use TypeBox for type-safe schemas
294
+ - Use plugins for modularity
295
+ - Enable logging (built-in pino)
296
+ - Use hooks for cross-cutting concerns
297
+ - Use fp() for plugins that add decorators
298
+ - Handle errors with setErrorHandler
299
+ - Use preHandler for auth/validation