What you'll learn
Replace file persistence with a production-shaped data layer. Combine PostgreSQL migrations, repositories, transactions, Redis, integration tests, and operational lifecycle into one coherent task API.
By the end of this lesson, you'll be able to:
- Deliver a migrated schema
- Preserve service contracts
- Protect multi-step writes
- Add and verify a safe cache
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 |
|---|---|---|
| Source of truth | The system owning durable state | Keep PostgreSQL authoritative and Redis disposable |
| Adapter | Infrastructure implementing an application port | Inject it at the composition root |
| Acceptance evidence | Proof that requirements hold | Cover correctness, concurrency, recovery, and operability |
Professional workflow
Build and verify Node.js programs from the terminal in small, observable steps.
- Define the durable API 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
Compose infrastructure at startup
Routes receive services; services receive ports; adapters own infrastructure.
const tasks = createPostgresTaskRepository({ pool });
const cached = createCachedTaskRepository({ tasks, redis, ttlSeconds: 60 });
const service = createTaskService({ tasks: cached, clock, ids });
const app = createApp({ taskService: service });
const server = app.listen(config.port);
installShutdown({ server, close: () => Promise.all([pool.end(), redis.quit()]) });Test the complete contract
This crosses HTTP, service, repository, and a real database.
test('creates and lists an owner task', async () => {
const created = await api.post('/tasks', token, { title: 'Ship phase 3' });
assert.equal(created.status, 201);
const listed = await api.get('/tasks?status=open', token);
assert.equal(listed.body.items[0].id, created.body.id);
});Production practice
Contract
The public HTTP contract stays stable while JSON storage is replaced by database and cache adapters.
Verification
Run migrations from empty, all API tests, concurrent writes, Redis outage tests, query plans, and shutdown checks.
Operations
Document configuration, migration order, pool budgets, backups, health semantics, cache metrics, and recovery commands.
Common failure mode
Independent workshop
Complete and present the Phase 3 data-backed task API.
Your finished workshop must include:
- Migration-managed schema
- Parameterized repository
- Transactional operation
- Redis fallback
- End-to-end tests
- Operations README
Definition of done
Recap & quick check
Key takeaways
- Boundaries make adapters replaceable
- PostgreSQL owns truth
- Transactions protect invariants
- Redis accelerates
- Evidence completes features
Quick check
1. Where should adapters be assembled?
2. What if Redis is unavailable?
3. What proves migration health?
Next: Web Security & Threat Modeling