@haykal/core-backend
v1.0.0
Published
> Global NestJS infrastructure module providing logging, error handling, database, Redis caching, request context, domain events, query building, and OpenAPI — the foundation every Haykal backend module depends on.
Downloads
27
Readme
@haykal/core-backend
Global NestJS infrastructure module providing logging, error handling, database, Redis caching, request context, domain events, query building, and OpenAPI — the foundation every Haykal backend module depends on.
Installation
pnpm add @haykal/core-backendConfiguration
forRoot() Options
import { CoreBackendModule } from '@haykal/core-backend';
@Module({
imports: [
CoreBackendModule.forRoot({
appName: 'haykal',
env: 'development',
redis: {
url: 'redis://localhost:6379',
ttlDefaultSeconds: 300,
keyPrefix: 'haykal',
},
database: {
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'postgres',
database: 'haykal',
poolSize: 10,
logging: false,
},
openApi: {
enabled: true,
title: 'Haykal API',
version: '1.0.0',
path: '/api/docs',
},
logging: {
level: 'debug',
json: false,
excludeRoutes: ['/health', '/ready'],
},
cors: {
origins: ['http://localhost:3000'],
credentials: true,
},
requestTimeoutMs: 30000,
}),
],
})
export class AppModule {}Configuration Reference
| Property | Type | Required | Default | Description |
| ------------------ | -------------------------------------------- | -------- | ------- | ------------------------------------------------- |
| appName | string | Yes | — | Application name for logging, cache keys, OpenAPI |
| env | 'development' \| 'staging' \| 'production' | Yes | — | Runtime environment |
| redis | RedisConfig | No | — | Redis config; caching disabled if omitted |
| database | DatabaseConfig | No | — | Database config; DB disabled if omitted |
| openApi | OpenApiConfig | No | — | Swagger/OpenAPI settings |
| logging | LoggingConfig | No | — | Logging level, format, exclusions |
| cors | CorsConfig | No | — | CORS origins, methods, credentials |
| multiTenancy | MultiTenancyConfig | No | — | Multi-tenancy header and enforcement |
| requestTimeoutMs | number | No | 30000 | Request timeout in ms |
RedisConfig
| Property | Type | Default | Description |
| ------------------- | -------- | ---------- | ------------------------------- |
| url | string | — | Redis connection URL (required) |
| ttlDefaultSeconds | number | 300 | Default cache TTL |
| keyPrefix | string | "haykal" | Cache key prefix |
DatabaseConfig
| Property | Type | Default | Description |
| --------------- | ------------------- | ------------ | ---------------------------- |
| host | string | — | Database host (required) |
| port | number | — | Database port (required) |
| username | string | — | Database user (required) |
| password | string | — | Database password (required) |
| database | string | — | Database name (required) |
| type | string | 'postgres' | Database type |
| ssl | DatabaseSslConfig | — | SSL config |
| poolSize | number | 10 | Connection pool size |
| logging | boolean | false | TypeORM query logging |
| synchronize | boolean | false | Auto-sync schema (dev only) |
| migrationsRun | boolean | false | Run migrations on boot |
| retryAttempts | number | 3 | Connection retry count |
| retryDelay | number | 3000 | Retry delay in ms |
OpenApiConfig
| Property | Type | Default | Description |
| ------------- | --------- | -------------- | ----------------- |
| enabled | boolean | false | Enable Swagger UI |
| title | string | 'Haykal API' | API title |
| description | string | — | API description |
| version | string | '1.0.0' | API version |
| path | string | '/api/docs' | Swagger UI path |
LoggingConfig
| Property | Type | Default | Description |
| --------------- | ---------------------------------------- | --------------------------------- | ---------------------- |
| level | 'debug' \| 'info' \| 'warn' \| 'error' | 'debug' (dev) / 'info' (prod) | Log level |
| json | boolean | false (dev) / true (prod) | JSON format |
| excludeRoutes | string[] | ['/health', '/ready'] | Routes to skip logging |
CorsConfig
| Property | Type | Default | Description |
| ------------- | ---------- | ------- | -------------------- |
| origins | string[] | — | Allowed origins |
| methods | string[] | — | Allowed HTTP methods |
| credentials | boolean | — | Allow credentials |
MultiTenancyConfig
| Property | Type | Default | Description |
| -------------------- | --------- | --------------- | ------------------------------- |
| enabled | boolean | — | Enable multi-tenancy (required) |
| headerName | string | 'X-Tenant-ID' | Tenant header name |
| enforceOnAllRoutes | boolean | false | Require tenant on all routes |
Async Configuration
CoreBackendModule.forRootAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => ({
appName: config.get('APP_NAME'),
env: config.get('NODE_ENV'),
// ...
}),
inject: [ConfigService],
});Key Exports
| Export | Type | Description |
| --------------------- | ------------- | ------------------------------------------- |
| CoreBackendModule | NestJS Module | Global module — import first in AppModule |
| CORE_BACKEND_CONFIG | Symbol | Inject the full config object |
| REDIS_CLIENT | Symbol | Inject the ioredis client instance |
Request Context
| Export | Type | Description |
| ----------------------- | -------- | ---------------------------------------------------------------------------------------- |
| RequestContextService | Service | Access current request context (traceId, userId, tenantId, etc.) |
| RequestContext | Class | Context object with traceId, requestId, tenantId, userId, roles, permissions |
| getRequestContext() | Function | Get context from AsyncLocalStorage outside DI |
Caching
| Export | Type | Description |
| ----------------- | ------- | --------------------------------------------- |
| CacheService | Service | Redis-backed cache with fail-open behavior |
| CacheKeyBuilder | Class | Deterministic, tenant-aware cache key builder |
Errors
| Export | HTTP Status | Description |
| ------------------- | ----------- | -------------------------------- |
| DomainError | — | Base class for all domain errors |
| NotFoundError | 404 | Resource not found |
| UnauthorizedError | 401 | Authentication required |
| ForbiddenError | 403 | Insufficient permissions |
| ConflictError | 409 | Resource conflict |
| BusinessRuleError | 422 | Business rule violation |
Domain Events
| Export | Type | Description |
| ----------------------- | ---------- | ------------------------------------------------------------- |
| DomainEvent | Base class | Extend to create domain events (auto eventId, occurredAt) |
| DomainEventBus | Service | emit() (sync) and emitAsync() (awaits handlers) |
| @OnDomainEvent(Event) | Decorator | Listen for domain events |
Database
| Export | Type | Description |
| ------------------------- | ---------------- | ------------------------------------------------------------------ |
| HaykalDatabaseModule | Module | TypeORM bootstrapping for PostgreSQL |
| HaykalBaseEntity | Entity class | Base entity with id, createdAt, updatedAt, deletedAt, etc. |
| BaseRepository | Repository class | findByIdOrFail, save, remove with transaction support |
| TransactionService | Service | runInTransaction() and withTransaction() |
| SnakeCaseNamingStrategy | Strategy | camelCase → snake_case column mapping |
Query Building & Filtering
| Export | Type | Description |
| -------------------------- | -------- | -------------------------------------------------------------------------------- |
| QueryOptionsDto | DTO | Combined filter, sort, pagination options |
| buildFindOptions() | Function | Convert QueryOptionsDto to TypeORM FindManyOptions |
| applyToQueryBuilder() | Function | Apply filters to a SelectQueryBuilder |
| buildPaginatedResponse() | Function | Create paginated response with metadata |
| FilterOperator | Enum | eq, neq, gt, gte, lt, lte, in, between, is_null, ilike, etc. |
Decorators
| Export | Type | Description |
| --------------------------- | --------- | ------------------------------------------- |
| @RawResponse() | Decorator | Bypass response envelope wrapping |
| @ApiStandardResponse(dto) | Decorator | Document standard { data, meta } response |
| @ApiProblemResponse() | Decorator | Document RFC 7807 error response |
| @RequestTimeout(ms) | Decorator | Override request timeout for a route |
Guards
| Export | Type | Description |
| ------------------ | --------- | --------------------------------- |
| TenantGuard | Guard | Enforce tenant presence on routes |
| @RequireTenant() | Decorator | Mark route as requiring a tenant |
Logging & OpenAPI
| Export | Type | Description |
| ---------------- | -------- | ------------------------------------------------------- |
| LoggerService | Service | Structured, context-aware logger |
| setupOpenApi() | Function | Bootstrap Swagger UI with JWT + Tenant security schemes |
Usage Examples
Throwing Domain Errors
import { NotFoundError, ConflictError } from '@haykal/core-backend';
// In a service
const user = await this.repository.findById(id);
if (!user) {
throw new NotFoundError('User', id);
}Using the Cache
import { CacheService } from '@haykal/core-backend';
@Injectable()
export class MyService {
constructor(private readonly cache: CacheService) {}
async getData(id: string) {
return this.cache.getOrSet(
`my-domain:item:${id}`,
() => this.repository.findById(id),
{ ttlSeconds: 600 },
);
}
}Emitting Domain Events
import { DomainEvent, DomainEventBus, OnDomainEvent } from '@haykal/core-backend';
export class OrderCreatedEvent extends DomainEvent {
constructor(public readonly orderId: string) {
super();
}
}
// Emitting
this.domainEventBus.emit(new OrderCreatedEvent(order.id));
// Listening
@OnDomainEvent(OrderCreatedEvent)
async handleOrderCreated(event: OrderCreatedEvent) {
// React to the event
}Using Query Building
import { QueryOptionsDto, buildFindOptions, buildPaginatedResponse } from '@haykal/core-backend';
async findAll(query: QueryOptionsDto) {
const options = buildFindOptions(query, {
allowedFilters: ['status', 'createdAt'],
allowedSorts: ['createdAt', 'name'],
});
const [items, total] = await this.repository.findAndCount(options);
return buildPaginatedResponse(items, total, query);
}Architecture
CoreBackendModule is @Global(). When imported via forRoot(), it:
- Registers global providers:
RequestContextService,LoggerService,DomainEventBus,CacheService - Sets up global
ResponseInterceptor— auto-wraps responses in{ data, meta, traceId, requestId } - Sets up global
HttpExceptionFilter+ValidationExceptionFilter— RFC 7807 error responses - Configures middleware pipeline:
RequestContextMiddleware→TenantResolutionMiddleware→RequestLoggerMiddleware→TimeoutMiddleware - Conditionally imports
HaykalDatabaseModule(ifdatabaseconfig provided) - Conditionally enables Redis caching (if
redisconfig provided; otherwise no-op)
Related Packages
@haykal/shared— Shared types and constants@haykal/core-client— Client-side HTTP client and React Query provider- Architecture docs
- Backend style guide
