@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.
Files changed (117) hide show
  1. package/.claude-plugin/marketplace.json +24 -0
  2. package/.claude-plugin/plugin.json +24 -0
  3. package/CHANGELOG.md +24 -0
  4. package/LICENSE +21 -0
  5. package/README.md +128 -0
  6. package/agents/accessibility-pro.md +139 -0
  7. package/agents/api-builder.md +110 -0
  8. package/agents/code-reviewer.md +190 -0
  9. package/agents/database-expert.md +138 -0
  10. package/agents/debugger.md +241 -0
  11. package/agents/docker-expert.md +51 -0
  12. package/agents/multi-agent-coordinator.md +378 -0
  13. package/agents/nextjs-expert.md +136 -0
  14. package/agents/performance-engineer.md +138 -0
  15. package/agents/playwright-expert.md +126 -0
  16. package/agents/react-specialist.md +97 -0
  17. package/agents/security-scanner.md +105 -0
  18. package/agents/test-generator.md +221 -0
  19. package/agents/typescript-pro.md +253 -0
  20. package/agents/ux-optimizer.md +93 -0
  21. package/docs/rules/orchestration.md.template +126 -0
  22. package/package.json +28 -0
  23. package/skills/bullmq/SKILL.md +225 -0
  24. package/skills/bullmq/references/flows-and-schedulers.md +186 -0
  25. package/skills/bullmq/references/job-types-and-options.md +163 -0
  26. package/skills/bullmq/references/patterns.md +273 -0
  27. package/skills/bullmq/references/production.md +308 -0
  28. package/skills/composition-patterns/SKILL.md +58 -0
  29. package/skills/composition-patterns/references/architecture-avoid-boolean-props.md +87 -0
  30. package/skills/composition-patterns/references/architecture-compound-components.md +107 -0
  31. package/skills/composition-patterns/references/patterns-children-over-render-props.md +77 -0
  32. package/skills/composition-patterns/references/patterns-explicit-variants.md +87 -0
  33. package/skills/composition-patterns/references/react19-no-forwardref.md +37 -0
  34. package/skills/composition-patterns/references/state-context-interface.md +194 -0
  35. package/skills/composition-patterns/references/state-decouple-implementation.md +96 -0
  36. package/skills/composition-patterns/references/state-lift-state.md +126 -0
  37. package/skills/conventional-commits/SKILL.md +148 -0
  38. package/skills/docker/SKILL.md +55 -0
  39. package/skills/docker/references/compose-configs.md +95 -0
  40. package/skills/docker/references/monorepo-dockerfile.md +111 -0
  41. package/skills/drizzle-pg/SKILL.md +202 -0
  42. package/skills/drizzle-pg/references/advanced.md +299 -0
  43. package/skills/drizzle-pg/references/migrations.md +214 -0
  44. package/skills/drizzle-pg/references/queries.md +321 -0
  45. package/skills/drizzle-pg/references/relations.md +272 -0
  46. package/skills/drizzle-pg/references/schema-pg.md +256 -0
  47. package/skills/drizzle-pg/references/sql-operator.md +215 -0
  48. package/skills/fastify-best-practices/SKILL.md +143 -0
  49. package/skills/fastify-best-practices/references/hooks-and-lifecycle.md +122 -0
  50. package/skills/fastify-best-practices/references/plugins-and-encapsulation.md +137 -0
  51. package/skills/fastify-best-practices/references/request-reply-errors.md +189 -0
  52. package/skills/fastify-best-practices/references/routes-and-handlers.md +134 -0
  53. package/skills/fastify-best-practices/references/server-and-options.md +127 -0
  54. package/skills/fastify-best-practices/references/typescript-and-logging.md +223 -0
  55. package/skills/fastify-best-practices/references/validation-and-serialization.md +190 -0
  56. package/skills/ioredis/SKILL.md +51 -0
  57. package/skills/ioredis/references/advanced-patterns.md +312 -0
  58. package/skills/ioredis/references/cluster-sentinel.md +280 -0
  59. package/skills/ioredis/references/connection-options.md +187 -0
  60. package/skills/ioredis/references/core-api.md +179 -0
  61. package/skills/nextjs-best-practices/SKILL.md +194 -0
  62. package/skills/nextjs-best-practices/references/async-patterns.md +84 -0
  63. package/skills/nextjs-best-practices/references/bundling.md +192 -0
  64. package/skills/nextjs-best-practices/references/data-patterns.md +310 -0
  65. package/skills/nextjs-best-practices/references/debug-tricks.md +127 -0
  66. package/skills/nextjs-best-practices/references/directives.md +74 -0
  67. package/skills/nextjs-best-practices/references/error-handling.md +237 -0
  68. package/skills/nextjs-best-practices/references/file-conventions.md +152 -0
  69. package/skills/nextjs-best-practices/references/font.md +175 -0
  70. package/skills/nextjs-best-practices/references/functions.md +116 -0
  71. package/skills/nextjs-best-practices/references/hydration-error.md +86 -0
  72. package/skills/nextjs-best-practices/references/image.md +184 -0
  73. package/skills/nextjs-best-practices/references/metadata.md +305 -0
  74. package/skills/nextjs-best-practices/references/parallel-routes.md +299 -0
  75. package/skills/nextjs-best-practices/references/route-handlers.md +154 -0
  76. package/skills/nextjs-best-practices/references/rsc-boundaries.md +168 -0
  77. package/skills/nextjs-best-practices/references/runtime-selection.md +40 -0
  78. package/skills/nextjs-best-practices/references/scripts.md +148 -0
  79. package/skills/nextjs-best-practices/references/self-hosting.md +210 -0
  80. package/skills/nextjs-best-practices/references/suspense-boundaries.md +67 -0
  81. package/skills/owasp-security-review/SKILL.md +98 -0
  82. package/skills/owasp-security-review/references/a01-broken-access-control.md +78 -0
  83. package/skills/owasp-security-review/references/a02-security-misconfiguration.md +81 -0
  84. package/skills/owasp-security-review/references/a03-supply-chain-failures.md +65 -0
  85. package/skills/owasp-security-review/references/a04-cryptographic-failures.md +82 -0
  86. package/skills/owasp-security-review/references/a05-injection.md +106 -0
  87. package/skills/owasp-security-review/references/a06-insecure-design.md +76 -0
  88. package/skills/owasp-security-review/references/a07-authentication-failures.md +83 -0
  89. package/skills/owasp-security-review/references/a08-integrity-failures.md +72 -0
  90. package/skills/owasp-security-review/references/a09-logging-alerting-failures.md +76 -0
  91. package/skills/owasp-security-review/references/a10-exceptional-conditions.md +131 -0
  92. package/skills/postgresql/SKILL.md +50 -0
  93. package/skills/postgresql/references/ddl-schema.md +300 -0
  94. package/skills/postgresql/references/indexes.md +257 -0
  95. package/skills/postgresql/references/jsonb.md +261 -0
  96. package/skills/postgresql/references/performance.md +291 -0
  97. package/skills/postgresql/references/psql-cli.md +153 -0
  98. package/skills/postgresql/references/queries.md +287 -0
  99. package/skills/postgresql/references/transactions.md +280 -0
  100. package/skills/react-best-practices/SKILL.md +110 -0
  101. package/skills/react-best-practices/references/advanced-patterns.md +91 -0
  102. package/skills/react-best-practices/references/async-patterns.md +233 -0
  103. package/skills/react-best-practices/references/bundle-optimization.md +201 -0
  104. package/skills/react-best-practices/references/client-patterns.md +178 -0
  105. package/skills/react-best-practices/references/js-performance.md +210 -0
  106. package/skills/react-best-practices/references/rendering-performance.md +209 -0
  107. package/skills/react-best-practices/references/rerender-optimization.md +316 -0
  108. package/skills/react-best-practices/references/server-performance.md +274 -0
  109. package/skills/service-worker/SKILL.md +195 -0
  110. package/skills/service-worker/references/api-reference.md +114 -0
  111. package/skills/service-worker/references/caching-strategies.md +202 -0
  112. package/skills/service-worker/references/push-and-sync.md +261 -0
  113. package/skills/typescript-conventions/SKILL.md +51 -0
  114. package/skills/ui-ux-guidelines/SKILL.md +105 -0
  115. package/skills/ui-ux-guidelines/references/accessibility-and-interaction.md +74 -0
  116. package/skills/ui-ux-guidelines/references/forms-content-checklist.md +126 -0
  117. 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