Service Layer no Node.js é uma camada que coordena casos de uso da aplicação. Ela recebe dados já interpretados pela entrada HTTP, aplica regras, chama repositories, publica eventos e define a transação necessária para concluir uma operação.
Controllers devem lidar com protocolo. Repositories devem lidar com persistência. A Service Layer conecta essas partes sem depender de detalhes de rota, banco ou framework.
Neste guia, você aprenderá responsabilidades, transações, validação, erros, idempotência, testes e boas práticas.
Controller com regra misturada
app.post('/users', async (req, res) => {
if (!req.body.email) return res.status(400).send();
const exists = await db.query('SELECT 1 FROM users WHERE email = $1', [req.body.email]);
if (exists.rowCount) return res.status(409).send();
const user = await db.query('INSERT ...');
await queue.publish('user.created', user.rows[0]);
res.status(201).send(user.rows[0]);
});A rota conhece validação, SQL, evento e regra de unicidade.
Extraindo o caso de uso
export function criarUsuarioService({ repository, eventBus, idGenerator, clock }) {
return {
async criar(input) {
const exists = await repository.existeEmail(input.email);
if (exists) throw new EmailAlreadyUsedError(input.email);
const user = {
id: idGenerator.next(),
email: input.email,
createdAt: clock.now()
};
await repository.salvar(user);
await eventBus.publicar({ type: 'user.created', data: user });
return user;
}
};
}Controller fino
app.post('/users', async (req, res) => {
try {
const user = await userService.criar(req.body);
res.status(201).json(user);
} catch (error) {
responderErro(error, res);
}
});O controller traduz HTTP para chamada de aplicação e traduz resultado para HTTP.
Validação de entrada
Validação estrutural deve ocorrer na fronteira: tipos, campos obrigatórios e limites. Regras de negócio permanecem no serviço.
Use JSON Schema conforme JSON Schema no Node.js.
Regras de negócio
Exemplos adequados para a Service Layer:
- verificar se ação é permitida;
- calcular transição de estado;
- coordenar múltiplos repositories;
- aplicar idempotência;
- publicar evento após commit;
- selecionar estratégia de negócio.
Transações
await database.transaction(async (tx) => {
const users = criarUsuarioRepository(tx);
const outbox = criarOutboxRepository(tx);
await users.salvar(user);
await outbox.adicionar({ type: 'user.created', data: user });
});O serviço coordena a unidade atômica. Repositories recebem a conexão transacional.
Outbox
Publicar evento diretamente depois do commit pode falhar e deixar o sistema inconsistente. O padrão outbox grava o evento na mesma transação e um worker publica depois.
Idempotência
Casos de uso expostos a retries precisam reconhecer repetição. Veja Idempotency Keys no Node.js.
Erros do domínio
class EmailAlreadyUsedError extends Error {
code = 'EMAIL_ALREADY_USED';
}O serviço lança erros estáveis. A camada HTTP decide se o código vira 409, 422 ou outro status.
Não retorne objetos HTTP
Evite retornar { statusCode, body } do serviço. Isso acopla o caso de uso ao protocolo e dificulta reutilização por CLI, fila ou GraphQL.
Dependências
Injete repositories, gateways, clock e logger conforme Dependency Injection no Node.js.
Repository
A persistência deve ser acessada por operações orientadas ao domínio. Veja Repository Pattern no Node.js.
Serviços pequenos
Não crie uma classe com cinquenta métodos para todo o módulo. Organize por caso de uso quando responsabilidades divergem:
criarUsuario
ativarUsuario
bloquearUsuario
alterarEmailQuery services
Leituras complexas podem usar serviços de consulta separados, retornando DTOs otimizados sem reconstruir entidades completas.
Logs
O serviço pode registrar eventos relevantes com contexto, mas não deve repetir cada log de infraestrutura. Use códigos e IDs, nunca senhas ou tokens.
Testes unitários
test('rejeita email existente', async () => {
const repository = {
async existeEmail() { return true; }
};
const service = criarUsuarioService({ repository, eventBus, idGenerator, clock });
await assert.rejects(
() => service.criar({ email: 'a@b.com' }),
{ code: 'EMAIL_ALREADY_USED' }
);
});Testes de integração
Verifique transações, constraints e outbox com banco real. Consulte Testes de Integração no Node.js.
Erros comuns
- service apenas repassa chamada ao repository;
- service conhece request e response;
- classe gigante;
- transação aberta dentro de cada repository;
- evento publicado antes do commit;
- dependências globais;
- misturar validação estrutural e regra;
- capturar todos os erros.
Checklist
- casos de uso claros;
- controllers finos;
- transações coordenadas;
- erros estáveis;
- dependências injetadas;
- sem detalhes HTTP;
- idempotência definida;
- testes unitários e integrados.
Conclusão
Service Layer no Node.js concentra a coordenação dos casos de uso sem misturar protocolo e persistência. Ela oferece um ponto claro para regras, transações, eventos e autorização.
Mantenha serviços focados, com dependências explícitas e erros estáveis. Uma camada útil representa ações do negócio, não apenas métodos genéricos sobre tabelas.
Consulte o catálogo Service Layer de Martin Fowler e a documentação de testes do Node.js.




