Getting Started with PRS Backend Development
This guide will help you set up your development environment and understand the basics of working with the PRS Backend codebase. For frontend development, see Getting Started with PRS Frontend Development.
Prerequisites
Before you begin, make sure you have the following installed:
- Node.js (v14 or later)
- npm (v6 or later)
- PostgreSQL (v12 or later)
- Git
Setting Up the Development Environment
1. Clone the Repository
| Bash |
|---|
| git clone <repository-url>
cd prs-backend
|
2. Install Dependencies
3. Set Up Environment Variables
Copy the example environment file and update it with your local configuration:
Edit the .env file with your database credentials and other configuration options.
4. Set Up the Database
Create a PostgreSQL database for the project:
Run the database migrations:
Seed the database with initial data:
5. Start the Development Server
The server will start on http://localhost:3000 by default.
Project Structure
The PRS Backend follows a layered architecture pattern:
| Text Only |
|---|
| src/
├── app/ # Application layer
│ ├── errors/ # Error classes and handlers
│ ├── handlers/ # Controllers and middlewares
│ └── services/ # Business logic services
├── domain/ # Domain layer
│ ├── constants/ # Domain constants and enums
│ └── entities/ # Domain entities and validation schemas
├── infra/ # Infrastructure layer
│ ├── database/ # Database configuration and models
│ ├── logs/ # Logging configuration
│ └── repositories/ # Data access repositories
├── interfaces/ # Interface layer
│ └── router/ # API routes
└── utils/ # Utility functions
|
Key Concepts
1. Dependency Injection
The PRS Backend uses Awilix for dependency injection. Services and repositories are registered in the container and injected into controllers and services.
| JavaScript |
|---|
| // src/container.js
const container = createContainer();
container.register({
// Repositories
requisitionRepository: asClass(RequisitionRepository),
// Services
requisitionService: asClass(RequisitionService),
// Controllers
requisitionController: asClass(RequisitionController),
});
|
2. Controllers
Controllers handle HTTP requests and delegate business logic to services:
| JavaScript |
|---|
| // src/app/handlers/controllers/requisitionController.js
class RequisitionController {
constructor({ requisitionService, db, utils, entities, constants, clientErrors }) {
this.requisitionService = requisitionService;
this.db = db;
this.utils = utils;
this.entities = entities;
this.constants = constants;
this.clientErrors = clientErrors;
}
async getAll(request, reply) {
try {
const requisitions = await this.requisitionService.getAll(request.query);
return reply.status(200).send(requisitions);
} catch (error) {
// Error handling
}
}
}
|
3. Services
Services implement business logic and orchestrate operations:
| JavaScript |
|---|
| // src/app/services/requisitionService.js
class RequisitionService extends BaseService {
constructor({ requisitionRepository, db, constants, clientErrors }) {
super({ repository: requisitionRepository });
this.requisitionRepository = requisitionRepository;
this.db = db;
this.constants = constants;
this.clientErrors = clientErrors;
}
async getAll(query) {
// Business logic
return this.requisitionRepository.findAll(query);
}
}
|
4. Repositories
Repositories handle data access and persistence:
| JavaScript |
|---|
| // src/infra/repositories/requisitionRepository.js
class RequisitionRepository extends BaseRepository {
constructor({ db }) {
super(db.requisitionModel);
this.db = db;
}
async findAll(query) {
// Data access logic
return super.findAll(query);
}
}
|
5. Domain Entities
Domain entities define validation schemas and basic business rules:
| JavaScript |
|---|
| // src/domain/entities/requisitionEntity.js
const createRequisitionSchema = z
.object({
isDraft: z.enum(['true']).transform(value => value === 'true'),
type: z.enum(REQUISITION_REQUEST_TYPES),
companyId: createNumberSchema('Company ID'),
// ... other fields
})
.strict();
|
Common Development Tasks
1. Creating a New API Endpoint
To create a new API endpoint, you need to:
- Create or update a controller method
- Create or update a service method
- Create or update a repository method (if needed)
- Add a route in the appropriate router file
Example:
| JavaScript |
|---|
| // 1. Controller method
async getRequisitionsByStatus(request, reply) {
try {
const { status } = request.params;
const requisitions = await this.requisitionService.getByStatus(status);
return reply.status(200).send(requisitions);
} catch (error) {
// Error handling
}
}
// 2. Service method
async getByStatus(status) {
return this.requisitionRepository.findAll({
where: { status },
});
}
// 3. Route
fastify.route({
method: 'GET',
url: '/status/:status',
handler: requisitionController.getRequisitionsByStatus.bind(requisitionController),
});
|
2. Adding Validation
To add validation to an API endpoint:
- Create a validation schema in the appropriate entity file
- Add the schema to the route definition
Example:
| JavaScript |
|---|
| // 1. Validation schema
const getRequisitionByStatusSchema = z
.object({
status: z.enum(Object.values(REQUISITION_STATUS)),
})
.strict();
// 2. Route with validation
fastify.route({
method: 'GET',
url: '/status/:status',
schema: {
params: getRequisitionByStatusSchema,
},
handler: requisitionController.getRequisitionsByStatus.bind(requisitionController),
});
|
3. Handling Transactions
To handle transactions:
| JavaScript |
|---|
| async createRequisition(request, reply) {
const transaction = await this.db.sequelize.transaction();
try {
const requisition = await this.requisitionService.create(request.body, transaction);
await transaction.commit();
return reply.status(201).send(requisition);
} catch (error) {
await transaction.rollback();
// Error handling
}
}
|
Debugging
1. Logging
The PRS Backend uses Pino for logging. You can add logs to your code:
| JavaScript |
|---|
| request.log.info({ data }, 'Message');
request.log.error({ error }, 'Error message');
|
2. Debugging with VS Code
You can debug the application using VS Code's built-in debugger. Create a .vscode/launch.json file:
| JSON |
|---|
| {
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/index.js",
"env": {
"NODE_ENV": "development"
}
}
]
}
|
Additional Resources