DEV Community

Cover image for Building The Apothecary: Learning Database Versioning Through Medieval Fantasy
RAMI GHARBI
RAMI GHARBI

Posted on

Building The Apothecary: Learning Database Versioning Through Medieval Fantasy

Introduction

Picture this: You're about to deploy a major pricing update to your e-commerce site. Your hands hover over the keyboard. One wrong migration and you could corrupt production data. Sound familiar?

I built The Apothecary to explore a different approach: treating databases like Git repositories. what if, to test a new promotion, you could branch your database? Go back to any point in time? Test multiple pricing strategies with no risks? it sure sounds like a dream come true.

18 medieval products with dynamic pricing and promotions

This isn't theoretical. whithin two days, I built a fully functional e-commerce demo site using Guepard, a database versioning system that brings Git-like workflows to PostgreSQL. Here's what i discovered about database versioning, why it is important, and how it flips in our head the way we think about data.


The Problem: Database Changes Are Scary

Let's be honest: sporatic database changes are a developer's nightmare. Unlike code, where Git gives us branches, commits, and instant rollbacks, databases feel... permanent. Make a mistake and you'll find yourself struggling with SQL scripts at 2 AM trying to restore from backups.

Traditional workflows look like this:

  1. Write migration scripts
  2. Test in staging (hope it matches production)
  3. Deploy during low-traffic hours
  4. Hold your breath and pray
  5. If something breaks, scramble to write rollback SQL

We've all heard the stories: a misplaced decimal in a pricing update, a migration that corrupts data, a schema change that accidently breaks production. When disaster strikes, rolling back means struggle, manual SQL queries, data recovery from backups, and hours of stress. The current tools we rely on make database mistakes feel permanent and recovery feel arduous.

What if databases could work more like Git? That's the question Guepard answers.


The Solution: Git for Databases

Guepard introduces three core concepts borrowed from Git:

1. Branches

Create isolated database clones in seconds. Each branch has its own data and schema. That is perfect for testing promotions or new features without messing with production.

2. Commits (Snapshots)

Save the current state of your database with a message. Unlike backups, commits are instant and lightweight - think the equivalent of git commit but for data.

3. Checkout

Jump to any branch or commit instantly. Made a mistake? Checkout the previous commit. Want to compare strategies? Checkout different branches side-by-side.

// Traditional approach - risky!
await prisma.item.updateMany({
  data: { price: { multiply: 10 } } // OOPS! Should be "add"
})
// Now you need backup recovery...

// With Guepard - safe!
// 1. Create branch for testing
// 2. Test the change
// 3. If wrong, just delete the branch
// 4. If right, merge to main
Enter fullscreen mode Exit fullscreen mode

Visual branch history - just like Git, but for your database

For The Apothecary, I built four real-world scenarios to demonstrate these concepts and guepard capabilities.


Four Real-World Scenarios

Scenario 1: Testing a Promotion Without Fear

The Use Case: Marketing wizards want to test a "15% off ingredients" promotion called "Lunar Blessing."

In a traditional setup, I'd see myself either:

  • Test in staging (which never perfectly matches production)
  • Deploy to production and cross my fingers
  • Set up complex feature flags

Dragon's Breath Essence showing 15% discount on the lunar-blessing-test branch

With Guepard, I created a branch called lunar-blessing-test in the web dashboard, checked out, and ran my seed script:

# Dashboard: Create branch "lunar-blessing-test" → Checkout
npm run seed:lunar-blessing
npm run dev
Enter fullscreen mode Exit fullscreen mode

Within seconds, I had in my disposition a complete copy of production data with the promotion active. I found myself being able to freely test the pricing, logic, verify how the UI was rendered, and test orders without messing with the main branch. When done, i could easily checkout to the main branch. Zero risk.

Scenario 2: Instant Recovery from Disasters

The Use Case: The apothecary has been cursed with a 10x pricing disaster! Let's revert it safely.

The cursed-batch seed multiplies all prices by 10

I created a commit called "before-price-update", then ran the cursed batch script that multiplies all prices by 10:

// seed-cursed-batch.ts
await prisma.item.updateMany({
  data: {
    price: {
      multiply: 10  // Simulating a pricing disaster
    }
  }
})
Enter fullscreen mode Exit fullscreen mode

