nnestjs-drizzle-crud
Guides

Defining services

The empty-subclass pattern, generics, and how to add custom methods without breaking the framework wiring.

The SqlBaseCrudService base class is designed so the vast majority of services are empty subclasses. The connection, dialect, table, and per-entity configuration are all injected by forFeature — your service file usually contains no logic.

The minimum

users/users.service.ts
import { SqlBaseCrudService } from 'nestjs-drizzle-crud';
import type { User } from '../db/schema';

export class UsersService extends SqlBaseCrudService<User> {}

The three remaining generics default to Partial<Entity>:

// Equivalent to:
class  extends <, <>, <>, <>> {}
// Supply all four generics, or rely on the inferred defaults.

With explicit DTOs

When you want to decouple your input shape from your entity (recommended for public APIs):

users/users.service.ts
import { SqlBaseCrudService } from 'nestjs-drizzle-crud';
import type { User } from '../db/schema';

export interface CreateUserDto { name: string; email: string }
export interface UpdateUserDto { name?: string; email?: string }
export interface UserFilterDto { name?: string; email?: string }

export class UsersService extends SqlBaseCrudService<
  User,
  CreateUserDto,
  UpdateUserDto,
  UserFilterDto
> {}

The FilterDto is not used to auto-build a query — it's the type signature of the findAll filter argument. Use it to constrain which columns can be filtered from controllers. See Filtering for the actual filter syntax.

Adding custom methods

Override nothing — just add your own methods. They have full access to this.config (the resolved SqlCrudConfig) and this.config.db (the Drizzle instance), so anything you can do in raw Drizzle is available inside the service.

users/users.service.ts
import { eq } from 'drizzle-orm';
import { SqlBaseCrudService } from 'nestjs-drizzle-crud';
import { users, type User } from '../db/schema';

export class UsersService extends SqlBaseCrudService<User> {
  findByEmail(email: string) {
    return this.findOne({ email } as Partial<User>);
  }

  async findActive() {
    return this.findAll(
      { deletedAt: { isNull: true } } as Partial<User>,
      { sortBy: 'createdAt', sortOrder: 'desc' },
    );
  }

  // Drop down to raw Drizzle for complex queries.
  async findWithPostCount() {
    return this.config.db
      .select({
        id: users.id,
        name: users.name,
        postCount: sql<number>`count(${posts.id})`.as('post_count'),
      })
      .from(users)
      .leftJoin(posts, eq(posts.authorId, users.id))
      .groupBy(users.id);
  }
}

Don't inject the database yourself

A common mistake is to add a constructor to inject the Drizzle instance:

Don't
export class UsersService extends SqlBaseCrudService<User> {
  constructor(
    @Inject(DRIZZLE_DB) private readonly db: any,  // ❌ not needed
  ) {
    super({ /* ❌ the table doesn't go here */ });
  }
}

The module constructs the service for you with the correct config. If you need the Drizzle instance inside a custom method, use this.config.db — it points to the same instance.

Adding a constructor that calls super({ ... }) is a legacy pattern. The modern pattern is an empty subclass plus forFeature({ service, table }). The service class itself doesn't need to know about the database or the table.

Don't add @Injectable() either

forFeature registers the service as a Nest provider automatically. Adding @Injectable() is harmless but redundant.

Don't add OnModuleInit to "warm up" the connection

DrizzleCrudModule handles connection lifecycle. The connection is established lazily on first query.

When the empty-subclass pattern isn't enough

Reach for these when the pattern breaks down:

  • You need a service that queries across multiple tables. Don't try to glue this onto SqlBaseCrudService — write a normal Nest service that injects DRIZZLE_DB and uses raw Drizzle.
  • You need streaming rows. findAll materializes the result. For large result sets, inject DRIZZLE_DB and use Drizzle's streaming APIs.
  • You need GraphQL resolvers. The service methods are plain async functions and can be called from any resolver layer.

Next

On this page