@jgamaraalv/ts-dev-kit 1.0.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.
- package/.claude-plugin/marketplace.json +24 -0
- package/.claude-plugin/plugin.json +24 -0
- package/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +128 -0
- package/agents/accessibility-pro.md +139 -0
- package/agents/api-builder.md +110 -0
- package/agents/code-reviewer.md +190 -0
- package/agents/database-expert.md +138 -0
- package/agents/debugger.md +241 -0
- package/agents/docker-expert.md +51 -0
- package/agents/multi-agent-coordinator.md +378 -0
- package/agents/nextjs-expert.md +136 -0
- package/agents/performance-engineer.md +138 -0
- package/agents/playwright-expert.md +126 -0
- package/agents/react-specialist.md +97 -0
- package/agents/security-scanner.md +105 -0
- package/agents/test-generator.md +221 -0
- package/agents/typescript-pro.md +253 -0
- package/agents/ux-optimizer.md +93 -0
- package/docs/rules/orchestration.md.template +126 -0
- package/package.json +28 -0
- package/skills/bullmq/SKILL.md +225 -0
- package/skills/bullmq/references/flows-and-schedulers.md +186 -0
- package/skills/bullmq/references/job-types-and-options.md +163 -0
- package/skills/bullmq/references/patterns.md +273 -0
- package/skills/bullmq/references/production.md +308 -0
- package/skills/composition-patterns/SKILL.md +58 -0
- package/skills/composition-patterns/references/architecture-avoid-boolean-props.md +87 -0
- package/skills/composition-patterns/references/architecture-compound-components.md +107 -0
- package/skills/composition-patterns/references/patterns-children-over-render-props.md +77 -0
- package/skills/composition-patterns/references/patterns-explicit-variants.md +87 -0
- package/skills/composition-patterns/references/react19-no-forwardref.md +37 -0
- package/skills/composition-patterns/references/state-context-interface.md +194 -0
- package/skills/composition-patterns/references/state-decouple-implementation.md +96 -0
- package/skills/composition-patterns/references/state-lift-state.md +126 -0
- package/skills/conventional-commits/SKILL.md +148 -0
- package/skills/docker/SKILL.md +55 -0
- package/skills/docker/references/compose-configs.md +95 -0
- package/skills/docker/references/monorepo-dockerfile.md +111 -0
- package/skills/drizzle-pg/SKILL.md +202 -0
- package/skills/drizzle-pg/references/advanced.md +299 -0
- package/skills/drizzle-pg/references/migrations.md +214 -0
- package/skills/drizzle-pg/references/queries.md +321 -0
- package/skills/drizzle-pg/references/relations.md +272 -0
- package/skills/drizzle-pg/references/schema-pg.md +256 -0
- package/skills/drizzle-pg/references/sql-operator.md +215 -0
- package/skills/fastify-best-practices/SKILL.md +143 -0
- package/skills/fastify-best-practices/references/hooks-and-lifecycle.md +122 -0
- package/skills/fastify-best-practices/references/plugins-and-encapsulation.md +137 -0
- package/skills/fastify-best-practices/references/request-reply-errors.md +189 -0
- package/skills/fastify-best-practices/references/routes-and-handlers.md +134 -0
- package/skills/fastify-best-practices/references/server-and-options.md +127 -0
- package/skills/fastify-best-practices/references/typescript-and-logging.md +223 -0
- package/skills/fastify-best-practices/references/validation-and-serialization.md +190 -0
- package/skills/ioredis/SKILL.md +51 -0
- package/skills/ioredis/references/advanced-patterns.md +312 -0
- package/skills/ioredis/references/cluster-sentinel.md +280 -0
- package/skills/ioredis/references/connection-options.md +187 -0
- package/skills/ioredis/references/core-api.md +179 -0
- package/skills/nextjs-best-practices/SKILL.md +194 -0
- package/skills/nextjs-best-practices/references/async-patterns.md +84 -0
- package/skills/nextjs-best-practices/references/bundling.md +192 -0
- package/skills/nextjs-best-practices/references/data-patterns.md +310 -0
- package/skills/nextjs-best-practices/references/debug-tricks.md +127 -0
- package/skills/nextjs-best-practices/references/directives.md +74 -0
- package/skills/nextjs-best-practices/references/error-handling.md +237 -0
- package/skills/nextjs-best-practices/references/file-conventions.md +152 -0
- package/skills/nextjs-best-practices/references/font.md +175 -0
- package/skills/nextjs-best-practices/references/functions.md +116 -0
- package/skills/nextjs-best-practices/references/hydration-error.md +86 -0
- package/skills/nextjs-best-practices/references/image.md +184 -0
- package/skills/nextjs-best-practices/references/metadata.md +305 -0
- package/skills/nextjs-best-practices/references/parallel-routes.md +299 -0
- package/skills/nextjs-best-practices/references/route-handlers.md +154 -0
- package/skills/nextjs-best-practices/references/rsc-boundaries.md +168 -0
- package/skills/nextjs-best-practices/references/runtime-selection.md +40 -0
- package/skills/nextjs-best-practices/references/scripts.md +148 -0
- package/skills/nextjs-best-practices/references/self-hosting.md +210 -0
- package/skills/nextjs-best-practices/references/suspense-boundaries.md +67 -0
- package/skills/owasp-security-review/SKILL.md +98 -0
- package/skills/owasp-security-review/references/a01-broken-access-control.md +78 -0
- package/skills/owasp-security-review/references/a02-security-misconfiguration.md +81 -0
- package/skills/owasp-security-review/references/a03-supply-chain-failures.md +65 -0
- package/skills/owasp-security-review/references/a04-cryptographic-failures.md +82 -0
- package/skills/owasp-security-review/references/a05-injection.md +106 -0
- package/skills/owasp-security-review/references/a06-insecure-design.md +76 -0
- package/skills/owasp-security-review/references/a07-authentication-failures.md +83 -0
- package/skills/owasp-security-review/references/a08-integrity-failures.md +72 -0
- package/skills/owasp-security-review/references/a09-logging-alerting-failures.md +76 -0
- package/skills/owasp-security-review/references/a10-exceptional-conditions.md +131 -0
- package/skills/postgresql/SKILL.md +50 -0
- package/skills/postgresql/references/ddl-schema.md +300 -0
- package/skills/postgresql/references/indexes.md +257 -0
- package/skills/postgresql/references/jsonb.md +261 -0
- package/skills/postgresql/references/performance.md +291 -0
- package/skills/postgresql/references/psql-cli.md +153 -0
- package/skills/postgresql/references/queries.md +287 -0
- package/skills/postgresql/references/transactions.md +280 -0
- package/skills/react-best-practices/SKILL.md +110 -0
- package/skills/react-best-practices/references/advanced-patterns.md +91 -0
- package/skills/react-best-practices/references/async-patterns.md +233 -0
- package/skills/react-best-practices/references/bundle-optimization.md +201 -0
- package/skills/react-best-practices/references/client-patterns.md +178 -0
- package/skills/react-best-practices/references/js-performance.md +210 -0
- package/skills/react-best-practices/references/rendering-performance.md +209 -0
- package/skills/react-best-practices/references/rerender-optimization.md +316 -0
- package/skills/react-best-practices/references/server-performance.md +274 -0
- package/skills/service-worker/SKILL.md +195 -0
- package/skills/service-worker/references/api-reference.md +114 -0
- package/skills/service-worker/references/caching-strategies.md +202 -0
- package/skills/service-worker/references/push-and-sync.md +261 -0
- package/skills/typescript-conventions/SKILL.md +51 -0
- package/skills/ui-ux-guidelines/SKILL.md +105 -0
- package/skills/ui-ux-guidelines/references/accessibility-and-interaction.md +74 -0
- package/skills/ui-ux-guidelines/references/forms-content-checklist.md +126 -0
- package/skills/ui-ux-guidelines/references/layout-typography-animation.md +95 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Hooks & Lifecycle
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Request/Reply hooks (in execution order)](#requestreply-hooks-in-execution-order)
|
|
6
|
+
- [Application hooks](#application-hooks)
|
|
7
|
+
- [Hook signatures](#hook-signatures)
|
|
8
|
+
- [Early response pattern](#early-response-pattern)
|
|
9
|
+
- [Scope rules](#scope-rules)
|
|
10
|
+
- [Common patterns](#common-patterns)
|
|
11
|
+
- [Gotchas](#gotchas)
|
|
12
|
+
|
|
13
|
+
## Request/Reply hooks (in execution order)
|
|
14
|
+
|
|
15
|
+
| Hook | Signature | When | Can modify response? |
|
|
16
|
+
| ------------------ | --------------------------- | --------------------------------------------------------------------- | --------------------------- |
|
|
17
|
+
| `onRequest` | `(request, reply)` | First hook; body is `undefined` | Yes |
|
|
18
|
+
| `preParsing` | `(request, reply, payload)` | Before body parsing; receives raw stream | Yes (return new stream) |
|
|
19
|
+
| `preValidation` | `(request, reply)` | After parsing, before schema validation | Yes |
|
|
20
|
+
| `preHandler` | `(request, reply)` | After validation, before handler | Yes |
|
|
21
|
+
| _handler_ | `(request, reply)` | Route handler | Yes |
|
|
22
|
+
| `preSerialization` | `(request, reply, payload)` | Before response serialization (skipped for string/buffer/stream/null) | Yes (return new payload) |
|
|
23
|
+
| `onSend` | `(request, reply, payload)` | Before sending; payload is serialized string/buffer | Yes (return new payload) |
|
|
24
|
+
| `onResponse` | `(request, reply)` | After response sent | No |
|
|
25
|
+
| `onError` | `(request, reply, error)` | When error thrown | No (read-only, for logging) |
|
|
26
|
+
| `onTimeout` | `(request, reply)` | Request timed out | No |
|
|
27
|
+
| `onRequestAbort` | `(request)` | Client closed connection | No |
|
|
28
|
+
|
|
29
|
+
## Application hooks
|
|
30
|
+
|
|
31
|
+
| Hook | Signature | When |
|
|
32
|
+
| ------------ | ------------------ | ----------------------------------- |
|
|
33
|
+
| `onReady` | `()` | Before server starts listening |
|
|
34
|
+
| `onListen` | `()` | After server starts listening |
|
|
35
|
+
| `preClose` | `()` | Before in-flight requests complete |
|
|
36
|
+
| `onClose` | `(instance)` | After in-flight requests complete |
|
|
37
|
+
| `onRoute` | `(routeOptions)` | New route registered (synchronous!) |
|
|
38
|
+
| `onRegister` | `(instance, opts)` | New plugin context created |
|
|
39
|
+
|
|
40
|
+
## Hook signatures
|
|
41
|
+
|
|
42
|
+
**Async (preferred):**
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
fastify.addHook("onRequest", async (request, reply) => {
|
|
46
|
+
// do work; throw to abort
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Callback:**
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
fastify.addHook("onRequest", (request, reply, done) => {
|
|
54
|
+
// do work; call done() to continue, done(err) to abort
|
|
55
|
+
done();
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**With payload (preParsing, preSerialization, onSend):**
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
fastify.addHook("onSend", async (request, reply, payload) => {
|
|
63
|
+
return modifiedPayload; // must return new payload
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Early response pattern
|
|
68
|
+
|
|
69
|
+
Send response from a hook to short-circuit the lifecycle:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
fastify.addHook("onRequest", async (request, reply) => {
|
|
73
|
+
if (!request.headers.authorization) {
|
|
74
|
+
return reply.code(401).send({ error: "Unauthorized" });
|
|
75
|
+
// Remaining hooks and handler are skipped
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
In callback style: call `reply.send()` without calling `done()`.
|
|
81
|
+
|
|
82
|
+
## Scope rules
|
|
83
|
+
|
|
84
|
+
- **App-level hooks** run for ALL routes (including child plugin routes)
|
|
85
|
+
- **Route-level hooks** run AFTER app-level hooks of the same type
|
|
86
|
+
- **Encapsulated hooks** only affect routes within the same plugin scope
|
|
87
|
+
- **Exception**: `onClose` does NOT respect encapsulation (always global)
|
|
88
|
+
- Multiple hooks of the same type: specified as arrays `preHandler: [fn1, fn2]`
|
|
89
|
+
|
|
90
|
+
## Common patterns
|
|
91
|
+
|
|
92
|
+
**Request timing:**
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
fastify.addHook("onRequest", async (request) => {
|
|
96
|
+
request.startTime = Date.now();
|
|
97
|
+
});
|
|
98
|
+
fastify.addHook("onResponse", async (request, reply) => {
|
|
99
|
+
request.log.info({ ms: Date.now() - request.startTime }, "request completed");
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Auth guard (plugin-scoped):**
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
function authPlugin(fastify, opts, done) {
|
|
107
|
+
fastify.addHook("onRequest", async (request, reply) => {
|
|
108
|
+
const token = request.headers.authorization;
|
|
109
|
+
if (!token) return reply.code(401).send({ error: "Unauthorized" });
|
|
110
|
+
request.user = await verifyToken(token);
|
|
111
|
+
});
|
|
112
|
+
done();
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Gotchas
|
|
117
|
+
|
|
118
|
+
- `onRequest`: body is always `undefined` (parsing hasn't happened yet)
|
|
119
|
+
- `onError`: read-only — use `setErrorHandler()` to modify error responses
|
|
120
|
+
- `preSerialization`: skipped for strings, buffers, streams, and null payloads
|
|
121
|
+
- Don't use async functions with streams in hooks — use callback style
|
|
122
|
+
- Hook errors in async: just `throw`; in callback: pass to `done(err)`
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Plugins & Encapsulation
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Creating a plugin](#creating-a-plugin)
|
|
6
|
+
- [Registering plugins](#registering-plugins)
|
|
7
|
+
- [fastify-plugin wrapper](#fastify-plugin-wrapper)
|
|
8
|
+
- [When to use fastify-plugin](#when-to-use-fastify-plugin)
|
|
9
|
+
- [Encapsulation rules](#encapsulation-rules)
|
|
10
|
+
- [Plugin structure pattern](#plugin-structure-pattern)
|
|
11
|
+
- [Plugin timeout](#plugin-timeout)
|
|
12
|
+
- [Checking plugin registration](#checking-plugin-registration)
|
|
13
|
+
- [Gotchas](#gotchas)
|
|
14
|
+
|
|
15
|
+
## Creating a plugin
|
|
16
|
+
|
|
17
|
+
**Callback plugin (FastifyPluginCallback -- project convention):**
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import type { FastifyPluginCallback } from "fastify";
|
|
21
|
+
|
|
22
|
+
const myPlugin: FastifyPluginCallback = (fastify, opts, done) => {
|
|
23
|
+
fastify.decorate("myService", new MyService(opts));
|
|
24
|
+
done();
|
|
25
|
+
};
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Async plugin:**
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
async function myPlugin(fastify, opts) {
|
|
32
|
+
fastify.decorate("myService", new MyService(opts));
|
|
33
|
+
fastify.get("/my-route", async (request) => {
|
|
34
|
+
return fastify.myService.getData();
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Registering plugins
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
fastify.register(myPlugin, { option1: "value" });
|
|
43
|
+
|
|
44
|
+
// With prefix (all routes inside get this prefix)
|
|
45
|
+
fastify.register(myPlugin, { prefix: "/api/v1" });
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## fastify-plugin wrapper
|
|
49
|
+
|
|
50
|
+
By default, `register()` creates a **new encapsulation scope**. Decorators, hooks, and schemas registered inside are invisible to the parent and siblings.
|
|
51
|
+
|
|
52
|
+
**Use `fastify-plugin` to break encapsulation** and share state with the parent:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import fp from "fastify-plugin";
|
|
56
|
+
|
|
57
|
+
export default fp(myPlugin, {
|
|
58
|
+
name: "my-plugin", // for hasPlugin() checks
|
|
59
|
+
fastify: "5.x", // version constraint
|
|
60
|
+
dependencies: ["other-plugin"], // require other plugins first
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### When to use fastify-plugin
|
|
65
|
+
|
|
66
|
+
| Scenario | Use fp? | Why |
|
|
67
|
+
| ---------------------------------- | ------- | ------------------------------- |
|
|
68
|
+
| Shared service (DB, Redis, auth) | Yes | Parent and siblings need access |
|
|
69
|
+
| Scoped routes (e.g., `/admin/*`) | No | Routes stay isolated |
|
|
70
|
+
| Decorating request/reply | Yes | Available to all routes |
|
|
71
|
+
| Adding application hooks | Yes | Hooks apply globally |
|
|
72
|
+
| Feature module with its own routes | No | Encapsulation is desired |
|
|
73
|
+
|
|
74
|
+
## Encapsulation rules
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
Root context (parent)
|
|
78
|
+
├── Plugin A (registered with fp → shares with root)
|
|
79
|
+
│ └── decorators visible to root and siblings
|
|
80
|
+
├── Plugin B (registered WITHOUT fp → scoped)
|
|
81
|
+
│ ├── decorators invisible to root and Plugin A
|
|
82
|
+
│ └── BUT can see root decorators (inheritance flows DOWN)
|
|
83
|
+
└── Plugin C
|
|
84
|
+
└── can see root + Plugin A decorators
|
|
85
|
+
└── cannot see Plugin B decorators
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Key rules:**
|
|
89
|
+
|
|
90
|
+
1. Child contexts inherit from parent (decorators, hooks, schemas)
|
|
91
|
+
2. Parent contexts CANNOT see child decorators/hooks
|
|
92
|
+
3. `fastify-plugin` makes child behave as if registered in parent scope
|
|
93
|
+
4. Hooks always execute parent-first, then child
|
|
94
|
+
5. Same decorator name in different scopes = OK (no collision)
|
|
95
|
+
|
|
96
|
+
## Plugin structure pattern
|
|
97
|
+
|
|
98
|
+
Order inside a plugin: **decorators → hooks → routes**
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import fp from "fastify-plugin";
|
|
102
|
+
|
|
103
|
+
export default fp(
|
|
104
|
+
async (fastify, opts) => {
|
|
105
|
+
// 1. Decorators first
|
|
106
|
+
fastify.decorate("db", await connectDB(opts.dbUrl));
|
|
107
|
+
|
|
108
|
+
// 2. Hooks second
|
|
109
|
+
fastify.addHook("onClose", async () => {
|
|
110
|
+
await fastify.db.close();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// 3. Routes last (if any)
|
|
114
|
+
fastify.get("/health", async () => ({ status: "ok" }));
|
|
115
|
+
},
|
|
116
|
+
{ name: "database" },
|
|
117
|
+
);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Plugin timeout
|
|
121
|
+
|
|
122
|
+
Default: 10 seconds. If a plugin takes longer to load, Fastify throws `FST_ERR_PLUGIN_TIMEOUT`.
|
|
123
|
+
|
|
124
|
+
Fix: set `pluginTimeout: 0` on the Fastify instance to disable, or increase the timeout.
|
|
125
|
+
|
|
126
|
+
## Checking plugin registration
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
fastify.hasPlugin("my-plugin"); // true if registered (uses fp name)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Gotchas
|
|
133
|
+
|
|
134
|
+
- **Lint errors with async plugin signatures**: See CLAUDE.md for why callback plugins avoid the `require-await` lint issue
|
|
135
|
+
- **Plugin options are scoped**: `prefix`, `logLevel`, `logSerializers` are ignored when using `fastify-plugin`
|
|
136
|
+
- **Decorator reference types**: `decorateRequest("data", {})` shares one object across ALL requests. Use `null` + `onRequest` hook instead
|
|
137
|
+
- **await register**: You can `await fastify.register(plugin)` for sequencing, but the plugin still loads in its own tick
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Request, Reply & Error Handling
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Request object](#request-object)
|
|
6
|
+
- [Key properties](#key-properties)
|
|
7
|
+
- [Methods](#methods)
|
|
8
|
+
- [Reply object](#reply-object)
|
|
9
|
+
- [Key methods](#key-methods)
|
|
10
|
+
- [Key properties](#key-properties-1)
|
|
11
|
+
- [Error handling](#error-handling)
|
|
12
|
+
- [Default behavior](#default-behavior)
|
|
13
|
+
- [Custom error handler](#custom-error-handler)
|
|
14
|
+
- [Creating errors with status codes](#creating-errors-with-status-codes)
|
|
15
|
+
- [Error handler scoping](#error-handler-scoping)
|
|
16
|
+
- [Key FST_ERR codes](#key-fst_err-codes)
|
|
17
|
+
- [404 handler](#404-handler)
|
|
18
|
+
- [Gotchas](#gotchas)
|
|
19
|
+
|
|
20
|
+
## Request object
|
|
21
|
+
|
|
22
|
+
### Key properties
|
|
23
|
+
|
|
24
|
+
| Property | Type | Description |
|
|
25
|
+
| -------------- | --------------- | ------------------------------- |
|
|
26
|
+
| `query` | object | Parsed query string |
|
|
27
|
+
| `body` | any | Parsed request body |
|
|
28
|
+
| `params` | object | URL parameters |
|
|
29
|
+
| `headers` | object | Request headers |
|
|
30
|
+
| `raw` | IncomingMessage | Node.js raw request |
|
|
31
|
+
| `id` | string | Request ID |
|
|
32
|
+
| `ip` | string | Client IP (respects trustProxy) |
|
|
33
|
+
| `ips` | string[] | Proxy chain IPs |
|
|
34
|
+
| `hostname` | string | Host from header |
|
|
35
|
+
| `protocol` | string | `http` or `https` |
|
|
36
|
+
| `method` | string | HTTP method |
|
|
37
|
+
| `url` | string | Request URL |
|
|
38
|
+
| `routeOptions` | object | Route config, schema, bodyLimit |
|
|
39
|
+
| `server` | FastifyInstance | Fastify instance |
|
|
40
|
+
| `log` | Logger | Request-scoped Pino logger |
|
|
41
|
+
| `is404` | boolean | True if no route matched |
|
|
42
|
+
|
|
43
|
+
### Methods
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
request.getValidationFunction("body"); // get compiled validator
|
|
47
|
+
request.validateInput(data, "querystring"); // validate arbitrary data
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Reply object
|
|
51
|
+
|
|
52
|
+
### Key methods
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
reply.code(201); // set status code (chainable)
|
|
56
|
+
reply.status(201); // alias for .code()
|
|
57
|
+
reply.header("X-Custom", "val"); // set single header (chainable)
|
|
58
|
+
reply.headers({ k: "v" }); // set multiple headers
|
|
59
|
+
reply.type("application/json"); // set Content-Type (chainable)
|
|
60
|
+
reply.redirect("/new-url"); // redirect (302 default)
|
|
61
|
+
reply.redirect("/url", 301); // redirect with status
|
|
62
|
+
|
|
63
|
+
reply.send(payload); // send response
|
|
64
|
+
// payload can be: object, string, Buffer, Stream, Error, null
|
|
65
|
+
|
|
66
|
+
reply.hijack(); // take over response (skips serialization + onSend)
|
|
67
|
+
reply.callNotFound(); // invoke 404 handler
|
|
68
|
+
|
|
69
|
+
reply.serialize(payload); // serialize using route's serializer
|
|
70
|
+
reply.serializer(fn); // set custom serializer for this reply
|
|
71
|
+
|
|
72
|
+
reply.getHeader("key"); // get response header
|
|
73
|
+
reply.hasHeader("key"); // check header exists
|
|
74
|
+
reply.removeHeader("key"); // remove header
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Key properties
|
|
78
|
+
|
|
79
|
+
| Property | Type | Description |
|
|
80
|
+
| ------------- | --------------- | ------------------------- |
|
|
81
|
+
| `statusCode` | number | Current status code |
|
|
82
|
+
| `sent` | boolean | True after response sent |
|
|
83
|
+
| `elapsedTime` | number | Ms since request received |
|
|
84
|
+
| `raw` | ServerResponse | Node.js raw response |
|
|
85
|
+
| `request` | Request | Associated request |
|
|
86
|
+
| `server` | FastifyInstance | Fastify instance |
|
|
87
|
+
|
|
88
|
+
## Error handling
|
|
89
|
+
|
|
90
|
+
### Default behavior
|
|
91
|
+
|
|
92
|
+
Fastify catches errors from:
|
|
93
|
+
|
|
94
|
+
- Sync `throw` in handler
|
|
95
|
+
- Rejected promise in async handler
|
|
96
|
+
- `done(error)` in callback handler
|
|
97
|
+
|
|
98
|
+
Default error response:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{ "statusCode": 500, "error": "Internal Server Error", "message": "..." }
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Custom error handler
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
fastify.setErrorHandler((error, request, reply) => {
|
|
108
|
+
request.log.error(error);
|
|
109
|
+
|
|
110
|
+
// Validation errors
|
|
111
|
+
if (error.validation) {
|
|
112
|
+
return reply.code(400).send({
|
|
113
|
+
error: "Validation Error",
|
|
114
|
+
message: error.message,
|
|
115
|
+
details: error.validation,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Custom app errors
|
|
120
|
+
const statusCode = error.statusCode ?? 500;
|
|
121
|
+
reply.code(statusCode).send({
|
|
122
|
+
error: statusCode >= 500 ? "Internal Server Error" : error.message,
|
|
123
|
+
statusCode,
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Creating errors with status codes
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
// Option 1: Set statusCode on Error
|
|
132
|
+
const err = new Error("Not found");
|
|
133
|
+
err.statusCode = 404;
|
|
134
|
+
throw err;
|
|
135
|
+
|
|
136
|
+
// Option 2: reply.send(error) with code
|
|
137
|
+
reply.code(404).send(new Error("Not found"));
|
|
138
|
+
|
|
139
|
+
// Option 3: Use http-errors
|
|
140
|
+
import createError from "http-errors";
|
|
141
|
+
throw createError(404, "User not found");
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Error handler scoping
|
|
145
|
+
|
|
146
|
+
Error handlers are encapsulated. A plugin can set its own error handler:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
fastify.register(async (instance) => {
|
|
150
|
+
instance.setErrorHandler((error, request, reply) => {
|
|
151
|
+
// handles errors only for routes in this plugin
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
If a new error is thrown inside an error handler, the **parent** error handler handles it.
|
|
157
|
+
|
|
158
|
+
### Key FST_ERR codes
|
|
159
|
+
|
|
160
|
+
| Code | Description |
|
|
161
|
+
| ---------------------------- | ---------------------------- |
|
|
162
|
+
| `FST_ERR_CTP_BODY_TOO_LARGE` | Body exceeds bodyLimit |
|
|
163
|
+
| `FST_ERR_VALIDATION` | Schema validation failed |
|
|
164
|
+
| `FST_ERR_BAD_STATUS_CODE` | Invalid HTTP status code |
|
|
165
|
+
| `FST_ERR_REP_ALREADY_SENT` | Reply already sent |
|
|
166
|
+
| `FST_ERR_HOOK_TIMEOUT` | Hook timed out |
|
|
167
|
+
| `FST_ERR_PLUGIN_TIMEOUT` | Plugin took too long to load |
|
|
168
|
+
| `FST_ERR_DUPLICATED_ROUTE` | Route already registered |
|
|
169
|
+
|
|
170
|
+
Access: `import { errorCodes } from "fastify"` → check with `instanceof`.
|
|
171
|
+
|
|
172
|
+
### 404 handler
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
fastify.setNotFoundHandler((request, reply) => {
|
|
176
|
+
reply.code(404).send({ error: "Not Found", message: `Route ${request.url} not found` });
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// With preHandler hooks:
|
|
180
|
+
fastify.setNotFoundHandler({ preHandler: [authHook] }, handler);
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Gotchas
|
|
184
|
+
|
|
185
|
+
- **reply.send() in async**: always `return reply.send()` or `return reply` — otherwise Fastify also sends the return value
|
|
186
|
+
- **reply.sent**: check this before sending to avoid `FST_ERR_REP_ALREADY_SENT`
|
|
187
|
+
- **reply.hijack()**: use for SSE, WebSockets — Fastify won't touch the response after this
|
|
188
|
+
- **Error objects only**: `reply.send("string error")` sends as plain text, not through error handler. Always send `Error` instances
|
|
189
|
+
- **Streams**: if a stream errors after headers are sent, Fastify can't change the status code
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Routes & Handlers
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Route declaration](#route-declaration)
|
|
6
|
+
- [URL parameters](#url-parameters)
|
|
7
|
+
- [Async handler patterns](#async-handler-patterns)
|
|
8
|
+
- [Route-level config](#route-level-config)
|
|
9
|
+
- [Route constraints](#route-constraints)
|
|
10
|
+
- [Gotchas](#gotchas)
|
|
11
|
+
|
|
12
|
+
## Route declaration
|
|
13
|
+
|
|
14
|
+
**Full form:**
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
fastify.route({
|
|
18
|
+
method: "GET", // or ["GET", "POST"]
|
|
19
|
+
url: "/users/:id",
|
|
20
|
+
schema: { ... }, // validation + serialization
|
|
21
|
+
handler: async (request, reply) => { ... },
|
|
22
|
+
// Optional hooks (run AFTER app-level hooks):
|
|
23
|
+
onRequest: [fn],
|
|
24
|
+
preHandler: [fn],
|
|
25
|
+
preSerialization: [fn],
|
|
26
|
+
onSend: [fn],
|
|
27
|
+
onResponse: [fn],
|
|
28
|
+
onError: [fn],
|
|
29
|
+
onTimeout: [fn],
|
|
30
|
+
// Options:
|
|
31
|
+
config: { ... }, // access via reply.routeOptions.config
|
|
32
|
+
bodyLimit: 1048576,
|
|
33
|
+
logLevel: "warn",
|
|
34
|
+
constraints: { version: "1.0.0", host: "example.com" },
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Shorthand:**
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
fastify.get(url, [options], handler);
|
|
42
|
+
fastify.post(url, [options], handler);
|
|
43
|
+
fastify.put(url, [options], handler);
|
|
44
|
+
fastify.delete(url, [options], handler);
|
|
45
|
+
fastify.patch(url, [options], handler);
|
|
46
|
+
fastify.head(url, [options], handler);
|
|
47
|
+
fastify.options(url, [options], handler);
|
|
48
|
+
fastify.all(url, [options], handler); // all HTTP methods
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## URL parameters
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
// Parametric
|
|
55
|
+
fastify.get("/users/:id", handler); // request.params.id
|
|
56
|
+
|
|
57
|
+
// Multiple params
|
|
58
|
+
fastify.get("/users/:id/posts/:postId", handler);
|
|
59
|
+
|
|
60
|
+
// Optional param
|
|
61
|
+
fastify.get("/posts/:id?", handler); // id is optional
|
|
62
|
+
|
|
63
|
+
// Wildcard (catch-all)
|
|
64
|
+
fastify.get("/files/*", handler); // request.params["*"]
|
|
65
|
+
|
|
66
|
+
// Multi-param with separators
|
|
67
|
+
fastify.get("/near/:lat-:lng/radius/:r", handler);
|
|
68
|
+
|
|
69
|
+
// Regex (expensive — avoid on hot paths)
|
|
70
|
+
fastify.get("/file/:name(^\\d+).png", handler);
|
|
71
|
+
|
|
72
|
+
// Escape colon (literal colon)
|
|
73
|
+
fastify.post("/name::verb"); // matches /name:verb
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Async handler patterns
|
|
77
|
+
|
|
78
|
+
**Return value (preferred):**
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
fastify.get("/", async (request, reply) => {
|
|
82
|
+
const data = await getData();
|
|
83
|
+
return data; // auto-sends with 200
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**With status code:**
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
fastify.post("/", async (request, reply) => {
|
|
91
|
+
const user = await createUser(request.body);
|
|
92
|
+
return reply.code(201).send(user);
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Deferred reply (callback outside promise chain):**
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
fastify.get("/", async (request, reply) => {
|
|
100
|
+
setImmediate(() => reply.send({ hello: "world" }));
|
|
101
|
+
await reply; // MUST await reply when sending outside promise
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Route-level config
|
|
106
|
+
|
|
107
|
+
Access custom config via `reply.routeOptions.config`:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
fastify.get("/hello", { config: { greeting: "Hi!" } }, (req, reply) => {
|
|
111
|
+
reply.send(reply.routeOptions.config.greeting);
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Route constraints
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
// Version constraint (requires Accept-Version header)
|
|
119
|
+
fastify.get("/", { constraints: { version: "1.0.0" } }, handlerV1);
|
|
120
|
+
fastify.get("/", { constraints: { version: "2.0.0" } }, handlerV2);
|
|
121
|
+
|
|
122
|
+
// Host constraint
|
|
123
|
+
fastify.get("/", { constraints: { host: "api.example.com" } }, handler);
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Gotchas
|
|
127
|
+
|
|
128
|
+
- **Don't mix async + callback** — pick one pattern per handler
|
|
129
|
+
- **Don't return undefined** in async handlers (Fastify waits for response)
|
|
130
|
+
- **Arrow functions** don't bind `this` to Fastify instance — use `function` if you need `this`
|
|
131
|
+
- **Regex params** hurt routing performance — use sparingly
|
|
132
|
+
- **Multiple params per path segment** (`:lat-:lng`) reduce router perf
|
|
133
|
+
- **HEAD routes** auto-created from GET unless you define HEAD first; disable with `exposeHeadRoutes: false`
|
|
134
|
+
- **Version constraints** require `Accept-Version` header in requests; add `Vary` header to prevent cache poisoning
|