What you'll learn
Persistence code must evolve without forcing routes and business rules to understand SQL history. Define repository contracts, version schema changes, seed repeatable data, and plan backward-compatible releases.
By the end of this lesson, you'll be able to:
- Design repositories around use cases
- Write ordered migrations
- Test adapters against PostgreSQL
- Sequence zero-downtime schema changes
Core mental model
Node.js becomes easier when you separate the JavaScript language from the runtime and the operating-system capabilities it exposes. Use this table as a decision guide.
| Concept | What it means | Decision rule |
|---|---|---|
| Repository | An application-facing persistence port | Expose domain operations, not a generic database wrapper |
| Migration | A versioned schema transition | Keep each change deterministic, reviewed, and deployed once |
| Expand-contract | Add compatibility before removing old shape | Use when application versions overlap |
Professional workflow
Build and verify Node.js programs from the terminal in small, observable steps.
- Define the data-access boundary boundary: inputs, outputs, invariants, ownership, and expected failures.
- Design the data or message contract before choosing implementation details.
- Implement the smallest correct path with dependencies passed explicitly.
- Add validation, failure translation, cleanup, and concurrency behavior.
- Verify the boundary with realistic data and at least one adversarial case.
- Measure or observe the behavior before optimizing or extracting abstractions.
Keep the feedback loop short
Guided code lab
Keep the contract domain-focused
The service depends on behavior while PostgreSQL stays an adapter detail.
export function createTaskService({ tasks, clock, ids }) {
return {
async create(ownerId, input) {
const task = { id: ids.next(), ownerId, title: input.title, createdAt: clock.now() };
return tasks.insert(task);
},
};
}Expand before enforcing
Large live tables often need add, backfill, dual-write, and enforcement as separate releases.
ALTER TABLE tasks ADD COLUMN priority text;
-- Backfill in bounded batches; deploy writers; then enforce:
ALTER TABLE tasks ALTER COLUMN priority SET DEFAULT 'normal';
ALTER TABLE tasks ALTER COLUMN priority SET NOT NULL;Production practice
Contract
Repository interfaces express domain behavior; migrations express stable production history.
Verification
Apply every migration from empty, upgrade an old snapshot, run repository contract tests, and verify rollback strategy.
Operations
Serialize migrators, back up high-risk changes, avoid long locks, and record the deployed schema version.
Common failure mode
Independent workshop
Extract task persistence and introduce priorities without downtime.
Your finished workshop must include:
- Repository interface
- PostgreSQL adapter
- Ordered migration
- Deterministic seed
- Contract tests
- Deployment note
Definition of done
Recap & quick check
Key takeaways
- Repositories protect boundaries
- Migrations are immutable history
- Seeds are deterministic
- Test the real engine
- Compatibility spans releases
Quick check
1. What should a repository expose?
2. How do you correct an applied migration?
3. Why expand before contract?
Next: MongoDB & Document Modeling