Development Guide - EML Backend
1. Introduction
This document provides details about the code structure, execution flow, design patterns, and best practices used in EML Backend. Its goal is to facilitate code understanding and serve as a reference for new developers joining the team.
2. Project Structure
The project follows a NestJS-based structure, organized as follows:
EML-Backend/
│── src/
│ ├── modules/ # Main system modules
│ │ ├── auth/ # Authentication module
│ │ ├── users/ # User management
│ │ ├── calls/ # Call management
│ │ ├── messages/ # Message management
│ │ ├── notifications/ # Notification system
│ │ ├── reports/ # Report generation
│ ├── common/ # Common utilities and decorators
│ ├── config/ # Environment variable configuration
│ ├── main.ts # Application entry point
│── test/ # Unit and integration tests
│── docker-compose.yml # Docker environment configuration
│── package.json # Project dependencies
│── README.md # Main documentation
Each module in src/modules/ follows this structure:
modules/
├── moduleName/
│ ├── moduleName.module.ts # Module definition
│ ├── moduleName.controller.ts # Controller handling routes
│ ├── moduleName.service.ts # Business logic
│ ├── dto/ # Data Transfer Object definitions
│ ├── entities/ # Model or entity definitions
3. Execution Flows
3.1 Authentication Flow
- The user sends credentials to
POST /auth/login. - The
AuthControllerreceives and validates the request. - The database is queried via
UserService. - If credentials are correct, a JWT token is generated.
- The response returns the generated token.
@Post('login')
async login(@Body() loginDto: LoginDto) {
return this.authService.validateUser(loginDto);
}
3.2 Message Sending Flow
- A request is received at
POST /messages/sendwith a recipient and message. - The
MessageServicestores the message in the database. - An event is sent to RabbitMQ for asynchronous processing.
- A consumer in the Notification Service processes the message and sends it to an external API.
@Post('send')
async sendMessage(@Body() messageDto: SendMessageDto) {
return this.messageService.processMessage(messageDto);
}
4. Design Patterns Used
4.1 Dependency Injection
NestJS dependency injection is used to facilitate module management.
@Injectable()
export class UserService {
constructor(private readonly userRepository: UserRepository) {}
}
4.2 Repository Pattern
TypeORM is used with a Repository pattern for database access:
@EntityRepository(User)
export class UserRepository extends Repository<User> {
async findByEmail(email: string): Promise<User | null> {
return this.findOne({ where: { email } });
}
}
4.3 Event-Driven Architecture (RabbitMQ)
RabbitMQ is used for microservice communication without direct dependencies.
@RabbitSubscribe({ exchange: 'notifications', routingKey: 'message.sent', queue: 'notifications_queue' })
async handleMessage(payload: MessagePayloadDto) {
console.log(`Message received: ${payload.message}`);
}
5. Configuration and Environment Variables
The .env file defines sensitive configurations:
PORT=3000
DATABASE_URL=postgresql://user:password@localhost:5432/eml_db
JWT_SECRET=supersecret
RABBITMQ_URL=amqp://rabbitmq:5672
Loaded in the code using ConfigService:
@Injectable()
export class ConfigService {
constructor(private readonly config: ConfigModule) {}
get databaseUrl(): string {
return this.config.get('DATABASE_URL');
}
}
6. Testing and Coverage
Unit and integration tests are implemented using Jest.
6.1 User Service Unit Test
describe('UserService', () => {
let service: UserService;
let repository: UserRepository;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [UserService, { provide: UserRepository, useValue: mockRepository }],
}).compile();
service = module.get<UserService>(UserService);
repository = module.get<UserRepository>(UserRepository);
});
it('should find a user by email', async () => {
repository.findByEmail.mockResolvedValue(mockUser);
const user = await service.getUserByEmail('[email protected]');
expect(user).toEqual(mockUser);
});
});
6.2 API Gateway Integration Test
describe('AuthController (e2e)', () => {
it('/auth/login (POST)', () => {
return request(app.getHttpServer())
.post('/auth/login')
.send({ email: '[email protected]', password: '12345' })
.expect(200)
.expect(res => {
expect(res.body).toHaveProperty('accessToken');
});
});
});
7. Conclusion
This Development Guide provides a detailed overview of the code, execution flows, and best practices in EML Backend. For further information, reviewing the documentation of each module is recommended.
🚀 This document will be updated as the system evolves.