Fumadocs + Code Hike
Backend

Setup Completo - Backend

Guia completo para criar um projeto backend TypeScript + Fastify + Prisma do zero

Setup Completo - Backend IngenioLab

Guia completo para criar um projeto backend TypeScript + Fastify + Prisma do zero, seguindo os padrões da IngenioLab.

Índice

  1. Configuração Inicial do Projeto
  2. Configuração do TypeScript
  3. Setup do Servidor Fastify
  4. Configuração do Banco com Prisma
  5. Migrações do Banco
  6. Seeding do Banco
  7. Plugins do Fastify
  8. Sistema de Autenticação
  9. Sistema de Rotas
  10. Rotas de Autenticação
  11. Configuração de Testes
  12. Testando a API

1. Configuração Inicial do Projeto

1.1 Criar Diretório e Inicializar npm

mkdir meu-projeto-backend
cd meu-projeto-backend
npm init -y

1.2 Instalar Dependências

# Dependências principais
npm install fastify
npm install @fastify/cors @fastify/helmet @fastify/jwt @fastify/multipart @fastify/rate-limit @fastify/static @fastify/swagger
npm install @scalar/fastify-api-reference
npm install fastify-plugin fastify-type-provider-zod
npm install @prisma/client prisma
npm install bcrypt dayjs dotenv uuid zod
npm install pino pino-pretty
# Dependências de desenvolvimento
npm install -D typescript @types/node tsx
npm install -D @types/bcrypt @types/uuid
npm install -D vitest @types/supertest supertest
npm install -D @biomejs/biome

1.3 Criar Estrutura de Pastas

mkdir -p src/{routes,middlewares,plugins,services,utils,types,schemas}
mkdir -p tests/{helpers,utils}
mkdir -p prisma public scripts docs

2. Configuração do TypeScript

2.1 Criar tsconfig.json

{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"target": "ES2022",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"moduleDetection": "force",
"resolveJsonModule": true,
"outDir": "./dist",
"rootDir": ".",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"removeComments": true,
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"isolatedModules": true,
"forceConsistentCasingInFileNames": true,
"incremental": true,
"skipLibCheck": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@/config/*": ["src/config/*"],
"@/schemas/*": ["src/schemas/*"],
"@/middlewares/*": ["src/middlewares/*"],
"@/routes/*": ["src/routes/*"],
"@/services/*": ["src/services/*"],
"@/types/*": ["src/types/*"],
"@/utils/*": ["src/utils/*"],
"@/plugins/*": ["src/plugins/*"],
"@/generated/*": ["prisma/generated/*"],
"@/prisma/*": ["prisma/*"]
}
},
"include": ["src/**/*", "prisma/**/*", "tests"],
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
}

2.2 Criar Tipagens de Environment

Criar src/types/env.d.ts:

declare namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'development' | 'production' | 'test';
PORT: string;
DATABASE_URL: string;
JWT_SECRET: string;
JWT_EXPIRES_IN: string;
CORS_ORIGIN: string;
ENABLE_TRACING: string;
}
}

2.3 Criar Extensões do Fastify

Criar src/types/fastify.d.ts:

