5. Add data with a clear owner
Move from a teaching Map toward deliberate persistence and correctly scoped caches.
Your goal#
Identify where data lives, what survives a restart, and how to choose the next integration without accidentally exposing one user's data to another.
Memory is useful, with a boundary#
const items = new Map<string, string>();
/** Save one teaching item. @param id Application-selected identifier. @param title Validated display text. @returns Nothing. Mutates only the process-local Map. */
export function remember(id: string, title: string): void {
if (!items.has(id) && items.size >= 100) throw new Error('Teaching store is full');
items.set(id, title);
}
/** Read one teaching item. @param id Identifier. @returns Stored title or undefined. Reads process-local memory only. */
export function recall(id: string): string | undefined {
return items.get(id);
}This store is bounded, but not durable or tenant-safe. It is not a database, not shared across replicas and not automatically saved by the framework. A restart/reload can discard it. Use it only to understand a small teaching flow.
Choose a real persistence layer#
| Need | Start with |
|---|---|
| Understand named connections and startup ownership | Database configuration |
| Use queries/transactions deliberately | Database queries |
| Select a concrete engine | SQL core, PostgreSQL, MySQL, SQLite, or MongoDB |
| Cache repeated public/scoped computation | Cache setup |
A database choice is a product/deployment decision. Follow the adapter's actual options, migrations and authorization requirements rather than pasting a universal connection example. Confirm the manager was initialized before using it. Disabled/degraded service state should not look like a successful write.
A cache has a different job#
A cache keeps a reusable result temporarily. It should not become the only copy of an important record unless the storage contract explicitly supports that use. Choose TTLs, maximum sizes and ownership. Include every response-changing dimension in the key.
const tenantId = 'verified-tenant';
const locale = 'fr';
const revision = 'catalog-3';
const key = `public-product-list:${tenantId}:${locale}:${revision}`;The identifiers above are illustrative public values. In an application, derive the tenant from verified authorization and validate locale against an allowlist. A user-supplied tenant ID is not proof of membership. Do not put confidential data in a public shared cache or cache key.
Verify failure and lifecycle behavior#
What happens if the database is unavailable? Who starts the connection? Who closes it on reload? Can a retried write happen twice? A successful connection test does not prove transaction behavior, tenant isolation or idempotency.
Start with a small explicit requirement. For the notes tutorial, durable private notes require an authenticated owner and a persistence design. Do not simply replace Map with a database call and declare the entire application production-ready.
Try it yourself#
Draw where the tutorial's notes live in one process and in two replicas. Restart the teaching server and observe the data loss. Then choose an adapter guide and write down the startup owner, required secret names, durability requirement and authorization boundary—without adding real credentials to source.
You are done when: you can distinguish durable storage, process-local state and a cache, and explain which owner may access each record.
PUT IT INTO PRACTICE
Check your understanding
Try the exercise above, then answer both questions. Each correct answer earns 10 local learning points, once. You can retry; all lessons stay open.