@bluealba/nestjs-slonik
v1.0.0
Published
NestJS module providing PostgreSQL integration with Slonik query builder, automatic migrations, and type parsing
Readme
SlonikModule
A NestJS module that provides PostgreSQL database integration using Slonik query builder with built-in migrations and type parsing.
Features
- Slonik Integration: Wraps the powerful Slonik PostgreSQL client for NestJS
- Automatic Migrations: Built-in database migration support with environment-specific migrations
- Type Parsing: Automatic parsing of PostgreSQL timestamp types to JavaScript Date objects
- Async Configuration: Flexible configuration with dependency injection support
Installation
npm install @bluealba/nestjs-slonikUsage
Basic Configuration
import { Module } from '@nestjs/common';
import { SlonikModule } from '@bluealba/nestjs-slonik';
// Define your environment type for type safety
type AppEnv = 'local' | 'dev' | 'staging' | 'prod';
@Module({
imports: [
SlonikModule.forRootAsync<AppEnv>({
useFactory: async () => ({
connectionUri: 'postgresql://user:password@localhost:5432/database',
migrations: {
migrationsPath: 'src/postgres-migrations',
environment: 'local', // TypeScript validates this against AppEnv
},
}),
}),
],
})
export class AppModule {}Configuration with ConfigService and mapColumnNamesToCamelCase enabled
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { SlonikModule } from '@bluealba/nestjs-slonik';
// Define your environment type for type safety
type AppEnv = 'local' | 'dev' | 'staging' | 'prod';
@Module({
imports: [
ConfigModule.forRoot(),
SlonikModule.forRootAsync<AppEnv>({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
connectionUri: configService.get('DATABASE_URL'),
mapColumnNamesToCamelCase: true,
migrations: {
migrationsPath: 'src/postgres-migrations',
environment: (configService.get('NODE_ENV') || 'local') as AppEnv,
},
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}Global Module Configuration
When global: true is set, the module becomes globally available across your application, eliminating the need to import it in other modules:
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { SlonikModule } from '@bluealba/nestjs-slonik';
// Define your environment type for type safety
type AppEnv = 'local' | 'dev' | 'staging' | 'prod';
@Module({
imports: [
ConfigModule.forRoot(),
SlonikModule.forRootAsync<AppEnv>({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
connectionUri: configService.get('DATABASE_URL'),
mapColumnNamesToCamelCase: true,
migrations: {
migrationsPath: 'src/postgres-migrations',
environment: (configService.get('NODE_ENV') || 'local') as AppEnv,
},
}),
inject: [ConfigService],
global: true, // Makes the module globally available
}),
],
})
export class AppModule {}With global: true, you can inject the database pool in any service without importing SlonikModule in feature modules.
Using the Database in Services
import { Injectable } from '@nestjs/common';
import { DatabasePool } from 'slonik';
import { InjectPool } from '@bluealba/nestjs-slonik';
@Injectable()
export class UserService {
constructor(@InjectPool() private readonly db: DatabasePool) {}
async findUser(id: number) {
return await this.db.one`
SELECT id, name, email, created_at
FROM users
WHERE id = ${id}
`;
}
async createUser(name: string, email: string) {
return await this.db.one`
INSERT INTO users (name, email, created_at)
VALUES (${name}, ${email}, NOW())
RETURNING *
`;
}
}Configuration Options
SlonikModuleOptions
| Option | Type | Description |
| ------------ | --------------------------------------- | ---------------------------------------------------------------------- |
| useFactory | (...args: any[]) => Promise<Config> \| Config | Factory function to create module configuration (required) |
| inject | any[] | Optional dependencies to inject into useFactory |
| imports | Array<Type \| DynamicModule> | Optional modules to import |
| global | boolean (default: false) | Makes the module globally available without needing to import it |
SlonikModuleConfig
| Option | Type | Description |
| --------------------------- | -------------------------- | ---------------------------------------------------------------- |
| connectionUri | string | PostgreSQL connection string |
| mapColumnNamesToCamelCase | boolean (default: false)| Transforms snake_case column names to camelCase in query results |
| migrations | DeploymentModuleOptions | Migration configuration |
| clientConfigurationInput | ClientConfigurationInput | Additional Slonik client configuration |
DeploymentModuleOptions
| Option | Type | Description |
| ---------------- | -------- | --------------------------------- |
| migrationsPath | string | Path to migration files directory |
| environment | TEnv extends string | Target environment for migrations (type-safe with generics) |
Migration Files
Create migration files in your specified migrationsPath:
src/postgres-migrations/
|--- 2025.01.01T00.00.00.000Z.create_users_table.js
|--- 2025.01.02T00.00.00.000Z.add_user_indexes.js
|--- byEnv/
|--- prd/
|--- 2025.01.01T00.00.00.000Z.production_specific_migration.jsMigration File Example
// 2025.01.01T00.00.00.000Z.create_users_table.js
const { sql } = require('slonik');
/** @type {import('umzug').MigrationFn<import('slonik').DatabaseConnection>} */
const up = async ({ context: connection }) => {
await connection.query(sql`
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
)
`);
};
/** @type {import('umzug').MigrationFn<import('slonik').DatabaseConnection>} */
const down = async ({ context: connection }) => {
await connection.query(sql`DROP TABLE users`);
};
module.exports = { up, down };Type Parsing
The module automatically configures type parsers for PostgreSQL timestamp types:
timestamp�Datetimestamptz�Date
These parsers are automatically applied to all database queries.
Environment-Specific Migrations
Migrations can be organized by environment using the byEnv directory structure:
src/postgres-migrations/
|--- common_migration.js # Runs in all environments
|--- byEnv/
|--- local/
|--- local_only.js # Runs only in local environment
|--- dev/
|--- dev_only.js # Runs only in dev environment
|--- prd/
|--- production_only.js # Runs only in production
|--- common_migration.js # Overrides base common_migration in productionMigration Override Behavior: If a migration file exists in both the base directory and the environment-specific byEnv/ directory with the same filename, the environment-specific version will override the base migration. This allows for customizing migrations per environment while maintaining a common base.
Environment Type Safety
The module supports TypeScript generic types to enforce compile-time type safety for environment names. Define your own environment type and pass it to the module:
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { SlonikModule } from '@bluealba/nestjs-slonik';
// Define your environment type
type MyEnv = 'local' | 'dev' | 'staging' | 'prod';
@Module({
imports: [
ConfigModule.forRoot(),
SlonikModule.forRootAsync<MyEnv>({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
connectionUri: configService.get('DATABASE_URL'),
migrations: {
migrationsPath: 'src/postgres-migrations',
environment: configService.get('NODE_ENV') as MyEnv, // TypeScript validates this!
},
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}Runtime Validation
If you need runtime validation for environment values, use @IsIn() from class-validator:
import { IsDefined, IsIn } from 'class-validator';
// Define allowed environments
const VALID_ENVS = ['local', 'dev', 'staging', 'prod'] as const;
type MyEnv = typeof VALID_ENVS[number];
export class Environment {
@IsDefined()
@IsIn(VALID_ENVS)
NODE_ENV: MyEnv;
@IsDefined()
DB_HOST: string;
@IsDefined()
DB_USER: string;
@IsDefined()
DB_PASS: string;
@IsDefined()
DB_NAME: string;
}Dependencies
This module depends on:
slonik- PostgreSQL clientumzug- Migration framework
