O backend segue uma arquitetura em camadas com bootstrap por factories. Cada domínio de negócio é encapsulado em um conjunto padronizado de arquivos.
index.js
└── CoreFactory.Iniciar()
├── BancoFactory → conecta ao MySQL, valida conexão
└── ServerFactory → cria o Express app, registra middlewares, sobe o servidor
└── Roteamento.IniciarRoteamento(app)
└── Domínios: Auth · Chat · Conversations · Documents · Ingest · Search · Usuario
└── *.Rotas.js → *.Controller.js → *Service.js → Model (Objection)
src/factories/)Responsáveis pelo bootstrap da aplicação. Chamadas em sequência por index.js na raiz.
| Factory | Responsabilidade |
|---|---|
Core.factory.js |
Orquestra Banco + Server |
Banco.factory.js |
Conecta ao MySQL e valida a conexão |
Server.factory.js |
Cria o app Express, registra middlewares globais, sobe o servidor HTTP |
src/routes/index.js)A classe Roteamento registra todos os domínios no Express via IniciarRoteamento(app). Cada domínio tem seu próprio arquivo *.Rotas.js que define as rotas e middlewares daquele domínio.
Todos os domínios são montados sob o prefixo /api, que recebe dois middlewares globais antes de qualquer rota: rateLimitMiddleware (RateLimit.js) e authMiddleware (Validajwt.js). Ou seja, o JWT é decodificado globalmente; os middlewares requireUser/requireAdmin por rota apenas verificam o resultado.
src/controllers/)Recebem a requisição HTTP, extraem os dados necessários (params, body, user) e delegam ao Service correspondente. Não contêm lógica de negócio.
src/services/)Contêm toda a lógica de negócio. São chamados pelos controllers. Podem chamar Models (Objection) ou clients externos (Qdrant, Ollama).
src/models/)Modelos Objection.js. Mapeiam as tabelas do MySQL e definem relacionamentos.
| Model | Tabela |
|---|---|
Usuario.model.js |
usuarios |
RefreshToken.model.js |
refresh_tokens |
Conversation.model.js |
conversations |
Message.model.js |
messages |
src/middleware/)| Arquivo | Função |
|---|---|
Validajwt.js |
Verifica e decodifica o JWT do header Authorization |
RequireUser.js |
Exige usuário autenticado (qualquer nível) |
RequireAdmin.js |
Exige nível "3" (administrador) |
Validate.js |
Valida body/params/query com schema zod (schema.parse()) |
ParseId.js |
Converte req.params.id para número e valida |
RateLimit.js |
Rate limiting por IP |
ErrorHandler.js |
Captura erros e formata a resposta de erro |
src/middleware/schemas/)Schemas zod usados com o middleware Validate (que chama schema.parse()). Um arquivo por domínio que exige validação:
| Arquivo | Domínio |
|---|---|
Chat.Schema.js |
Corpo do chat |
Ingest.Schema.js |
Corpo do ingest |
Search.Schema.js |
Corpo da busca |
Usuario.Schema.js |
Criar, atualizar e alterar senha de usuário |
Todo domínio segue a convenção de nomenclatura abaixo:
src/
├── routes/ NomeDoModulo.Rotas.js
├── controllers/ NomeDoModulo.Controller.js
├── services/ nomeDoModuloService.js
├── models/ NomeDoModulo.model.js (se houver tabela)
└── middleware/
└── schemas/ NomeDoModulo.Schema.js (se houver validação)
#*)O package.json define um alias de importação para evitar caminhos relativos longos:
"imports": {
"#*": "./src/*"
}
Como ficaria:
import { UsuarioController } from "#controllers/Usuario.Controller.js";
import { db } from "#config/db.config.js";
Estado atual: o alias está definido e funciona, mas ainda não é usado em nenhum arquivo do
src/— todo o código usa imports relativos (../config/db.config.js). Foi adicionado na refatoração para espelhar o@dominha_back, mas a migração dos imports não foi feita.
| Domínio | Prefix da rota | Descrição |
|---|---|---|
| Auth | /api/auth |
Login, logout, refresh de token |
| Chat | /api/chat |
Envio de mensagens ao LLM |
| Conversations | /api/conversations |
CRUD de conversas persistidas |
| Documents | /api/documents |
Listagem e exclusão de documentos |
| Ingest | /api/ingest |
Ingestão de conteúdo na base RAG |
| Search | /api/search |
Busca semântica nos documentos |
| Usuario | /api/users |
CRUD de usuários do sistema |
GET /health — retorna o status de todos os serviços:
{
"ok": true,
"checks": {
"api": true,
"db": true,
"qdrant": true,
"ollama": true
}
}