Free Checklist · 2025

Database Design Checklist

A complete pre-production database checklist covering schema design, data types, relationships, indexing, security, performance, migrations, backups, and scalability — 120+ items across 10 categories.

▸ How to use this checklist

Work through each category before your database goes into production. Critical items must be addressed before any user data enters the system. High items should be done before launch. Medium items should be completed in your first sprint post-launch. Database design mistakes made at the start are exponentially harder to fix at scale.

120+

Checklist Items

10

Categories

PostgreSQL

Primary Focus

Free

No signup needed

Priority Key:
Critical
High
Medium
120+ items · 10 categories · PostgreSQL focused
Before You Start

6 Database Mistakes That Cost You Later

These are the most common and expensive database design errors. Most are trivial to prevent upfront and catastrophic to fix in production.

CriticalStoring money as FLOAT

Impact

Rounding errors cause incorrect billing — $99.99 becomes $99.98999... in calculations

Fix

Use INTEGER (cents) or NUMERIC(19,4) for all monetary values

CriticalMissing indexes on foreign keys

Impact

JOIN queries do full table scans — a query that runs in 5ms at 1k rows takes 30 seconds at 1M rows

Fix

Always index foreign key columns in PostgreSQL — it does not auto-create them

CriticalNo row-level security

Impact

A single application bug can expose all users' data to any other user

Fix

Enable RLS on all tables and write policies before any user data enters the database

HighUsing timestamp without timezone

Impact

Daylight saving transitions cause duplicate or missing hours in time-based queries

Fix

Always use timestamptz — store in UTC, display in user's local timezone

CriticalStoring passwords in plain text

Impact

Any database exposure immediately compromises every user's password

Fix

Never store passwords. Use hashed authentication via Supabase Auth or bcrypt

HighNo soft delete strategy

Impact

Hard deletes break referential integrity and make data recovery impossible

Fix

Add deleted_at timestamptz column to all user-facing tables and filter in queries

Quick Reference

Data Type Selection Guide

Choose the right PostgreSQL data type for every common use case. The wrong type causes bugs that are invisible until they become production incidents.

Use Case
Recommended Type
Avoid
Reason
Primary key (user-facing)
uuid
serial / bigserial
UUIDs are non-enumerable and safe in URLs
Primary key (internal only)
bigint (generated always)
int
Bigint handles billions of rows; int maxes at 2B
Money / prices
integer (store cents)
float, double precision
Float arithmetic causes rounding errors in billing
Timestamps
timestamptz
timestamp (no TZ)
TZ-aware prevents DST and multi-region bugs
Dates only (no time)
date
timestamp, text
date type has date-specific functions and correct sorting
Boolean flags
boolean NOT NULL DEFAULT false
integer (0/1), text
Boolean is explicit and prevents invalid values
Email addresses
text + CHECK constraint
varchar(255)
Emails can be longer; constraint validates format
Status / state fields
text + CHECK constraint or enum
integer codes
Text values are self-documenting in queries
JSON / flexible data
jsonb
json, text
JSONB is binary, indexable, and queryable
File / image URLs
text (store URL only)
bytea (file content)
Files in object storage; only URL in DB
🗂️
01
Category

Schema Design & Naming Conventions

A well-named, consistently structured schema prevents confusion, reduces bugs, and makes onboarding new developers significantly faster. Establish these conventions before writing a single table.

