Django-style test DBs with Prisma: what I built in my monorepo (PoC)
2026-01-08
Django-style test DBs in NestJS: my MikroORM integration test helper (PoC)
2025-09-11
2026-01-08
2025-09-11
I like writing about software development but I rarely do it. The problem was never a lack of ideas. I have plenty of opinions about testing, framework choices, and how to build things. The problem was the time it took to go from "I want to write about X" to a published post. Sitting down, drafting, editing, formatting, and finally getting the thing into my blog's CMS felt like a side project on top of my actual side projects.
So I decided to treat it like any other engineering problem: find where the friction is and remove it.
I needed 3 things to make this work: a way to create posts programmatically in my blog, a bridge between Claude and that API, and a way to teach Claude how I write and what I care about.
My blog runs on Django. Until recently, the only way to create a post was through the Django admin. That's fine for manual writing, but it doesn't connect to anything else.
I added a simple API endpoint that accepts a POST request with the fields I need for a blog post: title, slug, meta description, content in Markdown, and tags. The post gets created as a draft so nothing goes live without my review.
You can see the pull request here. It's nothing fancy. A straightforward DRF endpoint with token authentication. The kind of thing Django makes easy and boring in the best way.
MCP (Model Context Protocol) is a standard that lets AI assistants like Claude interact with external tools and services. Instead of copying text from a chat window and pasting it into a form, Claude can call my blog's API directly.
I built a small MCP server that exposes my blog's POST endpoint as a tool Claude can use. The server runs locally and the configuration looks like this:
{
"mcpServers": {
"blog": {
"command": "uv",
"args": ["run", "server.py"],
"cwd": "/absolute/path/to/blog-mcp"
}
}
}
With this in place, I can ask Claude to create a draft and it sends the post data directly to my Django backend. No copy-pasting, no context switching.
This was the most interesting part. Claude is good at generating text, but generic AI-written content reads like generic AI-written content. I wanted posts that reflect my perspective, my background, and my opinions.
Claude Skills are instruction files that teach Claude specific workflows and preferences. I created one that captures how I think about software development: my experience with Django and TypeScript, my appreciation for simplicity over complexity, my focus on testability, and the way I prefer to explain things without hiding behind buzzwords.
The Skill also defines the output format so every post comes out with the exact fields my API expects. No reformatting needed.
Writing a post now looks like this:
I start with an idea. Maybe something I ran into at work, a pattern I found useful, or a topic I have a strong opinion about. I open Claude and describe what I want to write about, the key points I want to cover, and any specific angle I want to take.
Claude uses the Skill to generate a complete draft that matches my style and structure preferences. I read through it, adjust whatever needs adjusting, and when I'm happy with it I ask Claude to push the draft to my blog via the MCP server.
The post lands in my Django admin as a draft. I do a final review there, maybe tweak a sentence or two, and hit publish.
The whole process takes a fraction of what it used to take. More importantly, the friction that stopped me from writing is gone. I no longer need a free afternoon to produce a post. I need ten minutes and a clear idea.
Most of the words in my posts are generated by Claude. I want to be transparent about that. But here's what matters to me: every post starts with my idea, my perspective, and my direction. The Skill I built encodes my opinions and my way of explaining things. I review and edit everything before it goes live.
The goal was never to remove myself from the process. It was to remove the parts that slowed me down so I could focus on the part I actually enjoy: figuring out what's worth saying.
I wanted the same thing again: keep unit tests fast, but enable a subset of tests to run against a real Postgres database with migrations applied automatically similar to Django's default experience. In my monorepo PoC, I wired Prisma to support that workflow.
Note: This post describes a proof of concept. The core idea works, but I'll be explicit about what's still missing for a production-grade testing setup.
Django's test experience is integrated: test runner + ORM + migrations + DB lifecycle all come together. In the TS ecosystem, Prisma is “just” the ORM layer; you still need to decide:
So I built a pragmatic approach inside my monorepo PoC.
Repo: abel-castro/blog-monorepo (NestJS API + frontend in one workspace). 
My Prisma integration-testing recipe is:
Prisma is great, but it's intentionally not a full “framework”.
So: you build a thin layer that standardizes your project's conventions.
This PoC focuses on the “Django feeling” of:
In other words: integration tests become easy to add.
Even if the PoC works, there are predictable pain points:
If you want this to feel as boring as Django:
A single command Something like:
pnpm test (unit only, no DB)pnpm test:e2e (starts DB, migrates, runs integration specs, tears down)A robust reset strategy
Per-worker DB isolation (parallel-safe)
CI hardening
Django sets an expectation: “integration tests with a real DB should be trivial.” In Prisma + NestJS, that experience is absolutely achievable but you have to intentionally assemble the pieces.
Repo PoC: abel-castro/blog-monorepo.
Coming from Django, I missed the “it just works” test database story: tests can run real queries against a real DB with migrations applied automatically. In the TypeScript ecosystem, this is usually not “default behavior”, so I built a small helper for NestJS + MikroORM that gives me a Django-like workflow for integration tests while keeping unit tests fast and DB-free.
In Django, the test runner and ORM are part of one cohesive stack:
In many TS backends (NestJS + ORM + Jest/Vitest), the ecosystem is more modular. NestJS doesn’t own your ORM; the ORM doesn’t own your test runner; and the test runner doesn’t own your DB lifecycle. Result: you assemble your own conventions.
A few reasons (none are “bad”, it’s mostly ecosystem shape):
So instead of waiting for a universal solution, I created a small helper that matches my needs.
In this PR, I added a tiny utility class that:
*_test database,Key idea: unit tests stay unit tests (fast, mocked). Integration tests opt into “real DB”.
The helper lives at test/utils/database-test.ts and builds a MikroORM config from your app config, but with a test dbName and the migrator enabled. It ensures the database exists and migrates it.
Important implementation details from the PoC:
dbName becomes ${commonMikroOrmConfig.dbName}_testextensions: [Migrator] enables migrationsensureDatabase() creates the DB if missinggetMigrator().up() applies pending migrationsteardown closes the connection but keeps the database contentI added posts.integration.spec.ts as a minimal integration test showing how it feels to use: 
beforeAll: orm = await DatabaseTest.init()afterAll: await DatabaseTest.close()This test demonstrates real persistence + retrieval via the DB. It also includes a second test that asserts data is still there (“keeps data between tests”). This is just here to demonstrate the default behavior but in a real world project it would be a good idea to delete the created data after each test case.
This is explicitly a PoC, and it has important caveats:
If I were to evolve this from PoC to “real project quality”, I’d add:
Django made “real DB tests” feel boring—in a good way. In NestJS + MikroORM, I had to assemble the story myself. This helper is small, explicit, and already valuable for integration tests where real queries matter.
PoC source: PR #1 in abel-castro/blog-nest .