import type { PrismaClient } from '@/prisma/generated/prisma';
declare module 'fastify' {
interface FastifyInstance {
prisma: PrismaClient;
authenticate: (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
authorize: (roles: string[]) => (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
}
interface FastifyRequest {
startTime?: [number, number];
user?: {
id: string;
email: string;
role: string;
};
}
}

3. Setup do Servidor Fastify

3.1 Criar Estrutura Básica da App

Criar src/app.ts:

import Fastify, { type FastifyInstance } from 'fastify';
import {
jsonSchemaTransform,
serializerCompiler,
validatorCompiler,
} from 'fastify-type-provider-zod';
export async function initializeApp(): Promise<FastifyInstance> {
const app = Fastify({
logger: {
level: process.env.NODE_ENV === 'development' ? 'debug' : 'info',
transport: {
target: 'pino-pretty',
options: {
colorize: true,
translateTime: 'SYS:standard',
ignore: 'pid,hostname',
},
},
},
});
app.log.info('Iniciando aplicação...');
app.log.info(`Ambiente: ${process.env.NODE_ENV || 'development'}`);
// Configurar validação Zod
app.setValidatorCompiler(validatorCompiler);
app.setSerializerCompiler(serializerCompiler);
// Handler de erro básico
app.setErrorHandler(async (error, request, reply) => {
const statusCode = error.statusCode ?? 500;
app.log.error({
error: error.message,
stack: error.stack,
url: request.url,
method: request.method,
});
return reply.status(statusCode).send({
success: false,
message: error.message || 'Erro interno do servidor',
statusCode,
...(process.env.NODE_ENV === 'development' && { stack: error.stack }),
});
});
// Handler de rota não encontrada
app.setNotFoundHandler(async (_, reply) => {
return reply.status(404).send({
success: false,
message: 'Rota não encontrada',
statusCode: 404,
});
});
return app;
}

3.2 Criar Ponto de Entrada do Servidor

Criar src/server.ts:

import { initializeApp } from '@/app';
async function main() {
const host = process.env.HOST || '0.0.0.0';
const port = Number.parseInt(process.env.PORT || '3333', 10);
const app = await initializeApp();
try {
await app.listen({
host,
port,
});
app.log.info(`Servidor rodando em http://${host}:${port}`);
} catch (error) {
app.log.error('Falha ao iniciar servidor:', error);
process.exit(1);
}
}
const gracefulShutdown = async (signal: string) => {
console.log(`\n🔄 Recebido ${signal}, encerrando graciosamente...`);
process.exit(0);
};
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
main();

3.3 Adicionar Scripts ao package.json

{
"scripts": {
"dev": "tsx watch src/server.ts",
"build": "tsc",
"start": "node dist/server.js",
"test": "vitest"
}
}

4. Configuração do Banco com Prisma

4.1 Inicializar Prisma

npx prisma init

4.2 Criar Schema Básico do Prisma

Criar prisma/schema.prisma:

generator client {
provider = "prisma-client-js"
output = "./generated/prisma"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
enum UserRole {
ADMIN
AGENT
USER
}
model User {
id String @id @default(cuid())
email String @unique
password String
name String
phone String?
role UserRole @default(AGENT)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("users")
}

4.3 Criar Plugin do Prisma

Criar src/plugins/prisma.plugin.ts:

import type { FastifyPluginAsync } from 'fastify';
import fp from 'fastify-plugin';
import { PrismaClient } from '@/prisma/generated/prisma';
declare module 'fastify' {
interface FastifyInstance {
prisma: PrismaClient;
}
}
const prismaPlugin: FastifyPluginAsync = async (app) => {
const prisma = new PrismaClient({
log:
process.env.NODE_ENV === 'development'
? ['query', 'error', 'warn']
: ['error'],
errorFormat: 'pretty',
});
app.log.info('Conectando ao Prisma...');
try {
await prisma.$connect();
app.log.info('Prisma conectado com sucesso');
} catch (error) {
app.log.error('Falha ao conectar ao Prisma:', error);
throw error;
}
app.decorate('prisma', prisma);
app.addHook('onClose', async (app) => {
await app.prisma.$disconnect();
app.log.info('Prisma desconectado com sucesso');
});
};
export default fp(prismaPlugin, {
name: 'prisma-plugin',
fastify: '5.x',
});

4.4 Configuração do Environment

Criar .env:

# Database
DATABASE_URL="postgresql://user:password@localhost:5432/mydb?schema=public"
# JWT
JWT_SECRET="sua-chave-secreta-jwt-super-segura"
JWT_EXPIRES_IN="7d"
# Server
PORT=3333
NODE_ENV="development"
# CORS
CORS_ORIGIN="http://localhost:3000"

5. Migrações do Banco

5.1 Gerar Cliente Prisma

npx prisma generate

5.2 Criar Primeira Migração

npx prisma migrate dev --name initial_migration

5.3 Adicionar Scripts de Migração ao package.json

{
"scripts": {
"db:generate": "prisma generate",
"db:migrate": "prisma migrate dev",
"db:studio": "prisma studio",
"db:reset": "prisma migrate reset"
}
}

6. Seeding do Banco

6.1 Criar Arquivo de Seed

Criar prisma/seed.ts:

import { PrismaClient, UserRole } from './generated/prisma';
import bcrypt from 'bcrypt';
const prisma = new PrismaClient();
async function main() {
console.log('🌱 Iniciando seeding do banco...');
// Criar usuário admin
const adminPassword = await bcrypt.hash('admin123', 10);
const admin = await prisma.user.upsert({
where: { email: 'admin@ingeniolab.com.br' },
update: {},
create: {
email: 'admin@ingeniolab.com.br',
password: adminPassword,
name: 'Admin IngenioLab',
role: UserRole.ADMIN,
},
});
// Criar agente de teste
const agentPassword = await bcrypt.hash('agent123', 10);
const agent = await prisma.user.upsert({
where: { email: 'agent@ingeniolab.com.br' },
update: {},
create: {
email: 'agent@ingeniolab.com.br',
password: agentPassword,
name: 'Agente Teste',
role: UserRole.AGENT,
},
});
console.log('✅ Banco populado com sucesso');
console.log(`Admin: ${admin.email}`);
console.log(`Agente: ${agent.email}`);
}
main()
.catch((e) => {
console.error('❌ Falha no seeding:', e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});

6.2 Configurar Script de Seed

Adicionar ao package.json:

{
"scripts": {
"db:seed": "tsx prisma/seed.ts"
},
"prisma": {
"seed": "tsx prisma/seed.ts"
}
}

6.3 Executar Seeding

npm run db:seed

7. Plugins do Fastify

7.1 Adicionar Plugins Principais à App

Atualizar src/app.ts para incluir plugins:

import fastifyCors from '@fastify/cors';
import fastifyHelmet from '@fastify/helmet';
import fastifyJwt from '@fastify/jwt';
import fastifyMultipart from '@fastify/multipart';
import fastifyRateLimit from '@fastify/rate-limit';
import fastifyStatic from '@fastify/static';
import fastifySwagger from '@fastify/swagger';
import scalarFastifyApiReference from '@scalar/fastify-api-reference';
import prismaPlugin from '@/plugins/prisma.plugin';
import path from 'node:path';
export async function initializeApp(): Promise<FastifyInstance> {
// ... código existente ...
// Registrar CORS
await app.register(fastifyCors, {
origin: process.env.CORS_ORIGIN ?? '*',
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
});
// Registrar plugins de segurança
app.register(fastifyHelmet, { contentSecurityPolicy: false });
await app.register(fastifyRateLimit, {
max: 1000,
timeWindow: '1 minute',
});
// Registrar JWT
await app.register(fastifyJwt, {
secret: process.env.JWT_SECRET,
sign: {
expiresIn: process.env.JWT_EXPIRES_IN,
},
});
// Registrar multipart (upload de arquivos)
await app.register(fastifyMultipart, {
limits: {
fileSize: 10 * 1024 * 1024, // 10 MB
},
});
// Registrar servir arquivos estáticos
await app.register(fastifyStatic, {
root: path.resolve(__dirname, '../public'),
prefix: '/public/',
});
// Registrar documentação Swagger
await app.register(fastifySwagger, {
openapi: {
info: {
title: 'API IngenioLab',
description: 'Documentação da API backend',
version: '1.0.0',
},
servers: [
{
url: process.env.NODE_ENV === 'production'
? 'https://api.ingeniolab.com.br'
: 'http://localhost:3333',
},
],
components: {
securitySchemes: {
BearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
},
},
},
},
transform: jsonSchemaTransform,
});
// Registrar UI da documentação
await app.register(scalarFastifyApiReference, {
routePrefix: '/docs',
configuration: {
theme: 'kepler',
},
});
// Registrar plugin do Prisma
await app.register(prismaPlugin);
return app;
}

8. Sistema de Autenticação

8.1 Criar Utilitários de Autenticação

Criar src/utils/auth-helpers.ts:

export const authConfig = {
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN || '7d',
},
bcrypt: {
saltRounds: 10,
},
};
export interface JWTPayload {
sub: string; // user id
email: string;
role: string;
iat?: number;
exp?: number;
}

8.2 Criar Middleware de Autenticação

Criar src/middlewares/auth.middleware.ts:

import type { FastifyRequest, FastifyReply } from 'fastify';
import type { JWTPayload } from '@/utils/auth-helpers';
export async function authenticate(
request: FastifyRequest,
reply: FastifyReply
): Promise<void> {
try {
const token = await request.jwtVerify<JWTPayload>();
// Adicionar info do usuário ao request
request.user = {
id: token.sub,
email: token.email,
role: token.role,
};
} catch (error) {
return reply.status(401).send({
success: false,
message: 'Token inválido ou expirado',
statusCode: 401,
});
}
}
export function authorize(allowedRoles: string[]) {
return async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {
if (!request.user) {
return reply.status(401).send({
success: false,
message: 'Autenticação necessária',
statusCode: 401,
});
}
if (!allowedRoles.includes(request.user.role)) {
return reply.status(403).send({
success: false,
message: 'Permissões insuficientes',
statusCode: 403,
});
}
};
}

8.3 Registrar Middleware de Autenticação na App

Atualizar src/app.ts:

import { authenticate, authorize } from '@/middlewares/auth.middleware';
export async function initializeApp(): Promise<FastifyInstance> {
// ... configuração existente ...
// Registrar middleware de auth
app.decorate('authenticate', authenticate);
app.decorate('authorize', authorize);
// ... resto da configuração ...
}

9. Sistema de Rotas

9.1 Criar Schemas de Resposta

Criar src/schemas/response-schema.ts:

import { z } from 'zod';
export const SuccessResponseSchema = z.object({
success: z.literal(true),
data: z.any(),
message: z.string().optional(),
});
export const ErrorResponseSchema = z.object({
success: z.literal(false),
message: z.string(),
statusCode: z.number(),
stack: z.string().optional(),
});
export const PaginatedResponseSchema = z.object({
success: z.literal(true),
data: z.array(z.any()),
pagination: z.object({
page: z.number(),
limit: z.number(),
total: z.number(),
totalPages: z.number(),
}),
});

9.2 Criar Rota de Health (Exemplo)

Criar src/routes/health.route.ts:

import type { FastifyPluginAsync } from 'fastify';
import { z } from 'zod';
const HealthResponseSchema = z.object({
status: z.string(),
timestamp: z.string(),
uptime: z.number(),
environment: z.string(),
version: z.string(),
});
export const healthRoutes: FastifyPluginAsync = async (app) => {
app.get(
'/health',
{
schema: {
description: 'Endpoint de verificação de saúde',
tags: ['Health'],
response: {
200: HealthResponseSchema,
},
},
},
async (_, reply) => {
return reply.send({
status: 'OK',
timestamp: new Date().toISOString(),
uptime: Math.floor(process.uptime()),
environment: process.env.NODE_ENV || 'development',
version: '1.0.0',
});
}
);
};

10. Rotas de Autenticação

10.1 Criar Schemas de Autenticação

Criar src/schemas/auth-schema.ts:

import { z } from 'zod';
export const RegisterSchema = z.object({
name: z.string().min(2, 'Nome deve ter pelo menos 2 caracteres'),
email: z.string().email('Formato de email inválido'),
password: z.string().min(8, 'Senha deve ter pelo menos 8 caracteres'),
phone: z.string().optional(),
});
export const LoginSchema = z.object({
email: z.string().email('Formato de email inválido'),
password: z.string().min(1, 'Senha é obrigatória'),
});
export const AuthResponseSchema = z.object({
success: z.literal(true),
data: z.object({
user: z.object({
id: z.string(),
email: z.string(),
name: z.string(),
role: z.string(),
}),
token: z.string(),
}),
});

10.2 Criar Serviço de Autenticação

Criar src/services/auth.service.ts:

import bcrypt from 'bcrypt';
import type { PrismaClient, User } from '@/prisma/generated/prisma';
import { authConfig } from '@/utils/auth-helpers';
export class AuthService {
constructor(private prisma: PrismaClient) {}
async register(data: {
name: string;
email: string;
password: string;
phone?: string;
}): Promise<User> {
// Verificar se usuário já existe
const existingUser = await this.prisma.user.findUnique({
where: { email: data.email },
});
if (existingUser) {
throw new Error('Usuário já existe com este email');
}
// Hash da senha
const hashedPassword = await bcrypt.hash(
data.password,
authConfig.bcrypt.saltRounds
);
// Criar usuário
return await this.prisma.user.create({
data: {
name: data.name,
email: data.email,
password: hashedPassword,
phone: data.phone,
},
});
}
async login(email: string, password: string): Promise<User | null> {
// Encontrar usuário
const user = await this.prisma.user.findUnique({
where: { email },
});
if (!user) {
return null;
}
// Verificar senha
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) {
return null;
}
return user;
}
}

10.3 Criar Rotas de Autenticação

Criar src/routes/auth.route.ts:

import type { FastifyPluginAsync } from 'fastify';
import { AuthService } from '@/services/auth.service';
import {
RegisterSchema,
LoginSchema,
AuthResponseSchema,
} from '@/schemas/auth-schema';
import { ErrorResponseSchema } from '@/schemas/response-schema';
import { z } from 'zod';
export const authRoutes: FastifyPluginAsync = async (app) => {
const authService = new AuthService(app.prisma);
// Endpoint de registro
app.post(
'/auth/register',
{
schema: {
description: 'Registrar novo usuário',
tags: ['Authentication'],
body: RegisterSchema,
response: {
201: AuthResponseSchema,
400: ErrorResponseSchema,
409: ErrorResponseSchema,
},
},
},
async (request, reply) => {
try {
const userData = request.body;
const user = await authService.register(userData);
// Gerar token JWT
const token = await reply.jwtSign({
sub: user.id,
email: user.email,
role: user.role,
});
return reply.status(201).send({
success: true,
data: {
user: {
id: user.id,
email: user.email,
name: user.name,
role: user.role,
},
token,
},
});
} catch (error) {
const message = error instanceof Error ? error.message : 'Falha no registro';
const statusCode = message.includes('já existe') ? 409 : 400;
return reply.status(statusCode).send({
success: false,
message,
statusCode,
});
}
}
);
// Endpoint de login
app.post(
'/auth/login',
{
schema: {
description: 'Login com email e senha',
tags: ['Authentication'],
body: LoginSchema,
response: {
200: AuthResponseSchema,
401: ErrorResponseSchema,
},
},
},
async (request, reply) => {
try {
const { email, password } = request.body;
const user = await authService.login(email, password);
if (!user) {
return reply.status(401).send({
success: false,
message: 'Email ou senha inválidos',
statusCode: 401,
});
}
// Gerar token JWT
const token = await reply.jwtSign({
sub: user.id,
email: user.email,
role: user.role,
});
return reply.send({
success: true,
data: {
user: {
id: user.id,
email: user.email,
name: user.name,
role: user.role,
},
token,
},
});
} catch (error) {
app.log.error('Erro no login:', error);
return reply.status(500).send({
success: false,
message: 'Falha no login',
statusCode: 500,
});
}
}
);
// Exemplo de rota protegida
app.get(
'/auth/profile',
{
preHandler: [app.authenticate],
schema: {
description: 'Obter perfil do usuário atual',
tags: ['Authentication'],
security: [{ BearerAuth: [] }],
response: {
200: z.object({
success: z.literal(true),
data: z.object({
id: z.string(),
email: z.string(),
name: z.string(),
role: z.string(),
}),
}),
401: ErrorResponseSchema,
},
},
},
async (request, reply) => {
const user = await app.prisma.user.findUnique({
where: { id: request.user!.id },
select: {
id: true,
email: true,
name: true,
role: true,
},
});
return reply.send({
success: true,
data: user,
});
}
);
};

10.4 Registrar Rotas de Autenticação

Atualizar src/app.ts:

import { authRoutes } from '@/routes/auth.route';
import { healthRoutes } from '@/routes/health.route';
export async function initializeApp(): Promise<FastifyInstance> {
// ... configuração existente ...
// Registrar rotas
await app.register(authRoutes, { prefix: '/api' });
await app.register(healthRoutes, { prefix: '/api' });
return app;
}

11. Configuração de Testes

11.1 Criar Configuração do Vitest

Criar vitest.config.ts:

/// <reference types="vitest" />
import { resolve } from 'path';
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
setupFiles: ['./tests/setup.ts'],
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
exclude: ['node_modules', 'dist', '.idea', '.git', '.cache', 'coverage'],
testTimeout: 30000,
pool: 'threads',
poolOptions: {
threads: {
singleThread: true,
},
},
},
resolve: {
alias: {
'@': resolve(__dirname, './src'),
},
},
});

