Suggest an editImprove this articleRefine the answer for “Business logic and controllers”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)A controller should be thin - it takes input, calls the right service/use-case, and builds the response; all business logic moves down into layers: services (the application layer), the domain layer, repositories, schema validation, authorization/policies, mappers/DTOs, while cross-cutting concerns (auth, logging, rate limiting) go into middleware/infrastructure. **Key point:** integrations and side effects (external APIs, email/SMS, queues) also belong in the infrastructure layer and get called from use-cases, not directly from the controller.Shown above the full answer for quick recall.Answer (EN)ImageIn short: **a controller should be thin** - it takes input, calls the right "use-case/service", and builds the response. **All business logic moves down into lower layers.** Here's exactly where: ## Where to move the logic 1. **Services / Use-cases (Application layer)** - What lives here: orchestrating scenarios, transactions, repository calls, integrations, high-level domain checks. - Controller → `userService.createUser(dto)` → a result/exception. - Named by action: `CreateUser`, `ChangeEmail`, `PlaceOrder`. 2. **The Domain layer** - **Entities/Value Objects** with invariants and business rules (no dependency on Express/the DB). - Example: `User.changeEmail()` enforces the rule "can't change the email more than N times". 3. **Repositories / DAOs (Data access layer)** - Encapsulate DB/ORM access (Prisma, Sequelize, TypeORM), holding **only** CRUD and simple queries. - Interfaces live in the domain, implementations in the infrastructure. 4. **Schema validation (Validation layer)** - Input/output schemas: `zod/joi/express-validator`. - Run before the controller (middleware) or inside a use-case (if it depends on domain rules). - The controller stays free of if/else trees. 5. **Authorization/policies (Policy/ACL)** - Access rules: `canUpdateProfile(user, target)`. - Keep them separate from the controller (policy functions or Casbin/Oso). 6. **Mappers/DTOs and formatting** - Conversion between layers: request → DTO → domain entity → DTO → response. - Prevents DB/ORM details from leaking out. 7. **Cross-cutting concerns go into middleware/infrastructure** - Authentication, logging, tracing, rate limiting, caching, CSRF, CORS, **not in the controller**. 8. **Integrations and side effects** - External API clients, email/SMS, queues/jobs (BullMQ/RabbitMQ), cache (Redis), all in the infrastructure layer, called from use-cases. ## An example structure ```javascript src/ app.ts routes/ users.ts controllers/ users.controller.ts application/ # use-cases / services users/ create-user.usecase.ts change-email.usecase.ts domain/ users/ user.entity.ts email.vo.ts user.errors.ts user.repo.ts # repository interface infrastructure/ db/ prisma/ user.prisma-repo.ts # repository implementation http/ mailer.client.ts queues/ events.publisher.ts shared/ validation/ user.dto.ts (zod/joi) mapping/ user.mapper.ts policies/ user.policies.ts ```For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.