NestJS 12 + TypeORM 1 on in-memory SQLite, tested with @nestjs/testing: working versions (tested 2026-10-15)
A NestJS module with a TypeORM entity, repository-backed service and controller, wired to in-memory SQLite and tested end to end through the Nest app with supertest. better-sqlite3 stays on 12.x because TypeORM 1.1 peers on ^12.
passed2 passed · 0 failed2026-10-15 11:55 UTC- Status
- passed
- Last passed
- 2026-10-15 11:55 UTC
- Tests
- 2 / 2
- Language
- TypeScript
- Runtime
- node v22.23.3
npm install --no-audit --no-fund --ignore-scripts=false npm test
node v22.23.3 linux 6.17.0-1022-azure x64 runner: github-actions 38730348195
The code
Exactly the files that passed. Your agent can pull them with get_recipe("nestjs-typeorm-sqlite") (install), or see them on GitHub.
{
"name": "nestjs-typeorm-sqlite",
"private": true,
"type": "module",
"scripts": {
"test": "node --import tsx --test test/*.test.ts"
},
"dependencies": {
"@nestjs/core": "12.1.3",
"@nestjs/common": "12.1.3",
"@nestjs/platform-express": "12.1.3",
"@nestjs/typeorm": "12.0.2",
"typeorm": "1.1.2",
"better-sqlite3": "12.11.1",
"reflect-metadata": "0.2.2",
"rxjs": "7.8.2"
},
"devDependencies": {
"tsx": "4.23.15",
"@types/node": "24.19.2",
"@nestjs/testing": "12.1.3",
"supertest": "7.3.1",
"@types/supertest": "7.2.1",
"@types/express": "5.0.6"
}
}
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"allowImportingTsExtensions": true,
"noEmit": true,
"strict": true,
"skipLibCheck": true
}
}
import "reflect-metadata";
import { Body, Controller, Get, Inject, Injectable, Module, NotFoundException, Param, ParseIntPipe, Post } from "@nestjs/common";
import { TypeOrmModule, getRepositoryToken } from "@nestjs/typeorm";
import { Column, Entity, PrimaryGeneratedColumn, type Repository } from "typeorm";
// Explicit column types and @Inject tokens: tsx/esbuild do not emit decorator metadata.
@Entity()
export class Todo {
@PrimaryGeneratedColumn("increment") id!: number;
@Column({ type: "varchar", length: 200 }) title!: string;
@Column({ type: "boolean", default: false }) done!: boolean;
}
@Injectable()
export class TodosService {
constructor(@Inject(getRepositoryToken(Todo)) private readonly repo: Repository<Todo>) {}
create(title: string) { return this.repo.save(this.repo.create({ title })); }
findAll() { return this.repo.find({ order: { id: "ASC" } }); }
async findOne(id: number) {
const t = await this.repo.findOneBy({ id });
if (!t) throw new NotFoundException(`todo ${id} not found`);
return t;
}
}
@Controller("todos")
export class TodosController {
constructor(@Inject(TodosService) private readonly todos: TodosService) {}
@Post() create(@Body() body: { title: string }) { return this.todos.create(body.title); }
@Get() all() { return this.todos.findAll(); }
@Get(":id") one(@Param("id", ParseIntPipe) id: number) { return this.todos.findOne(id); }
}
@Module({
imports: [
TypeOrmModule.forRoot({ type: "better-sqlite3", database: ":memory:", entities: [Todo], synchronize: true }),
TypeOrmModule.forFeature([Todo]),
],
controllers: [TodosController],
providers: [TodosService],
})
export class AppModule {}
import "reflect-metadata";
import { test, before, after } from "node:test";
import assert from "node:assert/strict";
import { Test } from "@nestjs/testing";
import type { INestApplication } from "@nestjs/common";
import request from "supertest";
import { AppModule } from "../src/app.ts";
let app: INestApplication;
before(async () => {
const mod = await Test.createTestingModule({ imports: [AppModule] }).compile();
app = mod.createNestApplication();
await app.init();
});
after(() => app.close());
test("create and list", async () => {
const c = await request(app.getHttpServer()).post("/todos").send({ title: "ship" });
assert.equal(c.status, 201);
assert.equal(c.body.done, false);
assert.equal((await request(app.getHttpServer()).get("/todos")).body.length, 1);
});
test("404 and pipe validation", async () => {
assert.equal((await request(app.getHttpServer()).get("/todos/99")).status, 404);
assert.equal((await request(app.getHttpServer()).get("/todos/abc")).status, 400);
});
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
Drizzle ORM + better-sqlite3 CRUD
Typed schema, migrations-free table creation and CRUD with Drizzle on an in-memory SQLite database.
Drizzle ORM + drizzle-zod validated inserts on SQLite
Generate Zod insert and select schemas from Drizzle tables with drizzle-zod, refine them, and validate input before inserting into SQLite.
Hono + Drizzle ORM + SQLite REST API (Vitest)
CRUD REST API in Hono backed by Drizzle ORM on better-sqlite3, with Zod validation, tested with Vitest and app.request().
Kysely + better-sqlite3 typed queries
Type-safe SQL with Kysely on SQLite, including a schema migration, joins and a transaction.
All @nestjs/core recipesAll @nestjs/typeorm recipesAll typeorm recipesAll better-sqlite3 recipes