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 corenpm install fastify# Plugins essenciais IngenioLabnpm 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 desenvolvimentonpm 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.tsimport { 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.tsimport fastify, { FastifyInstance, FastifyServerOptions } from 'fastify'// Import de todos os pluginsimport 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 rotasimport 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 funcionalidadeawait app.register(database)await app.register(auth)await app.register(swagger)// Rotasawait 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.tsimport 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.tsimport 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.tsimport 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 requeststimeWindow: '1 minute', // por minutoallowList: ['127.0.0.1'], // IPs sem limitekeyGenerator: (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.tsimport 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çãofastify.decorate('authenticate', async function (request, reply) {try {await request.jwtVerify()} catch (err) {reply.send(err)}})// Decorator para verificar rolesfastify.decorate('authorize', (roles: string[]) => {return async function (request: any, reply: any) {try {await request.jwtVerify()const userRole = request.user.roleif (!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.tsimport 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.tsimport 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, // 10MBfiles: 5 // máximo 5 arquivos},attachFieldsToBody: true})// Utilitário para salvar arquivosfastify.decorate('saveFile', async function (file: any, path: string) {const buffer = await file.toBuffer()// Lógica para salvar no S3 ou filesystemreturn { filename: file.filename, path, size: buffer.length }})})
7. Static Files:
// src/plugins/static.tsimport 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.tsimport 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, // 1MBverifyClient: (info) => {// Verificar origem, autenticação, etc.return true}}})// Exemplo de rota WebSocketfastify.register(async function (fastify) {fastify.get('/ws/chat', { websocket: true }, (connection, req) => {connection.socket.on('message', (message) => {// Processar mensagemconnection.socket.send(`Echo: ${message}`)})connection.socket.on('close', () => {fastify.log.info('Client disconnected')})})})})
9. CSRF Protection:
// src/plugins/csrf.tsimport 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.tsimport 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 Coreawait 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.tsimport { 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áriosfastify.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 anyconst result = await userService.getUsers({ page, limit })return reply.send(result)})// POST /users - Criar usuáriofastify.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 anyconst newUser = await userService.createUser(userData)return reply.status(201).send(newUser)})// File upload examplefastify.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.tsimport { 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-stringifyreturn 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}})