fastifylisted
Install: claude install-skill zhaojiannet/canon
This skill enforces Fastify v5 plugin/route conventions for Node and TypeScript projects. The rule: every plugin is encapsulated by default; expose to parent scope only when needed; routes declare schemas, not manual validation.
Apply only when the file imports `fastify` or `@fastify/*`. If the project uses Express or Koa, **STOP** and ask the user before applying these rules.
## Plugin signature
```ts
import type { FastifyPluginAsync } from 'fastify'
export const userRoutes: FastifyPluginAsync = async (fastify, opts) => {
fastify.get('/users/:id', { schema: getUserSchema }, async (req, reply) => {
return { id: req.params.id }
})
}
```
Use `async (fastify, opts) =>` over `(fastify, opts, done) =>`. Async return is the completion signal.
## fastify-plugin (fp) — when to wrap
Wrap with `fp` when:
- The plugin calls `fastify.decorate(...)` and parent or sibling scopes need access.
- The plugin calls `fastify.addHook(...)` that should propagate to the whole app.
- It registers a top-level shared resource (database connection, cache).
Do **not** wrap when:
- The plugin only registers routes (routes belong in their own encapsulated scope).
- The decorators are intentionally local to the subtree.
```ts
import fp from 'fastify-plugin'
export default fp(async (fastify, opts) => {
fastify.decorate('db', await createDb(opts))
}, { name: 'db-plugin' })
```
## Route schemas — required
Every route declares JSON schema for `body`, `querystring`, `params`, and `respons