API REST da etapa 1 do sistema de gerenciamento de clínica médica. Este projeto entrega exclusivamente a base de acesso: cadastro de usuários, autenticação JWT e autorização por perfil (RBAC). Os módulos de especialidades, médicos, pacientes e consultas não fazem parte deste escopo.
- Node.js 20+
- TypeScript
- Express 5
- PostgreSQL
- TypeORM
- bcryptjs
- JSON Web Token (JWT)
- ESLint e Prettier
- Node.js 20 ou superior
- Docker Desktop (recomendado para executar o PostgreSQL localmente), ou PostgreSQL em execução
git clone https://github.com/Cleitoncme/medclinic-api.git
cd medclinic-api
npm ciCrie o arquivo .env a partir de .env.example e informe as credenciais locais do PostgreSQL. Não versione esse arquivo.
PORT=3000
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_NAME=medclinic
DATABASE_USER=postgres
DATABASE_PASSWORD=postgres
JWT_SECRET=use-uma-chave-longa-e-segura
JWT_EXPIRES_IN=1hCom o .env configurado, inicie o banco local:
docker compose up -dO serviço usa PostgreSQL 16, cria automaticamente o banco definido por DATABASE_NAME na primeira inicialização e persiste os dados no volume postgres_data. Para verificar se o banco está pronto:
docker compose psPara parar o contêiner sem remover seus dados:
docker compose downCom o PostgreSQL em execução, aplique a migration que cria a tabela users:
npm run migration:runPara desfazer a última migration:
npm run migration:revertOs dois scripts compilam o projeto e usam o DataSource em dist/database/data-source.js.
O cadastro público sempre cria o perfil ATTENDANT; ele não aceita a criação de administradores. Para demonstrar as rotas RBAC, configure temporariamente as variáveis abaixo no .env e execute:
ADMIN_NAME=Admin Demo
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=uma-senha-com-ao-menos-8-caracteresnpm run seed:adminO script não contém senha fixa e não cria uma nova conta caso já exista um usuário com o e-mail configurado.
# desenvolvimento
npm run dev
# produção (compile antes)
npm run build
npm start
# verificações de código
npm run lint
npm run format:checkPor padrão, a API responde em http://localhost:3000. Use GET /health para confirmar que o Express está ativo.
Execute a API com npm run dev e mantenha esse terminal aberto durante os testes. No Postman, crie um ambiente com a variável abaixo:
baseUrl = http://localhost:3000
Em cada teste, clique em New → HTTP Request, selecione o método indicado à esquerda da URL, preencha a URL e clique em Send.
GET {{baseUrl}}/healthResposta esperada (200):
{ "status": "ok" }Crie uma requisição com o método POST — usar GET nesta rota retorna 404.
POST {{baseUrl}}/auth/login
Content-Type: application/jsonNa aba Body, selecione raw e depois JSON. Informe o mesmo e-mail e senha definidos nas variáveis ADMIN_EMAIL e ADMIN_PASSWORD do .env:
{
"email": "admin@example.com",
"password": "uma-senha-com-ao-menos-8-caracteres"
}Resposta esperada (200):
{ "token": "jwt-assinado" }Copie somente o valor de token e salve-o no ambiente do Postman como adminToken.
GET {{baseUrl}}/users/meNa aba Authorization, escolha Bearer Token e cole {{adminToken}} no campo Token. Não escreva Bearer dentro desse campo, pois o Postman o acrescenta automaticamente.
Resposta esperada: 200 com os dados públicos do administrador. A senha não é retornada.
GET {{baseUrl}}/admin/pingUse novamente Authorization → Bearer Token com {{adminToken}}.
Resposta esperada (200):
{ "message": "Administrator access granted." }Se a resposta for 401 Authentication token is invalid or expired, confira se o método é GET, se a URL é /admin/ping e se a aba Authorization está configurada na própria requisição. Cole apenas o JWT no campo Token, sem aspas e sem espaços extras.
Cadastre um usuário comum:
POST {{baseUrl}}/auth/register
Content-Type: application/json{
"name": "Atendente Teste",
"email": "atendente@example.com",
"password": "SenhaSegura123"
}Faça login desse usuário em POST {{baseUrl}}/auth/login, salve o token como attendantToken e envie:
GET {{baseUrl}}/admin/ping
Authorization: Bearer {{attendantToken}}Resposta esperada: 403, comprovando que o perfil ATTENDANT não acessa recursos administrativos.
| Requisição | Resultado esperado |
|---|---|
GET /users/me sem token |
401 |
POST /auth/login com senha incorreta |
401 |
| Novo cadastro com e-mail já usado | 409 |
GET /admin/ping com token de atendente |
403 |
GET /auth/login |
404 (a rota aceita somente POST) |
O projeto organiza responsabilidades em camadas, preparado para receber os futuros domínios da clínica.
src/
├── config/ # leitura e validação das variáveis de ambiente
├── controllers/ # entrada HTTP e respostas
├── database/ # DataSource TypeORM e migrations
├── dtos/ # contratos de entrada e saída
├── entities/ # entidades TypeORM
├── middlewares/ # autenticação, RBAC e erros globais
├── repositories/ # acesso aos dados via TypeORM
├── routes/ # definição de endpoints
├── services/ # regras de negócio
├── types/ # extensão tipada do Express Request
├── utils/ # hash de senha e erros da aplicação
├── app.ts # configuração do Express
└── server.ts # conexão com banco e inicialização HTTP
Fluxo principal: Route → Middleware → Controller → Service → Repository → PostgreSQL.
User possui id UUID, name, email único, passwordHash, role e createdAt. A senha é transformada em hash bcrypt com 12 rounds, não é devolvida nas respostas e a coluna usa select: false no TypeORM.
O token JWT contém o identificador (id) e o perfil (role), possui expiração configurável por JWT_EXPIRES_IN e é enviado nas rotas protegidas no header:
Authorization: Bearer <token>Perfis disponíveis:
| Perfil | Permissões nesta etapa |
|---|---|
ADMIN |
Acessa rotas autenticadas e GET /admin/ping. |
ATTENDANT |
Acessa rotas autenticadas, mas recebe 403 em rotas administrativas. |
GET /health
Resposta 200:
{ "status": "ok" }POST /auth/register
{
"name": "Ana Silva",
"email": "ana@example.com",
"password": "SenhaSegura123"
}name, email e password são obrigatórios e precisam ser strings. O e-mail precisa ser válido, a senha precisa ter no mínimo oito caracteres e o perfil criado é sempre ATTENDANT. Corpos ausentes ou com valores de tipos inválidos retornam 400.
Resposta 201:
{
"user": {
"id": "uuid",
"name": "Ana Silva",
"email": "ana@example.com",
"role": "ATTENDANT",
"createdAt": "2026-09-10T00:00:00.000Z"
}
}POST /auth/login
{
"email": "ana@example.com",
"password": "SenhaSegura123"
}Resposta 200:
{ "token": "jwt-assinado" }Credenciais inválidas retornam 401, sem identificar qual campo falhou.
GET /users/me
Exige token válido. Retorna 200 com os dados públicos do usuário autenticado.
GET /admin/ping
Exige token válido de um usuário ADMIN.
Resposta 200:
{ "message": "Administrator access granted." }Com token de ATTENDANT, retorna 403.
As falhas são tratadas por middleware centralizado e usam a estrutura abaixo:
{
"statusCode": 401,
"message": "Authentication token is required."
}Situações previstas incluem validação inválida (400), credenciais ou token inválidos (401), permissão insuficiente (403), rota/usuário inexistente (404) e e-mail duplicado (409). Falhas inesperadas retornam 500 sem expor detalhes internos.
O desenvolvimento utiliza as branches main, develop, feat/setup-projeto, feat/database, feat/auth, feat/rbac e docs/readme, com commits semânticos que registram a evolução das funcionalidades.