Introducing nest-drizzle-native: A Nest-native Drizzle ORM integration

The release of nest-drizzle-native 0.2.1 introduces a community package designed to make Drizzle ORM feel entirely natural inside NestJS applications.

The core concept is straightforward: preserve Drizzle’s explicit, SQL-first philosophy while giving NestJS projects the module, dependency injection, repository, testing, and transaction ergonomics they expect.

The Motivation

Drizzle is excellent because it stays close to SQL and avoids heavy, opaque ORM magic.

NestJS, on the other hand, provides applications with a strong architectural shape built around modules, providers, decorators, testing utilities, lifecycle hooks, and clear dependency boundaries.

A bridge between those two worlds allows teams to build scalable enterprise architectures without turning Drizzle into something it is not. Existing solutions often act as thin connection wrappers, leaving developers to manually solve complex enterprise patterns like cross-service transactions or DTO co-generation.

What it supports today

  • DrizzleModule.forRoot(), forRootAsync(), and forFeature()
  • @InjectDrizzle() for direct Drizzle client injection
  • @DrizzleRepository() for query-focused provider classes
  • @Transactional() / @InjectTransaction() powered natively via @nestjs-cls/transactional
  • Named connections for multi-database applications
  • Testing utilities for clean module and repository testing
  • Zero runtime dependencies in the published library package
  • Runnable samples covering transactions, DTO validation, optional drizzle-zod validation, Swagger/OpenAPI integration, raw SQL execution, and various driver setups

Let’s look at the code

The biggest pain point of using Drizzle in a structured backend is transaction management. Passing a tx object through five layers of injected services completely breaks Dependency Injection.

With nest-drizzle-native, transactions are managed via Node.js AsyncLocalStorage, allowing them to flow seamlessly across your services just by using a decorator:

import { Injectable } from '@nestjs/common';
import { Transactional } from 'nest-drizzle-native';

@Injectable()
export class UserService {
  constructor(
    private readonly userRepository: UserRepository,
    private readonly auditService: AuditService,
  ) {}

  // A single decorator wraps the entire request scope in a transaction
  @Transactional()
  async createUser(data: CreateUserDto) {
    // 1. Insert the user using the native Drizzle API inside our repository
    const newUser = await this.userRepository.insert(data);

    // 2. Call another injected service. 
    // It automatically joins the existing transaction context!
    await this.auditService.logAction('USER_CREATED', newUser.id);

    return newUser;
  }
}

The repository itself is a clean class that exposes Drizzle’s raw query builder without hiding the SQL behind an Active Record layer:

import { DrizzleRepository } from 'nest-drizzle-native';
import { users } from './schema';

@DrizzleRepository(users)
export class UserRepository {
  constructor(@InjectDrizzle() private readonly db: DrizzleClient) {}

  async findActiveUsers() {
    return this.db.query.users.findMany({
      where: (users, { eq }) => eq(users.isActive, true),
    });
  }
}

What it does not try to do

The goal is strictly avoiding the re-creation of TypeORM or an Active Record layer.

Developers still write real, functional Drizzle queries. The library simply provides those queries with a native NestJS structure through decorators and context management, eliminating the need to pass transaction callbacks (tx) down through multiple layers of services.

Links

Community feedback from NestJS and Drizzle users is highly encouraged—especially regarding real-world transaction patterns, testing strategies, multi-database setups, and production adoption. Feel free to open issues or contribute to the repository!