API REST em Node.js e TypeScript para gestão de usuários, pessoas, endereços e catálogo de produtos, com PostgreSQL executado via Docker Compose.
- Runtime: Node.js (ESM)
- Servidor HTTP: Fastify
- Banco de dados: PostgreSQL (
pg+ TypeORM) - ORM: TypeORM com decorators para novas entidades (
Product,Category) - Validação: Zod (body, params e variáveis de ambiente)
- Build: tsup (saída em
build/)
O projeto segue uma arquitetura em camadas, separando responsabilidades entre apresentação, regras de aplicação, acesso a dados e infraestrutura.
flowchart TB
client[Cliente_HTTP]
controllers[http/controllers]
useCases[use-cases]
interfaces[repository_interfaces]
pgImpl[repositories/pg]
libPg[lib/pg]
typeorm[lib/typeorm]
db[(PostgreSQL_Docker)]
client --> controllers
controllers --> useCases
useCases --> interfaces
pgImpl -.->|implements| interfaces
pgImpl --> libPg
typeorm --> db
libPg --> db
| Pasta | Responsabilidade |
|---|---|
entities/ |
Classes de domínio (User, Person, Address, Product, Category) |
entities/models/ |
Contratos TypeScript das entidades (IUser, IPerson, IAddress, IProduct, ICategory) |
env/ |
Validação de variáveis de ambiente com Zod |
http/controllers/ |
Rotas e handlers HTTP (entrada da API) |
lib/pg/ |
Conexão PostgreSQL via driver pg (repositórios legados) |
lib/typeorm/ |
DataSource TypeORM (appDataSource) com synchronize: true |
utils/ |
Utilitários compartilhados (ex.: tratamento global de erros) |
repositories/ |
Contratos de acesso a dados (*.repository.interface.ts) |
repositories/pg/ |
Implementações PostgreSQL dos repositórios (driver pg) |
repositories/typeorm/ |
Implementações TypeORM (ProductRepository, CategoryRepository) |
use-cases/ |
Regras de aplicação e orquestração |
use-cases/factory/ |
Factories que instanciam use cases com suas dependências |
use-cases/errors/ |
Erros de domínio reutilizados na aplicação |
src/app.ts— carregareflect-metadata, inicializa o TypeORM, registra as rotas e conecta oglobalErrorHandlersrc/server.ts— sobe o servidor na porta definida emPORTsrc/utils/global-error-handler.ts— mapeia erros de domínio e validação para respostas HTTP
As classes em entities/ implementam contratos em entities/models/, desacoplando repositórios e use cases das implementações concretas:
| Interface | Classe | Campos principais |
|---|---|---|
IUser |
User |
username, password |
IPerson |
Person |
cpf, name, birth, email, user_id |
IAddress |
Address |
street, number (integer), complement, neighborhood, city, state, zip_code, person_id |
IProduct |
Product (TypeORM) |
name, description, image_url, price, categories[] — id UUID |
ICategory |
Category (TypeORM) |
name, created_at |
Product e Category possuem relação ManyToMany via tabela product_category. Ao criar um produto, categorias podem ser enviadas no body e são persistidas com cascade: true.
As implementações PostgreSQL em repositories/pg/ tipam parâmetros e retornos com as interfaces de User, Person e Address. Product e Category usam TypeORM em repositories/typeorm/ e são sincronizados automaticamente pelo appDataSource.
Configuração em src/lib/typeorm/typeorm.ts:
- Conexão PostgreSQL reutilizando variáveis de ambiente (
POSTGRES_*) - Entidades registradas:
Product,Category - Migration
ProductAutoGenerateUUID— defineuuid_generate_v4()como default doproduct.id synchronize: true— cria/atualiza tabelas automaticamente em desenvolvimentologginghabilitado quandoNODE_ENV === 'development'
Atenção:
synchronize: trueé adequado para desenvolvimento. Em produção, prefira migrations explícitas.
Os use cases dependem de interfaces de repositório, não das implementações concretas. As factories instanciam as classes em repositories/pg/ e as injetam via contrato:
| Interface | Implementação PG | Métodos |
|---|---|---|
IPersonRepository |
PersonRepository |
create |
IUserRepository |
UserRepository |
create, findWithPerson |
IAddressRepository |
AddressRepository |
create, findAddressesByPersonId |
IProductRepository |
ProductRepository (TypeORM) |
create |
ICategoryRepository |
CategoryRepository (TypeORM) |
create |
| Use case | Descrição |
|---|---|
CreateCategoryUseCase |
Cria categoria via TypeORM |
Controllers em http/controllers/category/ expõem o endpoint abaixo.
| Use case | Descrição |
|---|---|
CreateProductUseCase |
Cria produto com categorias opcionais via TypeORM |
Controllers em http/controllers/product/ expõem o endpoint abaixo.
| Use case | Descrição |
|---|---|
CreateAddressUseCase |
Cria endereço vinculado a uma pessoa (person_id) |
FindAddressByPersonUseCase |
Lista endereços de uma pessoa com paginação (page, limit) |
Controllers em http/controllers/address/ expõem os endpoints abaixo.
Os controllers não instanciam repositórios diretamente. A composição fica centralizada em use-cases/factory/:
| Factory | Use case |
|---|---|
makeCreateUserUseCase() |
CreateUserUseCase |
makeCreatePersonUseCase() |
CreatePersonUseCase |
makeFindWithPersonUseCase() |
FindWithPersonUseCase |
makeCreateAddressUseCase() |
CreateAddressUseCase |
makeFindAddressByPersonUseCase() |
FindAddressByPersonUseCase |
makeCreateProductUseCase() |
CreateProductUseCase |
makeCreateCategoryUseCase() |
CreateCategoryUseCase |
A função globalErrorHandler em src/utils/global-error-handler.ts centraliza as respostas HTTP e é registrada em app.ts via app.setErrorHandler:
| Erro | Status | Resposta |
|---|---|---|
ZodError (validação de body/params) |
400 |
{ message: "Validation error", errors: ... } |
ResourceNotFoundError |
404 |
{ message: "Resource not found" } |
| Demais erros | 500 |
{ message: "Internal server error" } |
O use case FindWithPersonUseCase lança ResourceNotFoundError quando o usuário não existe, em vez de retornar undefined para o controller tratar manualmente.
| Método | Rota | Descrição | Body / Params |
|---|---|---|---|
POST |
/user |
Cria usuário | { "username": "string", "password": "string" } |
GET |
/user/:id |
Busca usuário com dados de person (JOIN) | id na URL — retorna 404 se não encontrado |
POST |
/person |
Cria pessoa vinculada a um usuário | { "cpf": "string", "name": "string", "birth": "YYYY-MM-DD", "email": "string", "user_id": number } |
POST |
/address |
Cria endereço vinculado a uma pessoa | { "street": "string", "number": number, "complement": "string", "neighborhood": "string", "city": "string", "state": "string", "zip_code": "string", "person_id": number } |
GET |
/address/person/:person_id |
Lista endereços de uma pessoa (paginado) | person_id na URL; query page e limit (padrão: 1 e 10) |
POST |
/product |
Cria produto com categorias opcionais | { "name": "string", "description": "string", "image_url": "string", "price": number, "categories": [{ "name": "string", "id"?: number }] } |
POST |
/category |
Cria categoria | { "name": "string" } |
curl -X POST http://localhost:3000/user \
-H "Content-Type: application/json" \
-d '{"username": "celso", "password": "123456"}'curl -X POST http://localhost:3000/person \
-H "Content-Type: application/json" \
-d '{
"cpf": "11111111111",
"name": "Celso Gonçalves",
"birth": "1900-01-01",
"email": "teste@teste.com",
"user_id": 1
}'Observação: o campo
cpfno banco aceita até 11 caracteres (varchar(11)). Envie o CPF sem máscara (apenas dígitos).
curl -X POST http://localhost:3000/address \
-H "Content-Type: application/json" \
-d '{
"street": "Rua das Flores",
"number": 123,
"complement": "Apto 45",
"neighborhood": "Centro",
"city": "São Paulo",
"state": "SP",
"zip_code": "01001000",
"person_id": 1
}'curl "http://localhost:3000/address/person/1?page=1&limit=10"curl -X POST http://localhost:3000/product \
-H "Content-Type: application/json" \
-d '{
"name": "Ração Premium",
"description": "Ração para cães adultos 15kg",
"image_url": "https://example.com/racao.png",
"price": 89.90,
"categories": [
{ "name": "Alimentação" },
{ "name": "Cães" }
]
}'curl -X POST http://localhost:3000/category \
-H "Content-Type: application/json" \
-d '{ "name": "Alimentação" }'- Node.js 22+
- npm
- Docker e Docker Compose
Copie .env.example para .env e preencha:
PORT=3000
POSTGRES_USER=root
POSTGRES_PASSWORD=pettech
POSTGRES_DB=pettech
POSTGRES_HOST=localhost
POSTGRES_PORT=5432docker compose up -dO docker-compose.yml sobe um container PostgreSQL (pettech-postgres) na porta 5432, com volume persistente e script de inicialização em docker/postgres/.
Migração da tabela address (bancos já existentes):
Se o container já foi criado antes das colunas number, complement e neighborhood, execute manualmente:
docker exec -i pettech-postgres psql -U root -d pettech < docker/postgres/002-add-address-columns.sqlO script docker/postgres/002-add-address-columns.sql adiciona as colunas ausentes na tabela address.
npm installnpm run start:devO servidor ficará disponível em http://localhost:3000.
npm run build
npm run startO comando build compila o código TypeScript para a pasta build/ (ignorada pelo Git).
| Script | Comando | Uso |
|---|---|---|
start:dev |
tsx watch src/server.ts |
Desenvolvimento com hot reload |
start |
tsx src/server.js |
Execução após build |
build |
tsup src --out-dir build |
Compila TypeScript para build/ |
| Pacote | Uso |
|---|---|
fastify |
Servidor HTTP |
pg |
Driver PostgreSQL |
typeorm |
ORM para entidades Product e Category |
reflect-metadata |
Suporte a decorators TypeORM (requerido no bootstrap) |
zod |
Validação de schemas e variáveis de ambiente |
dotenv |
Carregamento do arquivo .env |
| Pacote | Uso |
|---|---|
typescript |
Compilador TypeScript |
tsx |
Execução de TS em desenvolvimento |
tsup |
Bundler/build para build/ |
@types/node, @types/pg |
Tipagens |
eslint, prettier e plugins |
Lint e formatação de código |
pettech/
├── build/ # saída do tsup (gitignored)
├── docker/
│ └── postgres/ # scripts de init e migrações SQL
├── src/
│ ├── entities/
│ │ └── models/
│ ├── env/
│ ├── http/controllers/
│ │ ├── address/
│ │ ├── category/
│ │ ├── person/
│ │ ├── product/
│ │ └── user/
│ ├── lib/
│ │ ├── pg/
│ │ └── typeorm/
│ │ └── migrations/
│ ├── repositories/
│ │ ├── *.repository.interface.ts
│ │ ├── pg/
│ │ └── typeorm/
│ ├── utils/
│ └── use-cases/
│ ├── errors/
│ └── factory/
├── docker-compose.yml
├── .env.example
└── package.json
Coleção de requisições disponível em .postman/Pettech.postman_collection.json.
.env— credenciais e configurações locaisbuild/— artefatos de compilaçãonode_modules/— dependências instaladas
Código desenvolvido por Celso Gonçalves e com o apoio do Cursor Agent para a criação do README e commits.