11.2 Criar Setup de Teste

Criar tests/setup.ts:

import { beforeAll, afterAll } from 'vitest';
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
beforeAll(async () => {
// Configurar ambiente de teste
process.env.NODE_ENV = 'test';
process.env.DATABASE_URL = 'postgresql://user:password@localhost:5432/test_db?schema=public';
// Executar migrações
await execAsync('npx prisma migrate reset --force --skip-seed');
await execAsync('npx prisma migrate deploy');
});
afterAll(async () => {
// Limpeza pode ser adicionada aqui se necessário
});

11.3 Criar Helper de Teste

Criar tests/helpers/test-app.ts:

import { initializeApp } from '@/app';
import type { FastifyInstance } from 'fastify';
let app: FastifyInstance;
export async function createTestApp(): Promise<FastifyInstance> {
if (app) {
return app;
}
app = await initializeApp();
await app.ready();
return app;
}
export async function closeTestApp(): Promise<void> {
if (app) {
await app.close();
}
}

11.4 Criar Teste de Exemplo

Criar src/routes/auth.route.test.ts:

import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { createTestApp, closeTestApp } from '@/../tests/helpers/test-app';
import type { FastifyInstance } from 'fastify';
describe('Rotas de Autenticação', () => {
let app: FastifyInstance;
beforeAll(async () => {
app = await createTestApp();
});
afterAll(async () => {
await closeTestApp();
});
describe('POST /api/auth/register', () => {
it('deve registrar um novo usuário com sucesso', async () => {
const response = await app.inject({
method: 'POST',
url: '/api/auth/register',
payload: {
name: 'Usuário Teste',
email: 'teste@ingeniolab.com.br',
password: 'senha123456',
},
});
expect(response.statusCode).toBe(201);
const body = JSON.parse(response.body);
expect(body.success).toBe(true);
expect(body.data.user.email).toBe('teste@ingeniolab.com.br');
expect(body.data.token).toBeDefined();
});
it('deve retornar erro para email duplicado', async () => {
// Primeiro registro
await app.inject({
method: 'POST',
url: '/api/auth/register',
payload: {
name: 'Usuário Teste',
email: 'duplicado@ingeniolab.com.br',
password: 'senha123456',
},
});
// Segundo registro com mesmo email
const response = await app.inject({
method: 'POST',
url: '/api/auth/register',
payload: {
name: 'Usuário Teste 2',
email: 'duplicado@ingeniolab.com.br',
password: 'senha123456',
},
});
expect(response.statusCode).toBe(409);
const body = JSON.parse(response.body);
expect(body.success).toBe(false);
expect(body.message).toContain('já existe');
});
});
describe('POST /api/auth/login', () => {
beforeAll(async () => {
// Criar usuário de teste
await app.inject({
method: 'POST',
url: '/api/auth/register',
payload: {
name: 'Usuário Login',
email: 'login@ingeniolab.com.br',
password: 'senha123456',
},
});
});
it('deve fazer login com credenciais válidas', async () => {
const response = await app.inject({
method: 'POST',
url: '/api/auth/login',
payload: {
email: 'login@ingeniolab.com.br',
password: 'senha123456',
},
});
expect(response.statusCode).toBe(200);
const body = JSON.parse(response.body);
expect(body.success).toBe(true);
expect(body.data.user.email).toBe('login@ingeniolab.com.br');
expect(body.data.token).toBeDefined();
});
it('deve retornar erro para credenciais inválidas', async () => {
const response = await app.inject({
method: 'POST',
url: '/api/auth/login',
payload: {
email: 'login@ingeniolab.com.br',
password: 'senhaerrada',
},
});
expect(response.statusCode).toBe(401);
const body = JSON.parse(response.body);
expect(body.success).toBe(false);
expect(body.message).toBe('Email ou senha inválidos');
});
});
});

