Shared Library (libs/shared)
Relevant source files
The following files were used as context for generating this wiki page:
The libs/shared library is a central component in the backend-luan-van monorepo. Its primary purpose is to provide a common set of modules, utilities, data structures, and configurations that can be shared across all microservices and the API Gateway. This approach promotes code reuse, ensures consistency in communication protocols and error handling, and enforces type safety between different parts of the distributed system.
The library is a key enabler of the project's architecture, which separates concerns into distinct applications (e.g., user-app, product-app) while allowing them to share foundational code. It centralizes common elements like Data Transfer Objects (DTOs), type definitions, NATS event patterns, and a global exception filter, significantly reducing boilerplate and improving maintainability.
Sources: nest-cli.json:69-77, apps/gateway/REFACTORING-GUIDE.md:1-10, libs/shared/main.ts
Library Configuration
The shared module is officially defined as a "library" within the project's monorepo configuration. This allows it to be compiled and used by other "application" type projects in the workspace.
Sources: nest-cli.json:69-77
A path alias @shared/* is configured in the root tsconfig.json, making it easy for any service to import shared components using a consistent and clean path.
// tsconfig.json:22-37
"paths": {
"@shared/*": ["libs/shared/*"],
"@ar-app/*": ["apps/ar-app/src/*"],
// ... other aliases
}
Sources: tsconfig.json:22-37
Core Components
The libs/shared library exports several key components that are fundamental to the application's operation. The main entry point, libs/shared/main.ts, re-exports all public-facing parts of the library.
The following diagram shows the high-level components of the shared library and how they are consumed by other applications.
Data Structures (DTOs & Types)
To ensure type safety across the NATS message bus, the library provides a comprehensive set of Data Transfer Objects (DTOs) and TypeScript types. These are used in controller method signatures in the API Gateway and in message handlers within the microservices. This guarantees that the data contracts between services are respected at compile time, preventing a wide class of runtime errors.
The refactoring guide for the API Gateway explicitly states that a key goal was to achieve "100% Type Coverage" with "No any types" by centralizing all response and request types in libs/shared/types/.
Sources: apps/gateway/REFACTORING-GUIDE.md:37-53, 161-164
RPC Exception Handling
A critical feature of the shared library is the AllRpcExceptionsFilter. This global filter is designed to catch exceptions thrown from microservices and normalize them into consistent, well-structured HTTP error responses at the API Gateway.
Sources: libs/shared/filters/rpc-exception.filter.ts
Filter Logic
The filter's behavior depends on the context (HTTP or RPC):
-
HTTP Context (API Gateway):
- It catches an exception, which could be a standard
Erroror anRpcExceptionfrom a microservice call. - It uses
ErrorParserto extract a status code, message, and details from the exception. - It uses
ErrorResponseBuilderto construct a standardized JSON error response. - It sends the response to the client with the appropriate HTTP status code.
- It catches an exception, which could be a standard
-
RPC Context (Microservice):
- It catches an exception.
- It builds a standardized RPC error object.
- It returns this error object as an
Observablethat throws the error, which is then sent back over NATS to the caller (the API Gateway).
The following sequence diagram illustrates the error handling flow from a microservice to the end client.
Custom Exceptions
The library also defines custom exception classes that extend RpcException, such as ValidationRpcException. These can be used within microservices to throw errors with specific meanings that the AllRpcExceptionsFilter can correctly interpret.
// libs/shared/exceptions/rpc-exceptions.ts
import { RpcException } from '@nestjs/microservices';
import { HttpStatus } from '@nestjs/common';
export class ValidationRpcException extends RpcException {
constructor(message: string | object, statusCode: number = HttpStatus.BAD_REQUEST) {
super({ message, statusCode });
}
}
Sources: libs/shared/exceptions/rpc-exceptions.ts
JWT Service
The library includes a shared JwtModule and JwtService. This is crucial for the "Perimeter Security" model, where the API Gateway handles authentication and authorization. The user-app uses the service to sign tokens, while the Gateway and other services can use it to verify them. The public key for JWT verification is distributed from the user-app via NATS, a process facilitated by shared components.
Sources: libs/shared/main.ts:10-11, apps/gateway/REFACTORING-GUIDE.md:13-18, deploys/README.md:183-197
NATS Event Patterns
To avoid magic strings and ensure consistency, all NATS message patterns are defined as constants and exported from libs/shared/events. Both the API Gateway (when sending messages) and the microservices (when defining message handlers) import patterns from this central location.
Sources: libs/shared/main.ts:19, apps/gateway/REFACTORING-GUIDE.md:73-76
Summary
The libs/shared library is an essential pillar of the project's microservices architecture. By centralizing DTOs, types, exception handling, JWT logic, and event patterns, it drastically improves developer experience, code quality, and system reliability. It allows developers to build and maintain services independently while adhering to a common, type-safe, and robust communication and error-handling framework.