Foundations View as Markdown

Database access and testing

Wiring a database into a Nest module, and building the test setup that lets an agent iterate without a container.

A database provider#

src/database/prisma.service.ts
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect();
  }
}
src/database/database.module.ts
@Global()
@Module({ providers: [PrismaService], exports: [PrismaService] })
export class DatabaseModule {}

onModuleInit is one of Nest's lifecycle hooks. onModuleDestroy is the matching one for cleanup, and Nest calls it on shutdown if you enable shutdown hooks (app.enableShutdownHooks()), which you should — otherwise connections leak on redeploy.

Keep queries out of the controller#

src/users/users.service.ts
@Injectable()
export class UsersService {
  constructor(private readonly prisma: PrismaService) {}

  findAll(limit: number) {
    return this.prisma.user.findMany({ take: limit, orderBy: { createdAt: 'desc' } });
  }

  async findOne(id: string) {
    const user = await this.prisma.user.findUnique({ where: { id } });
    if (!user) throw new NotFoundException(`User ${id} not found`);
    return user;
  }
}

Throwing NotFoundException from the service is idiomatic: Nest's exception filter turns it into a 404 with a JSON body, and the controller stays a one-liner.

Unit tests#

The whole value of dependency injection shows up here.

src/users/users.service.spec.ts
describe('UsersService', () => {
  let service: UsersService;
  const prisma = { user: { findUnique: jest.fn(), findMany: jest.fn() } };

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      providers: [UsersService, { provide: PrismaService, useValue: prisma }],
    }).compile();
    service = module.get(UsersService);
  });

  it('throws when the user does not exist', async () => {
    prisma.user.findUnique.mockResolvedValue(null);
    await expect(service.findOne('missing')).rejects.toThrow(NotFoundException);
  });
});

No database, no HTTP, milliseconds. This is the suite your agent should be able to run after every edit — see the verification loop for why speed here matters so much.

End-to-end tests#

test/users.e2e-spec.ts
describe('Users (e2e)', () => {
  let app: INestApplication;

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({
      imports: [AppModule],
    })
      .overrideProvider(MailerService).useValue({ send: jest.fn() })
      .compile();

    app = moduleRef.createNestApplication();
    app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
    await app.init();
  });

  afterAll(() => app.close());

  it('rejects an invalid email', () =>
    request(app.getHttpServer())
      .post('/users')
      .send({ email: 'nope', name: 'Ada' })
      .expect(400));
});

overrideProvider is the mechanism that makes end-to-end tests practical: swap the mailer, the payment gateway and the queue for fakes, keep everything else real.

Transactions in tests#

The fastest reliable pattern for integration tests is a transaction per test, rolled back afterwards. It gives you a real database with no cleanup and no cross-test pollution — and it is fast enough to stay in the loop.

Common questions#

Prisma or TypeORM?#

Prisma has better type inference and a clearer migration story; TypeORM fits better if you want the ActiveRecord/decorator style throughout or need something Prisma does not support. Either works well with Nest. Do not use both.

Should I add a repository layer on top of the ORM?#

Only if you have a real reason — swapping the data store, or a domain model that differs substantially from your tables. Otherwise it is indirection with no payoff, and the ORM's types are better than the ones you will hand-write.

How do I keep end-to-end tests fast?#

Reuse one application instance across the file, run each test in a transaction that is rolled back, and mock anything crossing the network. Most slow Nest suites are slow because they build a new application per test.

Get the NestJS agent pack

A battle-tested AGENTS.md, the review checklist, and the failure-mode cheat sheet for NestJS. One email, then occasional updates when the tooling shifts. No course pitch.

Unsubscribe in one click. We never sell the list. Or just take the AGENTS.md now — no email needed.