12. Testando a API

12.1 Atualizar package.json com Todos os Scripts

{
"scripts": {
"dev": "tsx watch src/server.ts",
"build": "tsc",
"start": "node dist/server.js",
"db:generate": "prisma generate",
"db:migrate": "prisma migrate dev",
"db:studio": "prisma studio",
"db:seed": "tsx prisma/seed.ts",
"test": "vitest",
"test:watch": "vitest --watch",
"test:coverage": "vitest --coverage"
}
}

12.2 Executar Setup Completo

# Instalar dependências
npm install
# Gerar cliente Prisma
npm run db:generate
# Executar primeira migração
npm run db:migrate
# Popular banco de dados
npm run db:seed
# Iniciar servidor de desenvolvimento
npm run dev

12.3 Testar Endpoints

# Verificação de saúde
curl http://localhost:3333/api/health
# Registrar usuário
curl -X POST http://localhost:3333/api/auth/register \
-H "Content-Type: application/json" \
-d '{"name":"Teste IngenioLab","email":"teste@ingeniolab.com.br","password":"senha123456"}'
# Fazer login
curl -X POST http://localhost:3333/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"teste@ingeniolab.com.br","password":"senha123456"}'
# Testar rota protegida (substitua SEU_TOKEN pelo token recebido no login)
curl http://localhost:3333/api/auth/profile \
-H "Authorization: Bearer SEU_TOKEN"

