abelcastro.dev

How I Use AI to Write Blog Posts

2026-04-02

mcpdjangoclaude

Django-style test DBs with Prisma: what I built in my monorepo (PoC)

2026-01-08

typescripttestingprisma

Django-style test DBs in NestJS: my MikroORM integration test helper (PoC)

2025-09-11

typescripttestingmikro-orm
1
...
3
4
5
...
13

Abel Castro 2026 - checkout the source code of this page on GitHub - Privacy Policy

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.

1st: A POST endpoint for my blog

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.

2nd: An MCP server to connect Claude to my API

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.

3rd: A Skill that knows how I write

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.

The workflow now

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.

A note about AI-generated content

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.

TL;DR

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:

  • How to initiate the DB
  • When migrations run
  • How to reset data
  • How to prevent parallel tests from stepping on each other

So I built a pragmatic approach inside my monorepo PoC.

Repo: abel-castro/blog-monorepo (NestJS API + frontend in one workspace). 

The approach (conceptually)

My Prisma integration-testing recipe is:

  1. Provide a real Postgres for tests - easiest path: docker-compose (local + CI)
  2. Use a dedicated DATABASE_URL for integration tests
    • never point integration tests at dev/prod DBs
    • ideally a separate DB name or schema
  3. Ensure schema is up-to-date before tests
    • run migrations programmatically, or via a pre-test command
    • be consistent: either “migrate” (schema history) or “db push” (fast, but less realistic)
  4. Reset data deterministically

Why Prisma feels different than Django here

Prisma is great, but it's intentionally not a full “framework”.

  • Prisma doesn't know your test runner (Jest/Vitest/node:test).
  • Prisma doesn't own your DI container (NestJS does).
  • Prisma migrations are a separate workflow (CLI / migration engine).
  • There's no universally “right” cleanup strategy.

So: you build a thin layer that standardizes your project's conventions.

What my PoC focuses on

This PoC focuses on the “Django feeling” of:

  • “I can write tests that hit a real DB”
  • “DB schema is ready when tests run”
  • “Unit tests remain fast and simple”

In other words: integration tests become easy to add.

Good points of this direction

  • More realistic tests for anything query-heavy:
  • filters, ordering, pagination
  • relations, constraints, unique indexes
  • migrations-related behavior
  • Less mocking around the ORM boundary
  • Higher confidence refactors (especially around query logic)

Current trade-offs

Even if the PoC works, there are predictable pain points:

  1. Speed
    • DB startup + migrations cost time.
    • Resetting DB between tests can dominate runtime if done naively.
  2. Isolation
    • The best isolation approaches add complexity (transactions, schemas, per-worker DBs).
    • The easiest approaches (shared DB, truncate) can allow ordering-dependent tests if you're sloppy.
  3. Parallel tests
    • Test workers + one DB can cause flakiness unless you isolate per worker.
  4. Developer friction
    • “Run DB before integration tests” has to be one command, or people won't use it.

What I'd implement next (turning PoC into a solid template)

If you want this to feel as boring as Django:

  1. A single command Something like:

    • pnpm test (unit only, no DB)
    • pnpm test:e2e (starts DB, migrates, runs integration specs, tears down)
  2. A robust reset strategy

  3. Per-worker DB isolation (parallel-safe)

    • create DB/schema names based on worker id
    • avoid cross-test collisions
  4. CI hardening

    • healthchecks
    • deterministic order
    • run integration tests in a separate job/stage

Closing

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.

TL;DR

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.

Background: what I miss from Django testing

In Django, the test runner and ORM are part of one cohesive stack:

  • A dedicated test database is created automatically.
  • Migrations (or schema setup) are handled for you.
  • Each test (or test class) gets isolation via transactions / database flush strategies.
  • The whole thing is standardized across most Django projects.

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.

Why this isn’t “built-in” in many TS stacks (likely reasons)

A few reasons (none are “bad”, it’s mostly ecosystem shape):

  1. Framework/ORM/test-runner separation NestJS is framework-only; MikroORM/Prisma are external; Jest/Vitest are external. No single layer feels responsible for end-to-end test DB lifecycle.
  2. Performance expectations differ JS/ S culture often optimizes for fast unit tests first; integration tests exist but are intentionally explicit/opt-in.
  3. Parallelism & isolation are harder than they look A “shared test DB” is easy; a “shared test DB with parallel test workers, deterministic cleanup, and fast migrations” is not trivial to implement.

So instead of waiting for a universal solution, I created a small helper that matches my needs.

The PoC: DatabaseTest helper

In this PR, I added a tiny utility class that:

  • connects to a dedicated *_test database,
  • creates the DB if it doesn’t exist,
  • runs migrations,
  • and lets only certain test files opt in. 

Key idea: unit tests stay unit tests (fast, mocked). Integration tests opt into “real DB”.

The code

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}_test
  • extensions: [Migrator] enables migrations
  • ensureDatabase() creates the DB if missing
  • getMigrator().up() applies pending migrations
  • teardown closes the connection but keeps the database content

Using it in tests

I 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()
  • create an entity, persistAndFlush, clear EM, query it back, assert.

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.

What I like about this approach (benefits)

  • Django-ish developer experience for integration tests: “run tests, DB exists, migrations applied”.
  • Opt-in integration tests: you only pay the DB cost where it matters.
  • No custom CLI needed: the helper is regular TypeScript code used by Jest specs.
  • Avoids over-mocking: you can test queries, relations, constraints, migrations… the real stuff.

Current trade-offs

This is explicitly a PoC, and it has important caveats:

  1. No isolation by default The DB content is kept between tests and between runs. That’s fast, but it can create inter-test coupling unless you manage cleanup carefully.
  2. No parallel-worker safety If you run tests with multiple workers, they may collide on the same DB.
  3. Migration runtime Running migrations on test start is great, but can be slow as the migration history grows.
  4. State drift risk “Kept DB” can hide bugs if your schema/data evolves but the DB wasn’t reset as you expected.

What I would implement

If I were to evolve this from PoC to “real project quality”, I’d add:

  • Isolation strategy: truncate tables between tests/suites
  • Parallel test support: per-worker DB name (e.g. _test_w1, _test_w2).
  • Seeding/factories: a standard place for fixtures, factories, and deterministic seeds.

Closing

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 .