The Complete Production SOP: Enforcing Fastify Plugin Encapsulation Architecture in Autonomous AI Agents
Master Fastify plugin encapsulation, fastify-plugin (fp), and instance decorators in Codex, Claude Code, and Cursor with our deterministic agent SOP.
Technical Overview & Architectural Context
Fastify relies on a directed acyclic graph (DAG) architecture where each call to `fastify.register()` provisions a brand new encapsulation scope. Without strict deterministic boundaries, autonomous coding agents (Codex, Claude Code, Cursor, Windsurf) frequently commit architectural anti-patterns: they attempt to register database pools or shared services within isolated child scopes, causing downstream route handlers to receive `undefined` references, or they invoke decorators multiple times across sibling plugins, triggering fatal initialization crashes. Furthermore, LLMs frequently hallucinate custom monkey-patching approaches or confuse Express-style middleware pipelines with Fastify's encapsulated lifecycle hooks (`onRequest`, `preHandler`). This causes hook leaks that compromise authentication layers and security policies across adjacent routes. The `fastify-plugin-encapsulation` skill eliminates these failure modes. By codifying strict SOP rules—such as mandating `fastify-plugin` (`fp`) wrappers for root-level singletons, configuring explicit plugin dependency metadata (`dependencies`, `decorators`), and strictly isolating route controllers with custom URL prefixes—the agent deterministically produces resilient, modular Fastify microservices and monoliths without runtime state collisions.
Prerequisites & Environment Requirements
Verified Search Intents & Long-Tail Queries Solved
This operational tutorial is engineered to provide deterministic solutions for these developer search queries:
Step-by-Step Execution Playbook
Local Environment Setup & SKILL.md Mounting
Create the local skill manifest directory and inject verified procedural rules into the agent runtime directory.
Autonomous coding assistants inspect workspace configuration roots (e.g., `.cursor/skills/`, `.claude/skills/`, or `~/.agents/skills/`) to parse triggers and scope guardrails. Mounting `SKILL.md` ensures that queries involving Fastify plugins automatically load encapsulation SOPs into active agent context.
mkdir -p .agents/skills/fastify-plugin-encapsulation && cat << 'EOF' > .agents/skills/fastify-plugin-encapsulation/SKILL.md
---
name: "fastify-plugin-encapsulation"
description: "Architect modular Fastify applications using plugin encapsulation and dependency decorators."
metadata:
author: "Fastify"
version: "1.0.0"
runtime_compatibility:
- "codex"
- "claude-code"
- "cursor"
- "windsurf"
triggers:
- "fastify plugin encapsulation"
- "use fastify-plugin fp"
- "decorate fastify instance"
permissions:
fs_read: true
fs_write: "workspace"
shell_exec: false
net_access: false
---
# Fastify Plugin Encapsulation Architecture (fastify-plugin)
Implements Fastify encapsulation rules: creating encapsulated feature scopes, sharing database connections via fastify-plugin (fp), decorating request and server instances (fastify.decorate), and lifecycle hooks.
## When to Invoke
Invoke this skill whenever you need to:
- fastify plugin encapsulation
- use fastify-plugin fp
- decorate fastify instance
## Execution SOP & Rules
1. Wrap shared utility plugins with fastify-plugin: export default fp(async (fastify, opts) => { ... }).
2. Decorate server instance safely: fastify.decorate('db', dbClient).
3. Register plugins with prefixes: fastify.register(authRoutes, { prefix: '/api/auth' }).
4. Understand that un-wrapped plugins retain encapsulation and do not leak hooks to parent scopes.
EOFimport fp from 'fastify-plugin';
import { FastifyPluginAsync } from 'fastify';
import { PrismaClient } from '@prisma/client';
declare module 'fastify' {
interface FastifyInstance {
db: PrismaClient;
}
}
const dbConnector: FastifyPluginAsync = async (fastify, _opts) => {
const prisma = new PrismaClient();
await prisma.$connect();
// fastify-plugin ensures this decorator escapes this scope to parent/sibling contexts
fastify.decorate('db', prisma);
fastify.addHook('onClose', async (instance) => {
await instance.db.$disconnect();
});
};
export default fp(dbConnector, {
name: 'app-db-plugin',
fastify: '>=4.x'
});Never dump entire library docs into the prompt. Reference only the interface declaration module and the `fp()` wrapper contract to preserve context tokens.
Omitting the TypeScript module augmentation for `FastifyInstance` causes compilation failure during agent linting when accessing `fastify.db` in downstream files.
Dependency Installation & TypeScript Interface Setup
Install fastify-plugin along with type definitions to support clean instance and request decorations.
To prevent compilation issues and ensure that decorated methods and properties are accessible across plugins, fastify-plugin must be installed and properly imported as a runtime dependency. Ensure types are correctly referenced.
pnpm add fastify fastify-plugin && pnpm add -D @types/node typescriptimport '@fastify/jwt';
declare module 'fastify' {
interface FastifyRequest {
userRole?: 'admin' | 'user' | 'guest';
}
}Ensure `fastify-plugin` is listed in `dependencies` rather than `devDependencies` to prevent runtime failure in containerized production deployments.
Avoid mutating native Node.js global objects to pass context; use `fastify.decorateRequest()` inside plugins instead.
Intent Matching & Encapsulated Route Hierarchy
Structure route plugins without fastify-plugin to maintain strict scope encapsulation for hooks and validation.
Unlike infrastructure plugins (database, Redis, telemetry), route plugins must NOT be wrapped with `fastify-plugin`. This guarantees that scoped authentication hooks, schema validators, and pre-parsing hooks remain localized to their defined route subtree.
import { FastifyPluginAsync } from 'fastify';
const userRoutes: FastifyPluginAsync = async (fastify, opts) => {
// Scoped hook: strictly isolated to /api/users/*
fastify.addHook('onRequest', async (request, reply) => {
if (!request.headers.authorization) {
return reply.code(401).send({ error: 'Unauthorized: Missing Token' });
}
});
fastify.get('/me', async (request, _reply) => {
// Accesses root decorator registered via fastify-plugin
return { user: 'Current context', status: 'active' };
});
};
// Intentionally exported without fp() to retain child encapsulation
export default userRoutes;Match frontmatter triggers like 'fastify plugin encapsulation' directly in PR descriptions or issue tickets to initiate automated agent workflows.
Wrapping a route file with `fastify-plugin` breaks encapsulation, causing any `onRequest` or `preHandler` hooks to run globally on every subsequent route in the server.
Verification, Linting & Output Quality Gate
Execute automated compiler checks and route tree inspection before approving agent PRs.
Instruct the agent to run the Fastify instance readiness check (`await fastify.ready()`) and output the route tree using `fastify.printRoutes()` or `fastify.printPlugins()` to verify proper encapsulation boundaries.
pnpm tsc --noEmit && node -e "import('./dist/server.js').then(m => m.app.ready()).then(() => console.log('Plugins verified')).catch(e => { console.error(e); process.exit(1); })"Use `console.log(fastify.printPlugins())` inside test bootstrap routines to verify the exact scope hierarchy of registered plugins.
Failing to await `fastify.ready()` before inspecting decorators may yield false-positive runtime undefined errors during startup.
Real-World Scenario: Architecting an Encapsulated Multi-Tenant Fastify Backend
“Refactor our monolithic Fastify app. Create a root-level Redis cache plugin that breaks encapsulation using fastify-plugin, and implement an isolated tenant route plugin at /tenants/:tenantId with a scoped hook that validates tenant IDs without leaking to public routes.”
Runtime Troubleshooting & Failure Modes
Root Cause: The agent registered a plugin containing `fastify.decorate('db', ...)` inside multiple child scopes, or wrapped a plugin with `fp()` that was inadvertently registered twice.
fastify.decorate('db', dbClient); // Ensure this is only executed in a single root plugin, or check if already present:
if (!fastify.hasDecorator('db')) {
fastify.decorate('db', dbClient);
}Root Cause: A service plugin was registered without `fastify-plugin` (`fp`), so the decorator remained trapped in its private scope and was inaccessible to sibling route plugins.
import fp from 'fastify-plugin';
// Wrap plugin to break boundary for shared dependencies:
export default fp(async (fastify, opts) => {
fastify.decorate('db', databaseInstance);
});Frequently Asked Questions (Fastify Plugin Encapsulation Architecture (fastify-plugin) SOP)
Q:When should I wrap a Fastify plugin with fastify-plugin (fp) vs leaving it unwrapped?
Wrap a plugin with fastify-plugin when it provides utilities, decorators, database connections, or global hooks that must be accessible across the entire application. Leave it unwrapped when declaring route endpoints, scoped schemas, or localized authentication hooks that should be confined to a specific sub-tree.
Q:How does fastify-plugin prevent naming collisions and dependency order errors?
fastify-plugin allows you to declare explicit metadata options, such as `name`, `dependencies` (requiring other plugins to load first), and `decorators` (requiring specific instance or request decorators to be present), preventing unhandled startup order race conditions.
Q:Can I decorate the request or reply objects from inside an encapsulated plugin?
Yes. Using `fastify.decorateRequest('propertyName', null)` or `fastify.decorateReply('helper', fn)` inside an unwrapped plugin confines those properties to requests handled within that specific plugin's route context. If wrapped in `fp()`, the request decorator applies globally.
Ready to equip your autonomous agent with Fastify Plugin Encapsulation Architecture (fastify-plugin)?
Copy the complete audited SKILL.md specification or download the 1-click zip bundle directly from our verified registry.