Fumadocs + Code Hike
Backend

Fastify + Plugins

Framework web rápido e eficiente com ecossistema robusto de plugins

O que é Fastify?

Fastify é um framework web para Node.js focado em performance, baixo overhead e uma arquitetura de plugins poderosa. Oferece performance superior e compatibilidade com o ecossistema Node.js.

Por que utilizamos Fastify na IngenioLab?

  • Performance superior: Até 30.000 req/s com baixo overhead
  • TypeScript first-class: Suporte nativo e excelente DX
  • Arquitetura de plugins: Modular e extensível
  • Schema-based: Validação e serialização automática
  • Logging integrado: Pino logger por padrão
  • Segurança: Plugins robustos para autenticação e proteção

Instalação Base

# Fastify core
npm install fastify
# Plugins essenciais IngenioLab
npm install \
@fastify/basic-auth \
@fastify/cors \
@fastify/csrf-protection \
@fastify/helmet \
@fastify/jwt \
@fastify/multipart \
@fastify/rate-limit \
@fastify/static \
@fastify/swagger \
@fastify/swagger-ui \
@fastify/websocket \
@scalar/fastify-api-reference
# Dependências de desenvolvimento
npm install -D @types/node

Estrutura de Projeto IngenioLab

backend-fastify/
├── src/
│ ├── plugins/ # Configuração de plugins
│ │ ├── auth.ts
│ │ ├── cors.ts
│ │ ├── database.ts
│ │ └── swagger.ts
│ ├── routes/ # Definição de rotas
│ │ ├── auth/
│ │ ├── users/
│ │ └── health.ts
│ ├── schemas/ # Schemas Zod/JSON
│ │ ├── auth.ts
│ │ └── user.ts
│ ├── services/ # Lógica de negócio
│ ├── utils/ # Utilitários
│ ├── app.ts # Configuração do app
│ └── server.ts # Entrada da aplicação
├── tests/
├── prisma/
├── package.json
└── tsconfig.json

Configuração Base do Servidor

1. server.ts (Entrada da aplicação):

// src/server.ts
import { build } from './app'
import { env } from './utils/env'
const start = async () => {
try {
const app = await build({
logger: {
level: env.LOG_LEVEL,
prettyPrint: env.NODE_ENV === 'development'
}
})
const address = await app.listen({
port: env.PORT,
host: '0.0.0.0'
})
app.log.info(`Server listening at ${address}`)
} catch (err) {
console.error(err)
process.exit(1)
}
}
start()

2. app.ts (Configuração principal):

// src/app.ts
import fastify, { FastifyInstance, FastifyServerOptions } from 'fastify'
// Import de todos os plugins
import cors from './plugins/cors'
import helmet from './plugins/helmet'
import rateLimit from './plugins/rateLimit'
import auth from './plugins/auth'
import swagger from './plugins/swagger'
import database from './plugins/database'
// Import de rotas
import healthRoutes from './routes/health'
import authRoutes from './routes/auth'
import userRoutes from './routes/users'
export async function build(opts: FastifyServerOptions = {}): Promise<FastifyInstance> {
const app = fastify(opts)
// Plugins de segurança (ordem importa)
await app.register(helmet)
await app.register(cors)
await app.register(rateLimit)
// Plugins de funcionalidade
await app.register(database)
await app.register(auth)
await app.register(swagger)
// Rotas
await app.register(healthRoutes, { prefix: '/health' })
await app.register(authRoutes, { prefix: '/auth' })
await app.register(userRoutes, { prefix: '/users' })
return app
}

Configuração dos Plugins IngenioLab

1. CORS Plugin:

