Plugins de Fastify
Organiza aplicaciones Fastify con plugins encapsulados, carga automática y decoradores compartidos.
Receta
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
import Fastify from "fastify";
import fp from "fastify-plugin";
import autoload from "@fastify/autoload";
import { join } from "node:path";
const app = Fastify({ logger: true });
// Decorador compartido (rompe la encapsulación intencionalmente)
await app.register(fp(async (fastify) => {
fastify.decorate("db", { query: async (sql: string) => [] });
}));
// Carga automática de todos los plugins en ./plugins y rutas en ./routes
await app.register(autoload, { dir: join(import.meta.dirname, "plugins") });
await app.register(autoload, { dir: join(import.meta.dirname, "routes") });
await app.listen({ port: 3000 });Cuándo usarlo: Cualquier aplicación Fastify que vaya más allá de un solo archivo. Los plugins son la unidad de modularidad principal.
Ejemplo de funcionamiento
src/
app.ts
plugins/
auth.ts
database.ts
routes/
users.ts
health.ts
// plugins/database.ts
import fp from "fastify-plugin";
export default fp(async (fastify) => {
const pool = { query: async (sql: string) => [{ id: 1 }] };
fastify.decorate("db", pool);
fastify.addHook("onClose", async () => {
// cerrar conexiones del pool
});
});
// routes/users.ts
import { FastifyPluginAsync } from "fastify";
const users: FastifyPluginAsync = async (fastify) => {
fastify.get("/", async () => {
return fastify.db.query("SELECT * FROM users");
});
};
export default users;
// app.ts
import Fastify from "fastify";
import autoload from "@fastify/autoload";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const app = Fastify({ logger: true });
await app.register(autoload, { dir: join(__dirname, "plugins") });
await app.register(autoload, { dir: join(__dirname, "routes"), options: { prefix: "/api" } });Lo que esto demuestra:
fastify-pluginenvuelve plugins compartidos para un ámbito global@fastify/autoloaddescubre plugins por convención de directorio- Hook
onClosepara limpieza al apagar - Plugins de ruta montados con el prefijo
/api
Análisis profundo
Cómo funciona
register(plugin)crea un nuevo contexto de encapsulación- Los decoradores, hooks y rutas dentro de un plugin son invisibles para los plugins hermanos
fastify-plugin(fp) eleva un plugin al ámbito padre- Autoload lee el directorio y registra cada archivo como un plugin
Reglas de encapsulación
| Patrón | Ámbito | Usar para |
|---|---|---|
register simple | Solo ámbito hijo | Módulos de características |
fastify-plugin | Ámbito padre | DB, auth, config |
Opción prefix | Prefijo de URL | Agrupación de rutas |
Hook onClose | Limpieza | Pools de conexión |
Fusión de declaraciones de TypeScript
import "fastify";
declare module "fastify" {
interface FastifyInstance {
db: { query: (sql: string) => Promise<unknown[]> };
}
}Errores comunes
- Decorador no visible en las rutas - registrado en un plugin hermano sin
fp. Solución: envuélvelo confastify-plugin. - Dependencias de plugins circulares - el plugin A registra B que registra A. Solución: extrae las dependencias compartidas a un tercer plugin.
- El orden de autoload es el orden del sistema de archivos - el plugin de autenticación puede cargarse después de las rutas. Solución: usa directorios
plugins/yroutes/; el directorio de plugins se carga primero. - Plugin asíncrono sin await - las rutas se registran antes de que la DB esté lista. Solución:
await app.register(dbPlugin)antes de las rutas. - Falta la limpieza
onClose- el pool de conexiones se filtra en SIGTERM. Solución: cierra los pools enonClose. - Exportación predeterminada vs con nombre - autoload espera una función asíncrona
export default. Solución: sigue la convención.
Alternativas
| Alternativa | Usar cuándo | No usar cuándo |
|---|---|---|
| Registro manual en app.ts | Aplicaciones pequeñas (< 5 plugins) | Código base en crecimiento |
| Módulos de NestJS | Necesitas DI, decoradores, guardas | Quieres un framework mínimo |
| Express Router | Código base de Express | Proyecto Fastify |
| Monolito de un solo archivo | Prueba de concepto/prototipo | Servicio de producción |
Preguntas frecuentes
¿Cuál es la diferencia entre plugin y ruta?
Ambos usan register. Las rutas son plugins que solo definen endpoints. Los plugins pueden añadir decoradores, hooks y plugins hijos.
¿Cómo determina autoload el orden?
Alfabéticamente por nombre de archivo. Prefija los archivos con números (01-database.ts) para controlar el orden si es necesario.
¿Pueden los plugins tener opciones?
Sí. fastify.register(plugin, { prefix: "/api", dbUrl: "..." }). Accede a través de las opciones de fastify-plugin o del closure.
¿Cómo pruebo un plugin de forma aislada?
Crea una instancia Fastify() vacía, register el plugin, usa inject(). No se necesita puerto.
¿Debería cada característica ser un plugin?
Sí. Un plugin por dominio (usuarios, pedidos, facturación) con sus propias rutas, hooks y esquemas.
¿Cómo comparto esquemas entre plugins?
Regístralos con fastify.addSchema() en un plugin de esquemas cargado primero, luego $ref en los esquemas de ruta.
¿Funciona autoload con TypeScript?
Usa tsx o compila a JS primero. Autoload carga archivos .js de la salida de la compilación en producción.
¿Cómo se compara esto con los módulos de NestJS?
Los plugins de Fastify son más ligeros y no tienen un contenedor DI. Los módulos de NestJS añaden proveedores, importaciones y exportaciones. Consulta Conceptos básicos de NestJS.
Relacionado
- Conceptos básicos de Fastify - introducción a la encapsulación
- Validación de esquemas JSON - esquemas compartidos
- Registro con Pino - plugin de registro
- Prueba de aplicaciones Fastify - pruebas de aislamiento de plugins
- Mejores prácticas de Fastify - lista de verificación de la sección
Versiones de la pila: Esta página fue escrita para Node.js 24.18.0 (LTS activa), npm 10+, TypeScript 5.6+, Express 5, Fastify 5 y NestJS 11.