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#
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() {
await this.$connect();
}
}@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#
@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.
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#
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.
AGENTS.md now — no email needed.