Stop Vibe Coding: Design Your DB
How to design a database before writing a single line of backend code
Gajanan Rathod

Most people start projects like this:
> I’ll just set up auth real quick and figure the DB later.
That “later” is where projects go to die.
If you’re building anything **serious** which ain't a toy database design is not optional. It’s the backbone. If you mess it up, your code *will* fight you later.
This post walks through **how I design a database before writing a single line of backend code**, using a real project I’m building: **AutoMix**, an audio mixing platform.
**Why Database Design Comes First**
Code can be refactored. UI can be redesigned. Bad database design? That’s pain, migrations, downtime, and regrets.
When you design your DB early:
* Your APIs become clearer * Your business logic simplifies * Your system scales cleaner * You stop guessing while coding
No vibe coding. Only intentional building.
**Step 1: Understand the Problem (Properly)**
Before tables, before diagrams write the **problem statement in plain English**.
AutoMix – Problem Statement
> AutoMix is an audio mixing platform where users can: > > * Create an account using email and password > > * Create multiple projects > > * Upload vocals, beats, and reference tracks > > * Trigger backend processing to mix audio > > * Receive final rendered audio outputs >
This doesn’t need to be fancy. It needs to be **clear**.
If you can’t explain your product simply, you’re not ready to design anything yet.
---
Step 2: Break the Problem into Entities
Now we ask one question repeatedly:
> **What “things” exist in this system?**
From the problem statement, we get:
* **Users** – people using the platform * **Projects** – containers for work * **Assets** – uploaded audio files * **Processing Jobs** – backend tasks * **Renders** – final mixed outputs
These are your **core entities**.
No tables yet. Just clarity.
---
Step 3: Decide Your Tables
Each entity usually becomes a table.
For AutoMix, we’ll have:
* `users` * `projects` * `assets` * `jobs` * `renders`
Simple. Clean. No over engineering.
---
Step 4: Design Each Table (Column by Column)
Now we zoom in and design **what each table stores**.
Users
Every project starts with users.
```plaintext users - id (PK) - email (unique) - password_hash - created_at ```
Projects
Each user can own multiple projects.
```plaintext projects - id (PK) - owner_id (FK → users.id) - name - no_of_renders - completed - created_at ```
Assets
Audio files uploaded inside a project.
```plaintext assets - id (PK) - project_id (FK → projects.id) - filename - filetype - duration - storage_key - uploaded_at ```
Jobs
Each processing request becomes a job.
```plaintext jobs - id (PK) - project_id (FK → projects.id) - requested_by (FK → users.id) - status - started_at - ended_at - created_at ```
Renders
Final outputs generated by jobs.
```plaintext renders - id (PK) - project_id (FK → projects.id) - job_id (FK → jobs.id) - storage_key - filename - filetype - duration - created_at ```
---
Step 5: Define Relationships Clearly
This is where many beginners mess up.
Let’s explicitly define relationships:
* A **user** owns many **projects** * A **project** has many **assets** * A **project** has many **jobs** * A **job** produces one **render**
Foreign key mappings:
```plaintext projects.owner_id → users.id assets.project_id → projects.id jobs.project_id → projects.id jobs.requested_by → users.id renders.project_id → projects.id renders.job_id → jobs.id ```
Once this is clear, your API routes basically design themselves.
---
Step 6: Visualize with ER Diagrams
Before coding, visualize.
Tools I recommend:
* [**Eraser.io**](http://Eraser.io) (my personal favorite) * Lucidchart * Mermaid
Here’s the exact schema I used in Eraser for AutoMix 👇 *(you can paste this directly into Eraser)*
```plaintext // AutoMix Database Schema
users [icon: user, color: blue] { id string pk email email notnull, unique password_hash string notnull created_at timestamp notnull }
projects [icon: gcp-project, color: red] { id string pk owner_id string fk notnull name string notnull no_of_renders int notnull completed boolean notnull created_at timestamp notnull }
assets [icon: audio, color: green] { id string pk project_id string fk notnull filename string notnull filetype enum('wav','mp3','flac','aac','other') notnull duration float storage_key string notnull uploaded_at timestamp notnull }
jobs [icon: k8s-job, color: green] { id string pk project_id string fk notnull requested_by string fk notnull status enum('queued','processing','succeeded','failed') notnull started_at timestamp ended_at timestamp created_at timestamp notnull }
renders [icon: upload, color: green] { id string pk project_id string fk notnull job_id string fk unique storage_key string notnull filename string filetype enum('wav','mp3','other') notnull duration float created_at timestamp notnull }
// Relationships projects.owner_id > users.id assets.project_id > projects.id jobs.project_id > projects.id jobs.requested_by > users.id renders.project_id > projects.id renders.job_id > jobs.id ```
---
Step 7: Iterate Before You Code
This schema is **not final** and that’s okay.
You should:
* Re-read it * Question every column * Add constraints * Remove redundancy * Think about edge cases
Once it feels clean **on paper**, *then* you write code.
---
Final Thoughts
If you skip database design, you’re not moving fast you’re just delaying failure.
Good database design:
* Makes backend logic boring (that’s good) * Reduces bugs * Saves weeks of refactoring * Forces you to understand your own product
Stop vibe coding. Start designing.
If this helped you, save it! future you will thank you.