14Items
All table names use snake_case plural nouns (e.g. users, project_briefs, invoice_line_items)Critical
All column names use snake_case (e.g. created_at, first_name, stripe_customer_id)Critical
Every table has a primary key named id — no exceptionsCritical
Every table has created_at (timestamptz, default now()) and updated_at (timestamptz) columnsCritical
Foreign key columns are named {referenced_table_singular}_id (e.g. user_id, project_id)Critical
Boolean columns use positive names (is_active, has_paid) — never double negatives (is_not_deleted)Critical
Enum columns use a consistent naming pattern — either a database enum type or a constrained text columnHigh
Tables for many-to-many relationships are named {table_a}_{table_b} in alphabetical order (e.g. project_tags)High
Column names are self-explanatory — avoid abbreviations (use description not desc, quantity not qty)High
Timestamps always use timestamptz (with timezone) not timestamp — prevents timezone bugsHigh
All tables and columns have inline SQL comments explaining their purpose if not obviousHigh
A schema README or ERD diagram exists and is kept up to dateMedium
Deleted records use soft delete via deleted_at timestamptz column — not permanent hard deletesMedium
Version or revision columns use integer type with default 1 and increment on updateMedium
6 critical · 5 high · 3 medium
🔢
02
Category

Data Types & Constraints

Choosing the right data type for each column prevents entire classes of bugs. Constraints enforce business rules at the database level — the safest place to enforce them.

