A microservice built with NestJS and MySQL for handling delayed actions (scheduled or deferred tasks).
The service is containerized with Docker.
- Features
- Getting Started
- Environment Variables
- Running the Service
- API Documentation
- Database
- Project Structure
- Scripts
- Documentation
- Contributing
- Code of Conduct
- Support
- License
- ⚡ Built with NestJS
- 🗄️ MySQL database with Docker volume persistence
- 🐳 Ready-to-use Docker & Docker Compose setup
- 🔒 Environment-based configuration with
.envfiles - 📖 Integrated Swagger API documentation
- ✅ Supports automatic database migrations
- 🐇 RabbitMQ messaging integration for microservices
- 🌐 WebSocket support for real-time communication
- ⏱️ Task scheduling with NestJS Schedule module
- 🌍 Internationalization (i18n) support
- 🧪 Structured testing setup with Jest and Supertest
- 🖊️ Code formatting and linting with Prettier & ESLint
- Docker
- Docker Compose
- Node.js (if running locally)
- pnpm (recommended)
git clone git@github.com:jahidhiron/nestjs-template.git
cd nestjs-templateBelow is an example of the .env file used on the local machine for the NestJS Template Project. You may refer to the .env.local.example file for guidance.
# App
PORT=8080
APPLICATION_MODE=development
API_BASE_URL="http://localhost:8080"
# DB
DATABASE_URL=mysql://root:123456@localhost:3306/template_db
MIGRATIONS_RUN=false
# Swagger
ENABLE_SWAGGER_PROTECTION=true
SWAGGER_USER=swagger-admin
SWAGGER_PASSWORD=Pass1234?
# RabbitMQ
ENABLE_RABBITMQ=true
RABBITMQ_URI=amqp://guest:guest@localhost:5672
RABBITMQ_MANAGEMENT_UI_PORT=15672
RABBITMQ_QUEUE=nest_template_queue
# Realtime
CLIENT_SOCKET_URL="http://localhost:8080"Below is an example of the .env file used on the Docker for the NestJS Template Project. You may refer to the .env.docker.example file for guidance.
# App
PORT=8080
APPLICATION_MODE=production
API_BASE_URL="http://localhost:8080"
# API container
API_CONTAINER_NAME=template-api
# DB
MYSQL_CONTAINER_NAME=mysql
MYSQL_ROOT_PASSWORD=root
MYSQL_DATABASE=template_db
MYSQL_USERNAME=app_user
MYSQL_PASSWORD=Pass1234@
MIGRATIONS_RUN=true
# Swagger
ENABLE_SWAGGER_PROTECTION=true
SWAGGER_USER=swagger-admin
SWAGGER_PASSWORD=Pass1234?
# RabbitMQ
ENABLE_RABBITMQ=true
RABBITMQ_CONTAINER_NAME=rabbitmq
RABBITMQ_DEFAULT_USER=admin
RABBITMQ_DEFAULT_PASS=Pass1234!
RABBITMQ_QUEUE=nest_template_queue
RABBITMQ_MANAGEMENT_UI_PORT=15672
# Realtime
CLIENT_SOCKET_URL="http://localhost:8080"Below is an example of the .env.example file for the NestJS Template Project. Please refer to the .env.example file for guidance.
# App
PORT=8080
APPLICATION_MODE=production
API_BASE_URL="http://localhost:8080"
# If you are using docker
API_CONTAINER_NAME=template-api
# DB
# If using a MySQL database as a Docker container, configure the following variables
MYSQL_CONTAINER_NAME=mysql
MYSQL_ROOT_PASSWORD=root
MYSQL_DATABASE=template_db
MYSQL_USERNAME=app_user
MYSQL_PASSWORD=root
# If using a local database or a cloud-based database, set the DATABASE_URL accordingly
DATABASE_URL=mysql://root:123456@localhost:3306/template_db
# This setting is common for both Docker and direct database URL configurations.
# Set MIGRATIONS_RUN to true if you want to automatically run database migrations.
MIGRATIONS_RUN=true
# Swagger
ENABLE_SWAGGER_PROTECTION=true
SWAGGER_USER=swagger-admin
SWAGGER_PASSWORD=Pass1234?
# RabbitMQ
# If using a RabbitMQ as a Docker container, configure the following variables
RABBITMQ_CONTAINER_NAME=rabbitmq
RABBITMQ_DEFAULT_USER=admin
RABBITMQ_DEFAULT_PASS=admin
# If using a local RabbitMQ or a cloud-based RabbitMQ, set the RABBITMQ_URI accordingly
RABBITMQ_URI=amqp://admin:admin@localhost:5672
# These settings are common for both Docker and direct configurations.
ENABLE_RABBITMQ=true
RABBITMQ_QUEUE=nest_template_queue
RABBITMQ_MANAGEMENT_UI_PORT=15672
# Realtime
CLIENT_SOCKET_URL="http://localhost:8080"
Important: Ensure that you create a
.envfile in the root directory and use either the.env.local.exampleor.env.docker.exampleas a template.
The NestJS Teamplate can be run locally for development or in a containerized production environment using Docker Compose.
pnpm run docker:uppnpm run docker:downpnpm run start:devThe NestJS Teamplate uses Swagger to provide interactive API documentation.
Swagger allows developers to explore and test endpoints directly from the browser.
Once the service is running (locally or in Docker), you can access Swagger UI at:
http://localhost:8080/apiExample for .env:
PORT=8080For production or staging environments, replace localhost:8080 with the corresponding API base URL.
Example:
https://api.dev.example.com/apiThe NestJS Teamplate uses MySQL as its primary database.
Database configuration is managed via the .env file, and data persistence is handled through Docker volumes when running in containers.
The database connection is configured using environment variables in .env:
| ---------------- | ------------------------------------------------------------------------------------------------------- |
| `MYSQL_HOST` | MySQL host (e.g., `mysql-db` for Docker or `localhost` for a local database) |
| `MYSQL_PORT` | MySQL port (default: `3306`) |
| `MYSQL_USERNAME` | MySQL username |
| `MYSQL_PASSWORD` | MySQL password |
| `MYSQL_DATABASE` | Database name |
| `MIGRATIONS_RUN` | Whether to run migrations automatically (`true`/`false`) |
| `DATABASE_URL` | You can use a database URL directly instead of specifying host, port, username, and password separately |
Example:
MYSQL_HOST=mysql-db
MYSQL_PORT=3306
MYSQL_USERNAME=myuser
MYSQL_PASSWORD=mysecretpassword
MYSQL_DATABASE=delayed_action_db
MIGRATIONS_RUN=true.
├── dist/
├── logs/
├── src/ # All application source code
│ ├── common/ # Cross‑cutting utilities
│ │ ├── constants/
│ │ ├── decorators/
│ │ ├── dtos/
│ │ ├── entities/
│ │ ├── enums/
│ │ ├── filters/
│ │ ├── helpers/
│ │ ├── helpers/
│ │ ├── interceptors/
│ │ ├── interfaces/
│ │ ├── middlewares/
│ │ ├── pipes/
│ │ ├── logger/
│ │ ├── pipes/
│ │ ├── providers/
│ │ ├── repositories/
│ │ ├── swagger/
│ │ ├── utils/
│ ├── config/ # Centralised configuration
│ │ ├── app/
│ │ ├── cors/
│ │ ├── db/
│ │ ├── i18n/
│ │ ├── logger/
│ │ ├── rabbitmq/
│ │ ├── realtime/
│ │ ├── swagger/
│ │ ├── config.module.ts
│ │ ├── config.service.ts
│ │ └── index.ts
│ ├── cron/
│ │ ├── services/ # Cron scheduler
│ │ │ ├── update-profile.service.ts
│ │ │ ├── index.ts
│ │ └── cron.module.ts
│ ├── db/ # Database Configuration
│ │ ├── config/
│ │ │ ├── base-database.config.ts
│ │ │ ├── database.config
│ │ │ ├── naming-strategy.config.ts
│ │ │ └── index.ts
│ │ ├── interfaces/
│ │ ├── migrations/
│ │ └── database.module.ts
│ ├── modules/ # Feature modules containing business logic
│ │ └── app/
│ │ └── healths/
│ │ └── projects/
│ │ ├── dtos/
│ │ ├── entities/
│ │ ├── enums/
│ │ ├── i18n/
│ │ ├── providers/
│ │ ├── repositories/
│ │ ├── swaggers/
│ │ ├── project.controller.ts
│ │ ├── project.service.ts
│ │ └── project.module.ts
│ ├── rabbitmq/
│ │ ├── constants/
│ │ ├── consumers/
│ │ └── producers/
│ │ └── rabitmq.module.ts
│ ├── realtime/
│ │ ├── adapters/
│ │ ├── gateways/
│ │ └── interfaces/
│ │ └── services/
│ │ └── types/
│ │ └── realtime.module.ts
│ ├── shared/
│ │ ├── hash/
│ │ ├── http-client/
│ │ └── responses/
│ │ └── shared.module.ts
│ └── main.ts
├── test/
├── .env
├── .env.example
├── .gitattributes
├── .gitignore
├── ..prettierrc
├── docker-compose.yaml
├── eslint.config.mjs
├── nest-cli.json
├── package.json
├── README.md
├── tsconfig.build.json
└── tsconfig.json
| Script | Description |
| -------------------- | ---------------------------------------------------------- |
| `clean` | Remove the `dist` directory. |
| `build` | Clean and build the project. |
| `format` | Format source files using Prettier. |
| `start:dev` | Start the application in development mode with watch mode. |
| `start:debug` | Start the application in debug mode with watch mode. |
| `start` | Start the compiled application. |
| `docker:up` | Build and start containers using Docker Compose. |
| `docker:down` | Stop and remove containers using Docker Compose. |
| `migration:create` | Create a new TypeORM migration. |
| `migration:generate` | Generate a new TypeORM migration. |
| `migration:run` | Run pending migrations. |
| `migration:revert` | Revert the last executed migration. |
| `lint` | Run ESLint with auto-fix. |
| `test` | Run unit tests. |
| `test:watch` | Run tests in watch mode. |
| `test:cov` | Run tests and generate coverage report. |
| `test:debug` | Run tests in debug mode. |
| `test:e2e` | Run end-to-end tests. |
This documentation covers the BaseRepository, AppLogger, Response Format, Swagger, RabbitMQ, Cron Job, Web Socket, i18n internationalization, and Application Configuration, along with their common methods and usage examples.
It provides guidance on performing CRUD operations, handling pagination, executing raw queries, and using transactions effectively.
Each method includes a brief description and a practical example to help developers quickly understand how to use these components in real projects.
The BaseRepository is a generic TypeORM repository that provides reusable CRUD operations, advanced query building, pagination, and raw query execution with integrated error handling and logging.
Features
⚡ Generic CRUD operations (create, createMany, findOne, update, updateMany, remove, removeMany)
🗄️ Advanced query builder with support for relations, search, sorting, pagination, and field selection
🐳 Execute raw SQL queries
🔒 Integrated error handling via ErrorResponse
📖 Logging using AppLogger
The BaseRepository provides the following reusable methods:
| Method | Description |
| ----------------------------------------- | ----------------------------------------------------------- |
| `create(data, manager?)` | Create a single entity |
| `createMany(data[], manager?)` | Create multiple entities |
| `findOne(where, options?, manager?)` | Find a single entity |
| `update(query, data, manager?)` | Update a single entity |
| `updateMany(query, data, manager?)` | Update multiple entities |
| `remove(query, manager?)` | Remove a single entity |
| `removeMany(query, manager?)` | Remove multiple entities |
| `list(params?)` | List entities without pagination |
| `paginatedList(params?)` | List entities with pagination |
| `rawQuery(query, parameters?, manager?)` | Execute raw SQL queries |
Creates a single entity in the database.
Example Usage:
const project = await projectRepository.create({ title: 'New Project' });Creates multiple entities in a single call.
Example Usage:
await taskRepository.createMany([{ title: 'Task 1' }, { title: 'Task 2' }]);Finds a single entity matching the query.
Example Usage:
await taskRepository.createMany([{ title: 'Task 1' }, { title: 'Task 2' }]);Updates a single entity matching the query.
Example Usage:
await projectRepository.update({ id: 1 }, { title: 'Updated Title' });Updates multiple entities matching the query.
Example Usage:
await taskRepository.updateMany({ projectId: 1 }, { status: 'done' });Deletes a single entity matching the query.
Example Usage:
await projectRepository.remove({ id: 1 });Deletes multiple entities matching the query.
Example Usage:
await taskRepository.removeMany({ projectId: 1 });Retrieves a paginated list of entities with optional search and sorting.
Example Usage:
const { q, page, limit, sortBy, projectId } = dto;
// Build the query object
const query: FindOptionsWhere<TaskEntity> = {};
if (projectId) {
query.project = { id: projectId }; // Filter tasks by projectId
}
// Use BaseRepository's paginatedList method
const result = await this.taskRepository.paginatedList({
q, // Optional search query string
query, // TypeORM where condition
searchBy: ['title'], // Fields to search in (title)
page, // Pagination page number
limit, // Pagination limit
sortBy, // Sorting options [{ whom: 'createdAt', order: 'DESC' }]
relations: { project: true }, // Include related project entity
});
// Return data with meta information
return {
meta: result.meta, // Pagination info: total, pages, currentPage
tasks: result.items, // List of TaskEntity objects
};Retrieves all entities matching the query without pagination.
Example Usage:
const { q, sortBy, projectId } = dto;
// Build the query object
const query: FindOptionsWhere<TaskEntity> = {};
if (projectId) {
query.project = { id: projectId }; // Filter tasks by projectId
}
// Use BaseRepository's list method
const result = await this.taskRepository.list({
q, // Optional search query string
query, // TypeORM where condition
searchBy: ['title'], // Fields to search in (title)
sortBy, // Sorting options [{ whom: 'createdAt', order: 'DESC' }]
relations: { project: true }, // Include related project entity
});
// Return data
return {
tasks: result.items, // List of TaskEntity objects
};Executes raw SQL queries directly on the database. Useful for complex queries, aggregates, or joins.
Example Usage:
// Example: Fetch all projects with a search term and task count
const sql = `
SELECT p.id, p.title, COUNT(t.id) AS totalTasks
FROM projects p
LEFT JOIN tasks t ON t.project_id = p.id
WHERE p.title LIKE ?
GROUP BY p.id
`;
const params = ['%My Project%'];
// Execute raw SQL via repository
const projects = await projectRepository.rawQuery(sql, params);
console.log(projects);
/*
[
{ id: 1, title: 'My Project 1', totalTasks: 5 },
{ id: 2, title: 'My Project 2', totalTasks: 3 }
]
*/You can perform multiple repository operations in a single transaction using EntityManager.
If any step fails, all changes are rolled back automatically.
Example Usage:
const result = await dataSource.transaction(async (manager: EntityManager) => {
// Create a project
const project = await projectRepository.create({ title: 'New Project' }, manager);
// Create profile linked to the project
await profileRepository.create({ bio: 'Project Bio', project }, manager);
// Create multiple tasks linked to the project
await taskRepository.createMany(
[
{ title: 'Task 1', project },
{ title: 'Task 2', project },
],
manager,
);
// Fetch and return the full project with profile and tasks
return projectRepository.findOne(
{ id: project.id },
{ relations: { profile: true, tasks: true } },
manager,
);
});
console.log(result);
/*
{
id: 1,
title: 'New Project',
profile: { id: 1, bio: 'Project Bio' },
tasks: [
{ id: 1, title: 'Task 1' },
{ id: 2, title: 'Task 2' }
]
}
*/Using BaseRepository provides several advantages for building scalable and maintainable applications:
-
Centralized Common Methods
All CRUD operations, listing, and raw query execution are centralized. You don’t need to write repetitive repository code for each entity. -
Reusability Across Modules
Methods likecreate,update,remove,paginatedList, andlistcan be used across all modules, ensuring consistency and reducing boilerplate code. -
Advanced Pagination & Filtering
ThepaginatedListmethod helps easily implement pagination with search, multi-directional sorting, and filtering out-of-the-box. This saves significant development time for common list endpoints. -
Flexible Querying
Supports advanced queries with relations, dynamic search fields, and custom TypeORMwhereconditions. -
Raw SQL Execution
TherawQuerymethod allows executing complex queries or aggregations directly in SQL when standard ORM methods are insufficient. -
Transaction Support
All methods supportEntityManager, allowing multiple operations to be executed within a single transaction, ensuring data consistency. -
Consistency & Best Practices
UsingBaseRepositoryensures that all database interactions follow the same pattern, including error handling and logging (viaErrorResponseandAppLogger). -
Rapid Development
Developers can focus on business logic rather than implementing repetitive repository methods, speeding up development and reducing bugs. -
Easily Extendable
You can extendBaseRepositoryfor custom entity-specific methods without breaking existing functionality. -
Improved Maintainability
Centralized repository logic makes maintenance easier, as updates or bug fixes in the base methods automatically apply across all entities using it.
The project uses SuccessResponse and ErrorResponse services to standardize API responses with i18n support and HTTP status codes.
| Method | HTTP Status | Description |
| ----------------------- | ------------------------- | ---------------------------------------- |
| `ok()` | 200 OK | Generic success response |
| `created()` | 201 Created | When a new resource is created |
| `accepted()` | 202 Accepted | When request is accepted for processing |
| `noContent()` | 204 No Content | When action succeeds but returns no data |
| `badRequest()` | 400 Bad Request | Invalid input or request |
| `unauthorized()` | 401 Unauthorized | Unauthorized access |
| `forbidden()` | 403 Forbidden | Access forbidden |
| `notFound()` | 404 Not Found | Resource not found |
| `conflict()` | 409 Conflict | Conflict with existing resource |
| `tooManyRequests()` | 429 Too Many Requests | Rate limit exceeded |
| `requestTimeout()` | 408 Request Timeout | Request took too long |
| `internalServerError()` | 500 Internal Server Error | Server-side error |
| `serviceUnavailable()` | 503 Service Unavailable | Server temporarily unavailable |
- Centralized CRUD operations via BaseRepository
- Pagination, search, filtering, and multi-directional sorting support
- Raw SQL execution for complex queries
- Standardized success and error responses
- i18n support for messages based on request headers
- Automatic and consistent HTTP status codes for all responses
return this.successResponse.created({
module: 'project',
key: 'create-project', // i18n key: "project.success.create-project"
...project,
});{
"method": "POST",
"success": true,
"status": "CREATED",
"statusCode": 201,
"path": "/project",
"timestamp": "2025-11-19T10:00:00.000Z",
"message": "Project created successful",
"data": {
"id": 1,
"title": "New Project"
}
}
The key (create-project) is automatically looked up in the en.json file:
"success": {
"create-project": "Project created successful"
}
return this.errorResponse.badRequest({
module: 'project',
key: 'project-already-exist', // i18n key: "project.error.project-already-exist"
});{
"method": "POST",
"success": false,
"status": "BAD_REQUEST",
"statusCode": 400,
"path": "/project",
"timestamp": "2025-11-19T10:05:00.000Z",
"message": "Project title already exist"
}
The key (project-already-exist) is automatically looked up in the en.json file:
"error": {
"project-already-exist": "Project title already exist"
}
Custom Logger (AppLogger)
The AppLogger is a centralized logging service built on Winston and integrated into NestJS. It provides structured, leveled logging for your application with automatic file rotation and environment-based formatting.
Key Features
-
Centralized logger for the entire application Supports multiple log levels: log, error, warn, debug, verbose
-
Automatically stores error logs in daily rotated files
-
Keeps the last 14 days of error logs
-
Console logs formatted for development with colorized, readable output
-
Only logs debug/verbose messages in non-production environments
| Method | Log Level | Description |
| ----------- | --------- | ---------------------------------------------------- |
| `log()` | INFO | Logs general information |
| `error()` | ERROR | Logs errors with optional stack trace |
| `warn()` | WARN | Logs warnings |
| `debug()` | DEBUG | Logs debug messages in non-production environments |
| `verbose()` | VERBOSE | Logs verbose messages in non-production environments |
try {
await this.dataSource.query('SELECT 1');
} catch (err: unknown) {
this.logger.error(
'Database query failed',
err instanceof Error ? err.stack : String(err),
'DbHealthProvider'
);
}
- The error is logged to console (if not in production)
- A structured JSON error log is stored in the daily rotated file:
/logs/error-2025-11-19.log
{
"level": "error",
"message": "Database query failed",
"stack": "Error: ...",
"context": "DbHealthProvider",
"timestamp": "2025-11-19 10:15:00"
}
This project uses NestJS Swagger to generate API documentation automatically. It provides descriptions, request/response schemas, and example responses for each endpoint, including error and success cases.
Each endpoint in the controller is decorated with custom Swagger decorators to automatically generate API documentation.
Example:
@Post()
@CreateProjectSwaggerDocs()
async create(@Body() dto: CreateProjectDto) {
// controller logic
}This example demonstrates how to use the custom Swagger helper CreateProjectSwaggerDocs to document the Create Project API endpoint.
It shows how to define the operation summary, request body, success response, and standard error responses with optional examples.
export function CreateProjectSwaggerDocs() {
return applyDecorators(
// Adds the operation summary and description in Swagger UI
ApiOperation({
summary: 'Create a new project', // Short description for Swagger endpoint
description: 'This endpoint allows creating a new project.' // Detailed explanation
}),
// Defines the request body schema for Swagger UI
ApiBody({
type: CreateProjectDto // The DTO that represents the structure of the request body
}),
// Defines the success response for the endpoint
SwaggerApiSuccessResponse(ProjectResponseDto, {
method: HttpMethod.POST, // HTTP method
status: HTTP_STATUS.CREATED.context, // Status text for Swagger UI
statusCode: HTTP_STATUS.CREATED.status,// HTTP status code (201)
path: ModuleName.Project, // Path/module for this endpoint
message: 'Project created successful', // Success message
}),
// Defines a Bad Request (400) response with examples
BadRequestResponse({
path: ModuleName.Project, // Path/module
method: HttpMethod.POST, // HTTP method
examples: {
// Example for validation error
validationError: {
summary: 'Validation Error', // Short description for Swagger UI
message: 'Validation Error', // Error message returned
errors: [{ field: 'title', message: 'Title should not be empty' }] // Field-level errors
},
// Example for duplicate project title
duplicateTitle: {
summary: 'Duplicate Project Title',
message: 'Project title already exist'
},
},
}),
// Defines Internal Server Error (500) response
InternalServerErrorResponse({
path: ModuleName.Project, // Path/module
method: HttpMethod.POST // HTTP method
})
);
}
This table lists the custom Swagger helpers used throughout the Project module.
These helpers standardize the API documentation by defining consistent success and error responses, including optional example payloads for better clarity in Swagger UI.
| Swagger Helper | HTTP Status / Type | Description |
| ----------------------------- | ------------------------- | -------------------------------------------------------------------------------------------------- |
| `SwaggerApiSuccessResponse` | 200 / 201 / custom | Defines successful response structure with optional examples. |
| `BadRequestResponse` | 400 Bad Request | Standard response for validation errors or bad requests, supports examples. |
| `UnauthorizedResponse` | 401 Unauthorized | Standard response for unauthorized access. |
| `ForbiddenResponse` | 403 Forbidden | Standard response for forbidden access. |
| `NotFoundResponse` | 404 Not Found | Standard response for resource not found. |
| `ConflictResponse` | 409 Conflict | Standard response for conflicts, e.g., duplicate entries. |
| `UnprocessableEntityResponse` | 422 Unprocessable Entity | Response for invalid business logic or unprocessable data. |
| `TooManyRequestsResponse` | 429 Too Many Requests | Rate-limit exceeded response. |
| `InternalServerErrorResponse` | 500 Internal Server Error | Response for unexpected server errors. |
| `ServiceUnavailableResponse` | 503 Service Unavailable | Response when the service is temporarily unavailable. |
| `buildSchema` | N/A | Internal helper that generates full Swagger schema for a specific HTTP status, including examples. |
The ConfigModule centralizes all application configuration, including environment variables, database settings, Swagger, logging, RabbitMQ, WebSocket, Cron Jobs, and internationalization.
It ensures every part of the system can easily access configuration through dependency injection.
-
Global Configuration Loading
The module loads multiple configuration files globally via
@nestjs/config:
NestConfigModule.forRoot({
isGlobal: true,
load: [appConfig, swaggerConfig, dbConfig, rabbitmqConfig, realtimeConfig],
});The RealtimeModule enables WebSocket-based real-time communication across the application.
It uses socket.io and NestJS gateways to allow broadcasting and targeted communication between the server and connected clients.
-
The module provides a
SocketServicethat abstracts:- Emitting events to all clients
- Emitting events to a specific client
- Storing and sharing the active Socket.io namespace
-
Two WebSocket gateways are included:
- MainGateway – Handles global connection initialization and connection logs.
- ProfileGateway – Handles profile-related WebSocket events (e.g.,
get_profile).
- Real-time communication using WebSockets
- Broadcast events to all connected clients
- Send updates to a specific user
- Extendable for new event channels
- Optionally integrates JWT authentication middleware
- Can be triggered by internal events (e.g., RabbitMQ consumers)
Sending an event to all connected clients:
this.socketService.emitToAll('profile_update', payload);The CronModule is responsible for running scheduled background tasks in the application.
These tasks run automatically at fixed intervals using NestJS @nestjs/schedule cron decorators.
- Periodically pick pending work items (e.g., profiles that need updates)
- Dispatch jobs into RabbitMQ for asynchronous processing
- Log status and failures
- Auto-handle queue failures by reverting profile states
The module includes a main service:
This is a scheduled background worker that:
- Runs every 30 seconds using:
@Cron(CronExpression.EVERY_30_SECONDS)The RabbitMqModule handles all asynchronous message-based communication in the application.
It is responsible for publishing jobs, consuming jobs, and managing retry logic using RabbitMQ.
This module enables high-performance background processing and decoupled service interactions.
- Configure and initialize the RabbitMQ microservice client
- Publish messages (producers)
- Consume messages (consumers)
- Handle failures using DLX (Dead Letter Exchange)
- Support real-time updates with WebSocket broadcasting
- Ensure reliable job delivery with retries and backoff
The module contains:
UpdateProfileProducerService
This service publishes messages into RabbitMQ.
It uses lazy connection handling to ensure the RMQ client connects once and safely retries on failure.
Key features:
- Ensures RMQ connection (
ensureConnected()) - Emits events with a 2-second timeout
- Logs failures without throwing exceptions
- Returns
true/falsedepending on queue success
Publish Flow:
validate payload → ensure connection → emit event → handle timeout → log → return status
UpdateProfileConsumerController
This controller listens for events and processes profile update jobs.
Processing Logic:
- Validate and lock the profile (
Queued → Processing) - Apply update (increment version, set timestamps, mark completed)
- Broadcast updates via WebSocket
- Acknowledge on success (
ack) - On failure:
- Mark for retry with
nextRunAt +5 minutes - Push message to DLX using
nack
- Mark for retry with
Failure Flow:
processing fail → update profile with retry time → nack (DLX) → auto retry by cron
The client is registered dynamically using application configuration (ConfigService):
ClientsModule.registerAsync([
{
name: ServiceNames.NEST_TEMPLATE_SYNC,
useFactory: (configService: ConfigService) => ({
transport: Transport.RMQ,
options: {
urls: [configService.rabbitmq.rabbitmqUri],
queue: configService.rabbitmq.rabbitmqQueue,
queueOptions: { durable: true },
},
}),
},
]);Cron → Pick Profiles → Producer Publishes Jobs → RabbitMQ → Consumer Processes → Real-time Emit
- Fully decoupled async processing
- Lazy + safe RMQ connection handling
- Timeout-protected message publishing
- Dead-letter exchange retry logic
- Real-time WebSocket broadcasting
- Robust error recovery
- Clean separation between Producer and Consumer
If you want to test the real-time WebSocket functionality, you can use the provided test-socket.html file located in the root directory of the project.
- Open the file in your browser:
test-socket.html - It allows you to connect to the
/realtimenamespace and test emitting/listening to events.
Note: Make sure your server is running and the WebSocket endpoint is accessible.
We welcome contributions to this project! To get started, fork the repository and clone it to your local machine. Here are some guidelines to follow:
Please create a new issue for any bugs or suggestions you have. Be sure to provide clear details about the problem, including steps to reproduce.
- Fork the repository and clone it locally.
- Create a new branch (
git checkout -b feature-name). - Write your code and tests.
- Commit your changes (
git commit -am 'Add new feature'). - Push your branch to your fork (
git push origin feature-name). - Submit a pull request.
We welcome contributions to improve the NestJS Template! Please follow the guidelines in the CONTRIBUTING.md file for details on how to contribute.
- Fork the repository
- Clone your fork
git clone git@github.com:jahidhiron/nestjs-template.git cd nestjs-template
We are committed to maintaining a positive and inclusive environment for all contributors. Please review our Code of Conduct to understand the expectations for participation in this project.
By participating, you agree to uphold these standards and contribute positively to the community.
If you need help or have questions, feel free to reach out:
📧 Email: namehiron.96@gmail.com
This project is licensed under the MIT License.
See the LICENSE file for details.