12.4 Executar Testes

# Executar todos os testes
npm run test
# Executar testes em modo watch
npm run test:watch
# Executar testes com cobertura
npm run test:coverage

12.5 Visualizar Documentação

Abra http://localhost:3333/docs para ver a documentação interativa da API.

12.6 Visualizar Banco de Dados

# Abrir Prisma Studio
npm run db:studio

Próximos Passos

Com este setup completo, você tem uma base sólida para desenvolver APIs backend seguindo os padrões da IngenioLab. Você pode expandir esta base adicionando:

  • Mais rotas e serviços
  • Validações mais complexas
  • Upload de arquivos
  • Integração com serviços externos
  • Caching com Redis
  • Filas de background jobs
  • Monitoramento e métricas

Estrutura Final do Projeto

meu-projeto-backend/
├── prisma/
│ ├── schema.prisma
│ ├── seed.ts
│ └── generated/
├── src/
│ ├── app.ts
│ ├── server.ts
│ ├── middlewares/
│ │ └── auth.middleware.ts
│ ├── plugins/
│ │ └── prisma.plugin.ts
│ ├── routes/
│ │ ├── auth.route.ts
│ │ ├── auth.route.test.ts
│ │ └── health.route.ts
│ ├── schemas/
│ │ ├── auth-schema.ts
│ │ └── response-schema.ts
│ ├── services/
│ │ └── auth.service.ts
│ ├── types/
│ │ ├── env.d.ts
│ │ └── fastify.d.ts
│ └── utils/
│ └── auth-helpers.ts
├── tests/
│ ├── setup.ts
│ └── helpers/
│ └── test-app.ts
├── .env
├── package.json
├── tsconfig.json
└── vitest.config.ts

