@marinade.finance/nestjs-common
v4.3.1
Published
Shared NestJS building blocks (database, telemetry, auth, rate limiting)
Readme
@marinade.finance/nestjs-common
Shared NestJS building blocks for Marinade Finance services. Provides database (PostgreSQL via slonik), Redis, authentication, rate limiting, telemetry/APM, Prometheus metrics, structured logging, Swagger setup, and graceful shutdown — all configured through environment variables.
Install
pnpm add @marinade.finance/nestjs-commonPeer dependencies: @nestjs/common, @nestjs/core, @nestjs/swagger, @willsoto/nestjs-prometheus, nestjs-pino, prom-client. Optional: elastic-apm-node.
Modules
Database
PostgreSQL connection pool via slonik with automatic migrations.
Database helpers live under the ./database subpath so services that don't use
a database don't need to install slonik. slonik is an optional peer
dependency — install it in consumers that import this module.
import {
DatabaseModule,
DatabaseService,
} from '@marinade.finance/nestjs-common/database'
DatabaseModule.forRoot(() => ({
url: getDatabaseUrl(),
maxPoolSize: getDatabasePoolSize(),
}))| Variable | Required | Description |
|---|---|---|
| DATABASE_URL | Yes | PostgreSQL connection string |
| DATABASE_POOL_SIZE | No | Max pool connections |
Redis
import { RedisModule } from '@marinade.finance/nestjs-common'
RedisModule.forRoot(() => ({
url: getRedisUrl(),
password: getRedisPassword(),
}))| Variable | Required | Description |
|---|---|---|
| REDIS_URL | Yes | Redis connection URL |
| REDIS_PASSWORD | No | Redis password |
Auth
JWT-based authentication guard with glob-pattern user whitelisting.
| Variable | Required | Description |
|---|---|---|
| JWT_SECRET | Yes | JWT signing secret |
| API_SECRET | Yes | API secret key |
| ALLOWED_USERS | Conditional | Comma-separated usernames/glob patterns |
Rate Limiting
Per-route rate limiting via @RateLimit() decorator and RateLimitGuard.
Telemetry / APM
Elastic APM integration. Call startApm() before NestFactory.create().
APM helpers live under the ./apm subpath so services that don't use APM don't
need to install elastic-apm-node. elastic-apm-node is an optional peer
dependency — install it in consumers that import this module.
import { startApm } from '@marinade.finance/nestjs-common/apm'
const agent = startApm() // reads env vars, returns undefined if disabled| Variable | Required | Default | Description |
|---|---|---|---|
| ELASTIC_APM_ENABLED / OTEL_ENABLED / otel.enabled | No | false | Enable telemetry (first match wins) |
| ELASTIC_APM_SERVER_URL / otel.url | When enabled | - | APM server endpoint |
| ELASTIC_APM_SECRET_TOKEN / otel.secretToken | No | - | APM auth token |
Metrics
Prometheus metrics server with health/readiness endpoints.
import { startMetricsServer } from '@marinade.finance/nestjs-common'
const server = startMetricsServer({
port: getMetricsPort(),
healthz: true,
readinessCheck: () => ({ ready: true }),
})| Variable | Required | Default | Description |
|---|---|---|---|
| METRICS_PORT | No | 9000 | Prometheus metrics port |
Logging
Structured JSON logging via pino with ECS format for Elastic.
import { getPinoElasticConfigBuilder, customLogLevel } from '@marinade.finance/nestjs-common'Bootstrap Utilities
import { setupSwagger, setupGracefulShutdown } from '@marinade.finance/nestjs-common'
setupSwagger(app, { title: 'My API' })
setupGracefulShutdown(app, { metricsServer, beforeShutdown: () => { /* mark not ready */ } })Service Configuration
| Variable | Required | Default | Description |
|---|---|---|---|
| SERVICE_NAME / ELASTIC_APM_SERVICE_NAME / serviceName | Yes | - | Service name (first match wins) |
| PORT | No | 3000 | HTTP server port |
Config Helpers
All env helpers from @marinade.finance/config-common are re-exported:
import { getEnvVar, getBoolEnvVar, getJsonEnvVar } from '@marinade.finance/nestjs-common'Single key or fallback arrays are supported:
getEnvVar(['PRIMARY_KEY', 'FALLBACK_KEY'], 'default')Example
main.ts — bootstrap
import { NestFactory } from '@nestjs/core'
import {
startMetricsServer,
setupSwagger,
setupGracefulShutdown,
getPinoElasticConfigBuilder,
customLogLevel,
getServiceName,
getPort,
getMetricsPort,
getServiceEnvironment,
} from '@marinade.finance/nestjs-common'
import { startApm } from '@marinade.finance/nestjs-common/apm'
import { LoggerModule } from 'nestjs-pino'
import { AppModule } from './app.module'
// APM — must be called before NestFactory.create()
startApm()
async function bootstrap() {
const serviceName = getServiceName()
const app = await NestFactory.create(
AppModule.register(serviceName),
{ bufferLogs: true },
)
// Swagger docs at /docs
setupSwagger(app, { title: serviceName })
// Metrics + health/readiness on separate port
const metricsServer = startMetricsServer({
port: getMetricsPort(),
healthz: true,
readinessCheck: () => ({ ready: true }),
})
// Graceful shutdown: signals → mark not-ready → close app → close metrics
setupGracefulShutdown(app, { metricsServer })
await app.listen(getPort())
}
void bootstrap()app.module.ts — wiring modules
import { DynamicModule, Module } from '@nestjs/common'
import { LoggerModule } from 'nestjs-pino'
import {
// Database env helpers
getDatabaseUrl,
getDatabasePoolSize,
// Redis
RedisModule,
getRedisUrl,
getRedisPassword,
// Metrics
MetricsModule,
// Logging
getPinoElasticConfigBuilder,
customLogLevel,
getServiceEnvironment,
} from '@marinade.finance/nestjs-common'
import { DatabaseModule } from '@marinade.finance/nestjs-common/database'
@Module({})
export class AppModule {
static register(serviceName: string): DynamicModule {
return {
module: AppModule,
imports: [
// Structured logging (pino → ECS JSON)
LoggerModule.forRoot(
getPinoElasticConfigBuilder()({
serviceName,
serviceVersion: '1.0.0',
serviceEnvironment: getServiceEnvironment(),
logLevel: 'info',
customLogLevel,
}),
),
// PostgreSQL pool + auto-migrations from ./migrations
DatabaseModule.forRoot(() => ({
url: getDatabaseUrl(),
maxPoolSize: getDatabasePoolSize(),
})),
// Redis (global)
RedisModule.forRoot(() => ({
url: getRedisUrl(),
password: getRedisPassword(),
})),
// Prometheus HTTP counter
MetricsModule.forRoot(() => ({ port: 9000 })),
],
}
}
}