In a real-life scenario, this would be catastrophic:
Prices exploded and everyone is sweating profusely. With Guepard? I opened the dashboard, found the "before-price-update" commit, clicked "Checkout." Three clicks, five seconds and magically the database was restored perfectly.

Restoring from commit in the Guepard dashboard

Scenario 3: Parallel Testing

The Use Case: Test two discount strategies simultaneously: 20% vs 25% off potions.

I created two branches: solstice-20-percent and solstice-25-percent. By checking out each branch, I could run the app and see different data states. In a real application, one would process test orders on both cases and compare metrics to deduce the best strategy empirically.

This is impossible with traditional databases unless you maintain separate servers. Guepard made it trivial.

Scenario 4: Schema Evolution Without Downtime

The Use Case: Add a customer reviews feature (new Review model) without disrupting the main site.

Review system tested in isolation

// schema-with-reviews.prisma (new model)
model Review {
  id        String   @id @default(cuid())
  itemId    String
  rating    Int      // 1-5 stars
  comment   String
  author    String
  item      Item     @relation(fields: [itemId], references: [id])
}
Enter fullscreen mode Exit fullscreen mode

I created a reviews-feature branch, switched my Prisma schema, pushed the migration, seeded 20 fantasy character reviews, checked everything worked as inteded. As easy as that. what if it had broken something? no harm done.


Technical Choices & Architecture

I built The Apothecary with Next.js 14 using the App Router, TypeScript for type safety, and Prisma as the ORM. The UI uses TailwindCSS for that medieval aesthetic - dark backgrounds, gold accents, serif fonts that feel ancient.

For state management, I chose Zustand to handle the shopping cart. No need for Redux complexity.

// store/cart.ts - Simple cart management with Zustand
export const useCartStore = create<CartState>((set) => ({
  items: [],
  addItem: (item) => set((state) => ({
    items: [...state.items, item]
  })),
  removeItem: (id) => set((state) => ({
    items: state.items.filter(i => i.id !== id)
  })),
}))
Enter fullscreen mode Exit fullscreen mode

The database schema is quite simple: Items, Promotions, Orders. Later I added Reviews as a way to successfully demonstrate schema evolution. Each model uses Prisma's type-safe queries and no raw SQL was needed.


Key Learnings

1. Database Branches Change Everything

The shift I noticed in my approach is astonishing. Once you can branch your database, you feel as if a heavy burden was lifted off your back. Experiments become cheap. Testing becomes thorough. Mistakes become trivial.

2. Recovery Shouldn't Be Stressful

Traditional backup/restore is quite the Sisyphean task. It is slow and stress-inducing. Guepard's commit system is quite the game changer: instant, confident, reversible.

3. Staging Never Matches Production

This project, even with its small scope, already proves that staging environments drift from production. Testing directly on a production branch alleviates confusion and ensures compatibility. It is a huge time saver.

4. Schema Migrations Need Isolation

Adding the Reviews feature in an isolated branch meant I could test to my heart's content: schema changes, data seeding, UI updates without laying a finger on main. This is how schema changes should work.

Multiple database environments running in parallel


What's Next?

This project enlightened me on the importance of database versionning. It is a huge shsift from how we approach data. Although Guepard is still in its early days, the concept is already proven. I can't express how excited i am to see ambitious tools like this mature. One can only imagine the endless possibilities such as data branches in CI/CD pipelines and automated testing on production-clone branches

The Apothecary: Where medieval fantasy meets modern database practices
If you're building something with databases(who isn't?). I wholeheartedly encourage you to explore versioning tools like Guepard. Start simple, try projects like this. Nothing beats the peace of minds that comes neatly packaged with it.


Try It Yourself

The entire project is open source. Feel free to clone it, break it and fix it as easily with a quick checkout. That's the beauty of versioned databases.


Final Thoughts: The Paradigm Shift

Building The Apothecary struck me deep: our approach to databases is archaic! We've easily accepted the inherent risk and irreversibility of data even though it is a limitation of our tools, not a property of databases themselves. Database versioning addresses the elephant in the room: Given the ability to branch, commit and rollback as easily as code, the entire development approach transforms. The fear of testing is alleviated. Experimentation becomes accessible. Recovery becomes instant. More than a niche feature, it is rather a new perspective on data infrastructure


What database challenges have you faced? Have you tried database versioning tools? Share your experiences in the comments!

Top comments (0)