// src/plugins/cors.ts
import fp from 'fastify-plugin'
import cors, { FastifyCorsOptions } from '@fastify/cors'
import { FastifyInstance } from 'fastify'
import { env } from '../utils/env'
const corsOptions: FastifyCorsOptions = {
origin: env.NODE_ENV === 'development'
? true
: ['https://app.ingeniolab.com', 'https://admin.ingeniolab.com'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}
export default fp(async function (fastify: FastifyInstance) {
await fastify.register(cors, corsOptions)
})

2. Helmet (Segurança):

// src/plugins/helmet.ts
import fp from 'fastify-plugin'
import helmet from '@fastify/helmet'
import { FastifyInstance } from 'fastify'
export default fp(async function (fastify: FastifyInstance) {
await fastify.register(helmet, {
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
})
})

3. Rate Limiting:

// src/plugins/rateLimit.ts
import fp from 'fastify-plugin'
import rateLimit from '@fastify/rate-limit'
import { FastifyInstance } from 'fastify'
export default fp(async function (fastify: FastifyInstance) {
await fastify.register(rateLimit, {
max: 100, // máximo 100 requests
timeWindow: '1 minute', // por minuto
allowList: ['127.0.0.1'], // IPs sem limite
keyGenerator: (request) => {
return request.headers['x-forwarded-for'] as string || request.ip
},
errorResponseBuilder: (request, context) => {
return {
code: 429,
error: 'Too Many Requests',
message: `Rate limit exceeded, retry in ${context.ttl} seconds`,
date: Date.now(),
expiresIn: context.ttl
}
}
})
})

4. JWT Authentication:

// src/plugins/auth.ts
import fp from 'fastify-plugin'
import jwt from '@fastify/jwt'
import { FastifyInstance } from 'fastify'
import { env } from '../utils/env'
export default fp(async function (fastify: FastifyInstance) {
await fastify.register(jwt, {
secret: env.JWT_SECRET,
sign: {
algorithm: 'HS256',
expiresIn: '24h'
},
verify: {
algorithms: ['HS256']
}
})
// Decorator para verificar autenticação
fastify.decorate('authenticate', async function (request, reply) {
try {
await request.jwtVerify()
} catch (err) {
reply.send(err)
}
})
// Decorator para verificar roles
fastify.decorate('authorize', (roles: string[]) => {
return async function (request: any, reply: any) {
try {
await request.jwtVerify()
const userRole = request.user.role
if (!roles.includes(userRole)) {
return reply.status(403).send({
error: 'Forbidden',
message: 'Insufficient permissions'
})
}
} catch (err) {
reply.send(err)
}
}
})
})

5. Basic Auth (Para endpoints administrativos):

// src/plugins/basicAuth.ts
import fp from 'fastify-plugin'
import basicAuth from '@fastify/basic-auth'
import { FastifyInstance } from 'fastify'
import { env } from '../utils/env'
export default fp(async function (fastify: FastifyInstance) {
await fastify.register(basicAuth, {
validate: async (username, password, req, reply) => {
if (username !== env.ADMIN_USERNAME || password !== env.ADMIN_PASSWORD) {
return new Error('Unauthorized')
}
},
authenticate: { realm: 'IngenioLab Admin' }
})
})

6. File Upload (Multipart):

// src/plugins/multipart.ts
import fp from 'fastify-plugin'
import multipart from '@fastify/multipart'
import { FastifyInstance } from 'fastify'
export default fp(async function (fastify: FastifyInstance) {
await fastify.register(multipart, {
limits: {
fileSize: 10 * 1024 * 1024, // 10MB
files: 5 // máximo 5 arquivos
},
attachFieldsToBody: true
})
// Utilitário para salvar arquivos
fastify.decorate('saveFile', async function (file: any, path: string) {
const buffer = await file.toBuffer()
// Lógica para salvar no S3 ou filesystem
return { filename: file.filename, path, size: buffer.length }
})
})

7. Static Files:

// src/plugins/static.ts
import fp from 'fastify-plugin'
import staticFiles from '@fastify/static'
import { FastifyInstance } from 'fastify'
import path from 'node:path'
export default fp(async function (fastify: FastifyInstance) {
await fastify.register(staticFiles, {
root: path.join(__dirname, '../../public'),
prefix: '/public/',
decorateReply: false // Não adicionar reply.sendFile
})
// Servir uploads (com autenticação)
await fastify.register(staticFiles, {
root: path.join(__dirname, '../../uploads'),
prefix: '/uploads/',
decorateReply: false,
preHandler: fastify.authenticate // Apenas usuários autenticados
})
})

8. WebSocket Support:

// src/plugins/websocket.ts
import fp from 'fastify-plugin'
import websocket from '@fastify/websocket'
import { FastifyInstance } from 'fastify'
export default fp(async function (fastify: FastifyInstance) {
await fastify.register(websocket, {
options: {
maxPayload: 1048576, // 1MB
verifyClient: (info) => {
// Verificar origem, autenticação, etc.
return true
}
}
})
// Exemplo de rota WebSocket
fastify.register(async function (fastify) {
fastify.get('/ws/chat', { websocket: true }, (connection, req) => {
connection.socket.on('message', (message) => {
// Processar mensagem
connection.socket.send(`Echo: ${message}`)
})
connection.socket.on('close', () => {
fastify.log.info('Client disconnected')
})
})
})
})

9. CSRF Protection:

// src/plugins/csrf.ts
import fp from 'fastify-plugin'
import csrf from '@fastify/csrf-protection'
import { FastifyInstance } from 'fastify'
export default fp(async function (fastify: FastifyInstance) {
await fastify.register(csrf, {
sessionPlugin: '@fastify/secure-session',
csrfOpts: {
hmacKey: 'seu-hmac-key-secreto-aqui',
cookieKey: '_csrf',
cookieOpts: {
path: '/',
sameSite: 'strict',
httpOnly: true,
secure: process.env.NODE_ENV === 'production'
}
}
})
})

Documentação API

10. Swagger + Scalar API Reference:

// src/plugins/swagger.ts
import fp from 'fastify-plugin'
import swagger from '@fastify/swagger'
import swaggerUi from '@fastify/swagger-ui'
import scalarPlugin from '@scalar/fastify-api-reference'
import { FastifyInstance } from 'fastify'
export default fp(async function (fastify: FastifyInstance) {
// Swagger Core
await fastify.register(swagger, {
openapi: {
info: {
title: 'IngenioLab API',
description: 'API documentation for IngenioLab services',
version: '1.0.0'
},
servers: [
{
url: 'http://localhost:3000',
description: 'Development server'
}
],
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT'
}
}
}
}
})
// Swagger UI (interface tradicional)
await fastify.register(swaggerUi, {
routePrefix: '/docs',
uiConfig: {
docExpansion: 'list',
deepLinking: false
},
staticCSP: true,
transformStaticCSP: (header) => header
})
// Scalar API Reference (interface moderna)
await fastify.register(scalarPlugin, {
routePrefix: '/reference',
configuration: {
theme: 'alternate',
layout: 'modern',
showSidebar: true,
hideDownloadButton: false
}
})
})