Esta estrutura fornece uma base robusta e escalável para qualquer projeto backend da IngenioLab.

On this page

Setup Completo - Backend IngenioLabÍndice1. Configuração Inicial do Projeto1.1 Criar Diretório e Inicializar npm1.2 Instalar Dependências1.3 Criar Estrutura de Pastas2. Configuração do TypeScript2.1 Criar tsconfig.json2.2 Criar Tipagens de Environment2.3 Criar Extensões do Fastify3. Setup do Servidor Fastify3.1 Criar Estrutura Básica da App3.2 Criar Ponto de Entrada do Servidor3.3 Adicionar Scripts ao package.json4. Configuração do Banco com Prisma4.1 Inicializar Prisma4.2 Criar Schema Básico do Prisma4.3 Criar Plugin do Prisma4.4 Configuração do Environment5. Migrações do Banco5.1 Gerar Cliente Prisma5.2 Criar Primeira Migração5.3 Adicionar Scripts de Migração ao package.json6. Seeding do Banco6.1 Criar Arquivo de Seed6.2 Configurar Script de Seed6.3 Executar Seeding7. Plugins do Fastify7.1 Adicionar Plugins Principais à App8. Sistema de Autenticação8.1 Criar Utilitários de Autenticação8.2 Criar Middleware de Autenticação8.3 Registrar Middleware de Autenticação na App9. Sistema de Rotas9.1 Criar Schemas de Resposta9.2 Criar Rota de Health (Exemplo)10. Rotas de Autenticação10.1 Criar Schemas de Autenticação10.2 Criar Serviço de Autenticação10.3 Criar Rotas de Autenticação10.4 Registrar Rotas de Autenticação11. Configuração de Testes11.1 Criar Configuração do Vitest11.2 Criar Setup de Teste11.3 Criar Helper de Teste11.4 Criar Teste de Exemplo12. Testando a API12.1 Atualizar package.json com Todos os Scripts12.2 Executar Setup Completo12.3 Testar Endpoints12.4 Executar Testes12.5 Visualizar Documentação12.6 Visualizar Banco de DadosPróximos PassosEstrutura Final do Projeto