Modules
Harpia’s architecture is heavily based on Modules. Each module represents a specific domain or functionality within your application (e.g., users, auth, telemetry), promoting the separation of concerns and making the project easier to maintain and scale.
Module Structure
A typical module in Harpia resides in the modules/<module-name> directory and generally contains the following subdirectories and files:
modules/users/
│
├── controllers/ # Handles incoming HTTP requests and returns responses
├── services/ # Contains the core business logic of the application
├── repositories/ # Responsible for direct communication with the database
├── validations/ # Validation schemas (Zod) for incoming data
├── tests/ # Local tests (E2E or unit) for the module
└── users.routes.ts # Route definition file
1. Controllers
Controllers serve as the entry point for HTTP requests. They should not contain complex business logic. Their primary responsibility is to extract data from the request (body, params, query), call the appropriate service, and format the response for the client.
2. Services
Services contain the core logic of your application. If you need to calculate something, verify advanced permissions, or orchestrate multiple database calls, do it within the service. Services are independent of the HTTP protocol.
3. Repositories
The Repository pattern abstracts communication with Prisma (or any other database). Instead of calling Prisma directly within the service, you inject or instantiate the repository. This facilitates unit testing (mocking) and future database migrations.
4. Route File (*.routes.ts)
Harpia features an Auto-Discovery Route system. Any file ending in .routes.ts within the app/modules/ directory will be automatically mapped and imported by start/routes.ts.
Route Definition Example:
import { Router } from "@harpiats/core";
import { userController } from "./controllers";
import { isAuthenticated } from "app/middlewares/auth";
// Creates a new Router instance
const userRoutes = new Router("/users");
// Defines routes (with and without middleware)
userRoutes.get("/", userController.index);
userRoutes.get("/:id", userController.show);
userRoutes.post("/", isAuthenticated, userController.create);
// It is mandatory to export the routes instance!
export { userRoutes };
Avoiding Naming Conflicts
Notice that the instantiated router variable takes the module’s name (
userRoutes) instead of a generic term likeroutes. When generating new modules via the CLI, Harpia will automatically create instances with their respective names (e.g.,postRoutes,orderRoutes, etc.). This is natively done by design to prevent collisions and variable conflicts at runtime when the Auto-Discovery imports files from different modules.
Benefits of the Modular Approach
- Encapsulation: All logic pertaining to “Users” is kept in a single, dedicated location.
- Auto-Discovery: The framework is responsible for finding and mounting the routes at startup, provided they follow the
*.routes.tsnaming convention. - Decoupling: Services and Repositories can be easily imported by other modules should they need to communicate.