Exemplo de Rota Completa

// src/routes/users/index.ts
import { FastifyInstance } from 'fastify'
import { CreateUserSchema, UserResponseSchema, UpdateUserSchema } from '../../schemas/user'
import { userService } from '../../services/userService'
export default async function userRoutes(fastify: FastifyInstance) {
// GET /users - Listar usuários
fastify.get('/', {
schema: {
description: 'List all users',
tags: ['users'],
security: [{ bearerAuth: [] }],
querystring: {
type: 'object',
properties: {
page: { type: 'integer', minimum: 1, default: 1 },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }
}
},
response: {
200: {
type: 'object',
properties: {
users: { type: 'array', items: UserResponseSchema },
pagination: {
type: 'object',
properties: {
page: { type: 'integer' },
limit: { type: 'integer' },
total: { type: 'integer' }
}
}
}
}
}
},
preHandler: [fastify.authenticate]
}, async (request, reply) => {
const { page = 1, limit = 10 } = request.query as any
const result = await userService.getUsers({ page, limit })
return reply.send(result)
})
// POST /users - Criar usuário
fastify.post('/', {
schema: {
description: 'Create new user',
tags: ['users'],
security: [{ bearerAuth: [] }],
body: CreateUserSchema,
response: {
201: UserResponseSchema
}
},
preHandler: [fastify.authorize(['admin'])]
}, async (request, reply) => {
const userData = request.body as any
const newUser = await userService.createUser(userData)
return reply.status(201).send(newUser)
})
// File upload example
fastify.post('/avatar', {
schema: {
description: 'Upload user avatar',
tags: ['users'],
consumes: ['multipart/form-data'],
response: {
200: {
type: 'object',
properties: {
url: { type: 'string' }
}
}
}
},
preHandler: [fastify.authenticate]
}, async (request, reply) => {
const data = await request.file()
if (!data) {
return reply.status(400).send({ error: 'No file uploaded' })
}
const savedFile = await fastify.saveFile(data, 'avatars/')
return reply.send({ url: savedFile.path })
})
}

Testing com Fastify

// tests/routes/users.test.ts
import { test } from 'vitest'
import { build } from '../../src/app'
test('GET /users should return user list', async () => {
const app = await build()
const response = await app.inject({
method: 'GET',
url: '/users',
headers: {
authorization: 'Bearer valid-jwt-token'
}
})
expect(response.statusCode).toBe(200)
expect(response.json()).toHaveProperty('users')
})
test('POST /users should create user', async () => {
const app = await build()
const userData = {
name: 'João Silva',
email: 'joao@exemplo.com',
password: 'MinhaSenh@123'
}
const response = await app.inject({
method: 'POST',
url: '/users',
headers: {
authorization: 'Bearer admin-jwt-token',
'content-type': 'application/json'
},
payload: userData
})
expect(response.statusCode).toBe(201)
expect(response.json()).toHaveProperty('id')
})

Performance Tips

1. Serialização Rápida:

const userSchema = {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
email: { type: 'string' }
}
}
fastify.get('/users/:id', {
schema: { response: { 200: userSchema } }
}, async (request, reply) => {
// Fastify serializa automaticamente usando fast-json-stringify
return user
})

2. Logging Estruturado:

fastify.get('/users', async (request, reply) => {
request.log.info({ userId: request.user.id }, 'Fetching users')
try {
const users = await userService.getUsers()
request.log.info({ count: users.length }, 'Users fetched successfully')
return users
} catch (error) {
request.log.error({ error }, 'Failed to fetch users')
throw error
}
})