14Items
Primary keys use uuid (UUIDv4 or UUIDv7) for user-facing entities — not sequential integers that are enumerableCritical
Email columns use text with a CHECK constraint enforcing format — not varchar(255) with no validationCritical
Monetary values stored as integer (cents) or numeric(19,4) — never float or double precision (rounding errors)Critical
NOT NULL constraints applied to all columns that should never be nullCritical
CHECK constraints enforce valid ranges and business rules (e.g. quantity > 0, status IN ('active', 'inactive'))Critical
UNIQUE constraints applied to columns that must be unique (email, username, public_token)Critical
Text columns with fixed valid values use CHECK constraints or enum types — not free-form textHigh
URL columns validated with a CHECK constraint (starts with http:// or https://)High
Phone number columns stored as text — not integer (loses leading zeros and international formats)High
JSON/JSONB columns used only for genuinely flexible data — not as a substitute for proper relational designHigh
Array columns (PostgreSQL) used only for simple list data that does not need to be queried individuallyHigh
Default values defined for columns that should always have a value (e.g. status DEFAULT 'active', count DEFAULT 0)Medium
Binary data (files, images) stored in object storage (S3/Supabase Storage) with only the URL in the databaseMedium
Text columns have appropriate length limits via CHECK constraints where business rules applyMedium
6 critical · 5 high · 3 medium
🔗
03
Category

Relationships & Referential Integrity

Foreign key constraints enforce relationships between tables at the database level. Without them, orphaned records accumulate silently and corrupt your data over time.

11Items
All foreign key columns have explicit FOREIGN KEY constraints — not just application-level enforcementCritical
ON DELETE behaviour explicitly defined for every foreign key (CASCADE, SET NULL, or RESTRICT)Critical
ON DELETE RESTRICT used for critical relationships where orphan records would be catastrophicCritical
ON DELETE CASCADE used only when child records are meaningless without the parent (e.g. deleting a user deletes their sessions)Critical
ON DELETE SET NULL used for optional relationships where the child record should survive (e.g. deleted author on a blog post)Critical
Many-to-many relationships use a dedicated junction table — not a JSON array in a columnHigh
Self-referential tables (e.g. category trees, comment threads) have parent_id nullable foreign key to same tableHigh
Circular foreign key dependencies explicitly documented and avoided where possibleHigh
All foreign key columns are indexed (PostgreSQL does NOT auto-index FK columns unlike MySQL)High
Referential integrity verified in staging environment by attempting to insert invalid foreign key valuesMedium
Relationship cardinality documented in ERD (one-to-one, one-to-many, many-to-many)Medium
5 critical · 4 high · 2 medium
04
Category

Indexing Strategy

Indexes are the single biggest lever for database query performance. Missing indexes on frequently queried columns cause queries to do full table scans — unacceptable at scale.

14Items
Primary key indexes exist on all tables (automatic for most databases)Critical
All foreign key columns have a B-tree index (PostgreSQL requires manual creation — it does NOT auto-index FKs)Critical
Columns used in frequent WHERE clauses are indexedCritical
Columns used in ORDER BY on large tables are indexedCritical
UNIQUE indexes applied to all columns with uniqueness constraints (email, username, api_key)Critical
Composite indexes created for multi-column queries that always filter on the same combination of columnsHigh
Partial indexes used for filtered queries (e.g. CREATE INDEX ON proposals WHERE deleted_at IS NULL)High
GIN indexes used for full-text search columns (tsvector) or JSONB columnsHigh
Index names follow a consistent convention (idx_{table}_{column} or idx_{table}_{col1}_{col2})High
EXPLAIN ANALYZE run on all critical queries — no sequential scans on tables over 10k rowsHigh
Indexes reviewed for columns with very low cardinality (boolean columns rarely benefit from an index)Medium
Unused indexes identified and removed (they slow down INSERT/UPDATE operations for no gain)Medium
pgvector index (IVFFlat or HNSW) created on embedding columns if AI vector search is usedMedium
Index bloat monitored — REINDEX scheduled after high-volume batch operationsMedium
5 critical · 5 high · 4 medium
🧩
05
Category

Normalisation & Data Integrity

Normalisation eliminates data duplication and update anomalies. Most production SaaS databases should aim for Third Normal Form (3NF) with pragmatic denormalisation only where performance requires it.

10Items
No repeating groups in a single column — data that could be multiple values lives in a related tableCritical
Each table represents exactly one entity — user data and order data are not in the same tableCritical
No transitive dependencies — columns depend only on the primary key, not on other non-key columnsCritical
Addresses stored in structured columns (street, city, state, country, postal_code) — not as a single text blobCritical
Calculated or derived values are computed at query time, not stored as columns (avoid stale cached values)High
Denormalisation is documented with a clear rationale and the update strategy that keeps denormalised copies freshHigh
Lookup/reference data (countries, currencies, categories) lives in its own table — not hardcoded in application codeHigh
User-generated content stored in dedicated content tables — not embedded in the users tableHigh
Reporting queries that require many joins are served by a materialised view or read replicaMedium
Audit/history tables duplicate key data intentionally for point-in-time accuracy — this denormalisation is acceptableMedium
4 critical · 4 high · 2 medium
🔒
06
Category

Security & Access Control

Database security failures are catastrophic and irreversible. These controls must be in place before any user data enters the database.

14Items
Row-level security (RLS) enabled on all tables containing user data — enforced at database layer, not just applicationCritical
RLS policies tested by attempting to query another user's data using their JWT — access must be deniedCritical
Database connection strings stored in environment variables — never committed to version controlCritical
Application database user has minimum required privileges — not superuser or ownerCritical
Passwords never stored in plain text — use Supabase Auth, or bcrypt/argon2 with cost factor ≥ 12Critical
Sensitive data columns (PII, payment info) identified and documented in a data mapCritical
PII fields encrypted at rest where regulation requires (HIPAA, GDPR sensitive categories)Critical
Database connection uses SSL/TLS — unencrypted connections rejectedHigh
API keys and secrets stored as hashed values in the database — only the hash is stored, not the raw valueHigh
Audit log table records all data mutations to sensitive tables (who changed what and when)High
Database access limited to application server IPs via firewall rules — not publicly accessibleHigh
Separate read replica credentials used for analytics and reporting queriesHigh
Data retention policy defined — records older than X period archived or deleted per compliance requirementsMedium
Database user activity logging enabled for production — queries logged for security auditMedium
7 critical · 5 high · 2 medium
🚀
07
Category

Performance & Query Optimisation

Performance problems that appear at 10,000 rows become critical at 1,000,000 rows. Design for scale from day one — it is far cheaper than a migration under load.

13Items
All queries touching tables over 10,000 rows reviewed with EXPLAIN ANALYZE — no Seq Scans without justificationCritical
N+1 query patterns eliminated — ORM batch loading (Supabase .select with joins) used where applicableCritical
Pagination implemented on all list endpoints — no endpoint returns unbounded result setsCritical
Connection pooling configured (PgBouncer or Supabase connection pooler) — not new connection per requestHigh
Database query response time P99 under 200ms for all critical queries at target loadHigh
Large batch operations use chunked processing — not a single query that locks the tableHigh
Slow query log enabled — queries over 100ms flagged for reviewHigh
Materialised views used for expensive aggregation queries that run frequentlyHigh
Database statistics updated regularly (ANALYZE) so query planner makes optimal decisionsHigh
Read-heavy tables on a read replica — analytical queries do not compete with write trafficMedium
Table partitioning applied to time-series tables over 100 million rows (e.g. events, logs)Medium
Vacuum settings tuned for high-update tables — autovacuum keeping dead tuple count lowMedium
Column statistics targets increased on high-cardinality columns used in complex query plansMedium
3 critical · 6 high · 4 medium
🔄
08
Category

Migrations & Schema Changes

Database migrations are irreversible in production. Every schema change must be reviewed carefully, tested thoroughly, and deployed with a rollback strategy.

12Items
All schema changes are made via versioned migration files — never manual SQL in productionCritical
Migrations are idempotent — running them twice produces the same resultCritical
Every migration has a corresponding rollback/down migrationCritical
All migrations tested on a production data snapshot in staging before deploying to productionCritical
Column renames done in three stages: add new column → backfill data → drop old column (avoids downtime)Critical
Adding a new NOT NULL column done with a default value OR in two steps (add nullable → backfill → add NOT NULL constraint)High
Large table migrations (millions of rows) done with CONCURRENTLY where possible to avoid locksHigh
Migration run time estimated before deploying — operations over 30 seconds planned for maintenance windowHigh
Zero-downtime deployment strategy documented for schema changes that affect running application codeHigh
Migrations reviewed by a second developer before merging to main branchHigh
Migration history tracked in a migrations table — applied migrations recorded with timestampMedium
Database migration tool used consistently (Supabase CLI, Prisma Migrate, Flyway) — no ad hoc scriptsMedium
5 critical · 5 high · 2 medium
💾
09
Category

Backups & Disaster Recovery

Backups are only useful if they work. Every item in this section must be verified through a real restore test — not just assumed to be working.

12Items
Automated daily full database backups configured with at minimum 7-day retentionCritical
Backup restore tested successfully in staging — a backup never tested is a backup you cannot trustCritical
Point-in-time recovery (PITR) enabled for production database — Supabase Pro and above provides thisCritical
Recovery Time Objective (RTO) defined — how long can the product be down? Maximum acceptable: 4 hoursCritical
Recovery Point Objective (RPO) defined — how much data can be lost? Maximum acceptable: 24 hoursCritical
Backups stored in a separate geographic region from the primary databaseHigh
Backup access is restricted — not accessible with the same credentials as the application databaseHigh
Disaster recovery runbook written and accessible to the team — step-by-step guide to restore from backupHigh
Backup success/failure monitored and alerting in place — failures reported within 1 hourHigh
Monthly restore drill conducted — team practices the recovery procedure to ensure it works and stays currentMedium
Database export in standard format (pg_dump) as an additional backup alongside managed backupsMedium
Backup encryption verified — backups encrypted at rest and in transitMedium
5 critical · 4 high · 3 medium
📈
10
Category

Scalability & Future-Proofing

Design decisions made at 1,000 rows affect you at 100,000,000 rows. Build with growth in mind from the start — retrofitting scalability is expensive.

12Items
Schema designed to support 100× current data volume without structural changesCritical
Multi-tenant architecture uses a single database with RLS tenant isolation — not one database per tenant (until very large scale)Critical
Sharding strategy documented for tables expected to exceed 1 billion rowsHigh
Time-series data (events, logs, analytics) partitioned by date range from day oneHigh
Archive strategy defined for data older than a defined period — cold storage or archival tablesHigh
Database vertical scaling path documented — next instance size defined before it is urgently neededHigh
Read replicas provisioned or planned before read traffic exceeds primary database capacityHigh
Caching strategy documented for hot data — Redis or application-level cache for frequently read rowsHigh
Database connection limits understood — connection pooling required before 100 concurrent application processesMedium
Load test run against database with simulated peak traffic — bottlenecks identified before productionMedium
Database monitoring dashboards configured — CPU, memory, disk I/O, connection count, query performance all visibleMedium
Data growth projections calculated — estimated database size at 6 months, 12 months, and 24 monthsMedium
2 critical · 6 high · 4 medium
Built by 4Byte

How 4Byte designs production databases

This checklist is built from our experience designing and shipping databases for 45+ production SaaS products, web applications, and AI systems. Every item on this list comes from a real-world lesson — many of them learned the hard way on client projects.

We use PostgreSQL via Supabase as our default database for every project. Row-level security, proper indexing, UUID primary keys, and automated backups are set up from day one — not added later.

PostgreSQL via Supabase on every production build
RLS, indexes, and constraints configured before first user
pgvector for AI embeddings — no separate vector database needed
Migration-first workflow — no ad hoc SQL in production, ever
45+

Production Databases

Shipped and battle-tested

Zero

Data Breaches

RLS from day one, every time

99.9%

Uptime Target

With PITR backup on all builds

≤ 4hrs

Response Time

On all new enquiries

FAQ

Frequently Asked Questions

What should a database design checklist include?+

A complete database design checklist should cover schema design and naming conventions, data type selection, primary and foreign key constraints, indexing strategy, normalisation level, row-level security, data validation and constraints, backup and recovery configuration, performance optimisation, audit trails, and scalability planning. Each item should be verified before a database goes into production.

What are the most common database design mistakes?+

The most common database design mistakes include using generic column names like 'data' or 'value', missing indexes on frequently queried foreign key columns, storing sensitive data without encryption, not using constraints to enforce business rules at the database level, over-normalising to the point of requiring too many joins, under-normalising leading to data duplication, missing created_at and updated_at timestamps, and not planning for soft deletes.

Should I use UUID or integer for primary keys?+

For most SaaS applications, UUID (specifically UUIDv4 or UUIDv7) is the better choice. UUIDs can be generated client-side, do not expose sequential ID information to users, are safe to expose in URLs, and prevent enumeration attacks. Integer IDs are faster for indexing and joins, and are preferred for internal tables with very high insert volumes where sequential ordering matters.

What is row-level security and when should I use it?+

Row-level security (RLS) is a database feature that restricts which rows a user or role can access, enforced at the database level rather than the application layer. Use RLS whenever multiple users share a database and should only see their own data — which is virtually every SaaS application. RLS prevents bugs in application code from accidentally exposing other users' data.

How many indexes should a table have?+

A table should have indexes on its primary key (automatic), all foreign key columns, and any columns used in WHERE clauses, ORDER BY, or JOIN conditions in frequent queries. Avoid indexing columns with low cardinality (like boolean flags). As a general rule, read-heavy tables benefit from more indexes; write-heavy tables should have fewer indexes to avoid insert/update overhead.

What is the difference between soft delete and hard delete?+

A hard delete permanently removes a row from the database with a DELETE statement. A soft delete sets a deleted_at timestamp column to the current time, leaving the row in the database but filtering it from queries with a WHERE deleted_at IS NULL condition. Soft deletes are preferred for user data, audit trails, and compliance — they allow data recovery and maintain referential integrity.

Related Resources

More Templates & Guides

TemplatePRD TemplateView resource →ChecklistSaaS Launch ChecklistView resource →ComparisonPostgreSQL vs MongoDBView resource →ComparisonSupabase vs FirebaseView resource →
Open for new projects

Want a database designed right from the start?

We design and build production-grade PostgreSQL databases for SaaS products, web apps, and AI systems. Every item on this checklist is handled before your first user signs up. Free strategy call — response in under 4 hours.

Book a Free Strategy Call →PostgreSQL vs MongoDB →
📞100% Free Strategy Call
Response in ≤ 4 Hours
🛡️No Obligation
🚀45+ Products Shipped