Basically, till now, we figured out how to store data. Then we figured out the contracts which help us update the data. And now, we are creating systems that can help us display and capture changes to the data.
Layer 0: Database Schema
“How do we STORE data?”
- Tables, relationships, constraints
- Single source of truth for DATA
- Rules: no duplicates, no computed values, timestamps everywhere
↓
Layer 1: API Contracts
“How do we UPDATE data?”
- Endpoints, requests, responses, validations
- Contract between frontend and backend
- Rules: specific actions, specific request formats, specific response formats
- Ensures updates reach the database correctly
↓
Layer 2: State Management
“How do we DISPLAY data and CAPTURE changes?”
- What data does each page need?
- When do we fetch it?
- How do we track user actions locally?
- How do we sync those changes back to the backend?
- Rules: single source of truth in frontend too, no data duplication, everything flows through the store
State management = contract about UI DATA FLOW (UI ↔ store ↔ backend) and I think this is where vibe coding falls through because AI is bad at UI, so it is not able to generate the pages properly. Thus, it is not able to generate the list of demand properly and thus it is not able to use the API points that exist. So, it copes up with it by creating duplicate end points or just hardcoding dummy data. The lack of robust planning at UI leads you to having a failed state management. With leads to users taking action, but database not being updated, and source of truth collapsing. Both database and the code are now useless. So you have to plan state management carefully.
The software development is a process where you have to be very conscious about all the decisions you have taken in the past, be it about the database, be it about the API contracts, be it about the state management, and then the state of components. Lapse in incorporating past context can lead you to making serious errors that again render both your database and codebase useless.
So now that we understand database API contracts and state management, let’s go on to the next step: actually displaying the data. To do that, we use components.
- Layer 0: Database (stores data)
- Layer 1: API (updates data)
- Layer 2: State (mirrors data for display and captures any changes.)
As you can see, components naturally would have the duty to display data and also capture changes and deliver it to state management so that it can incorporate it.
Since we have a state management layer doing the data storage and API calling part, we should not do it in the component. A component should never call APIs directly, store data locally, calculate matrix duplicate data from another component, or in any way circumvent the state. You go to state to get data or to just tell them what is the update.
That also means we need a translator between the components and the states so that state remains one, but for diverse components, we can still format it into a structure that the state can manage, no matter what. They are called selectors. They allow the state to always be normalized and consistent, while components get exactly what they need. So even if you change the structure of state, you just have to update the selector, and components can stay the same. Introduces a lot more flexibility.
Thus, in this article, we will have to deal with:
- Creation of components
- Creation of Selectors
- And the protocol to communicate with the state to tell them what happened so that it can get the further actions done, such as API calls or updating any metric.
Ok so UI is way complicated, like it almost has its own API type thing going on. I got hot just backend talk so much and frontend might be just making calls and directly populating data, but it also has its own kind of API which, like API call and response, mirrors this protocol and selector
Layer 0: Database
(No external API, just internal structure)
Layer 1: Backend API
Input: HTTP requests from frontend
Output: HTTP responses to frontend
Contract: API endpoints document what clients can do
Layer 2: Frontend State Management API
Input: Actions from components
Output: Selectors to components
Contract: Protocol documents what UI can do
Layer 3: Components
(No external API, just consume selectors and dispatch actions)
That means whatever rules apply to the backend also apply to the frontend:
- No duplicates
- Don’t skip validation
- Don’t let anyone access the database directly
- Don’t let anyone access the state directly
Thus, whatever duties the code had to maintain the truth of the database also apply to the front-end code which tries to maintain the state as the source of truth. Front-end parallels, back-end a lot, and I’m really fascinated by it. I thought front-end was just pretty.
Database as source of truth for DATA
↓
API contract ensures data flows correctly
↓
API validation prevents bad data
------------------------------------------------------
State as source of truth for UI DATA
↓
Protocol ensures UI actions flow correctly
↓
Action validation prevents bad updates
The solution is identical:
- One place where truth lives (database / state)
- One way to change it (API / Protocol)
- Validation at the gate (API validation / Action validation)
- No shortcuts (can’t access DB / can’t access state
Layer 0: Database Schema
"Here's how we structure DATA"
Rules: no duplicates, timestamps, relationships
Layer 1: API Contracts
"Here's how backend VALIDATES and UPDATES data"
Rules: validation, request/response format, transactions
Layer 2: State Management
"Here's how frontend MIRRORS and UPDATES state"
Rules: no duplicates, validation, protocol, selectors
Layer 2.5: Selectors
"Here's how frontend READS from state"
Rules: pure functions, composition, decouple components
Layer 2.6: Protocol/Actions
"Here's how frontend WRITES to state"
Rules: intent-based, validation, side effects
Layer 3: Components
"Here's how we DISPLAY data and CAPTURE input"
Rules: read via selectors, write via protocol, dumb presentation
Designing Components
Component design IS about: “How do I display this specific piece of state and capture this specific user action?” Because fetching the data is done by selectors, and sending the data is done by protocols. Just like back-end where there can be many routes and you have to tap the correct end-point to start the correct route to get the data you want, the component should tap the correct selector. Same with sending something, you need to tap the correct protocol to send the information.
Thus, you will need a proper list of all the selectors and protocols you have. And each one of them should be having proper information about the type of response they will give and expect. Just like we had in our API contract document. I think making a front-end contract document can be the move.
Key parallels:
| Backend | Frontend |
|---|---|
| Endpoints | Selectors + Actions |
| Request format | Payload shape |
| Response format | Output type |
| Validation rules | Input validation |
| Database operations | State updates |
| Error responses | Error handling |
| Side effects | Side effects |
| API documentation | Component documentation |
Database ← API Contract → Backend ← State Management API → Frontend
User Action → Component → Protocol → State Update → API Call → Database → API Response → State Update → Selector → Component Re-render → User sees
User Action → "I clicked 'Delete Key'"
↓
Component → Captures the event, identifies intent
↓
Protocol/Action → dispatch({ type: 'DELETE_KEY', payload: { keyId: 123 }})
↓
State Update → Optimistically marks key as "deleting..."
↓
API Call → DELETE /api/keys/123
↓
Database → DELETE FROM api_keys WHERE id = 123
↓
API Response → { success: true, deletedId: 123 }
↓
State Update → Removes key from state.apiKeys
↓
Selector → getActiveKeys(state) recalculates
↓
Component Re-render → React sees new data from selector
↓
User Sees → Key disappears from the list
Just from the illustrations above, you can see that the component sits between the protocol and the user as well as between the selector and the user. Thus the component flows downstream when it comes to designing from selectors and protocols. I don’t think it is formally called a protocol, but I will just call it that.
SELECTORS PROTOCOL/ACTIONS
↓ ↑
↓ ↑
┌───────────────────────────┐
│ COMPONENT │
│ │
│ (Translation Layer) │
└───────────────────────────┘
↓ ↑
↓ ↑
USER SEES USER ACTS
You design your UI, and based on that, you have a list of demands that say, “Hey, I want this data for these UI pages.” And based on that, you create state management design and front-end contract design. Which encompasses all the data types, how you are going to get them, what is the structure of them, etc.
Now from that UI page design and front-end contracts design you figure out the components.
A component will reside inside a UI page. So, we will have to go page-by-page. You look at your UI design mockup, check which front-end contract can serve it, and then group related selectors and actions. A component will emerge naturally.
UI Design shows:
├─ Header (user name, credits bar)
├─ Stats Cards (keys count, scans count)
└─ Recent Activity (last 5 scans list)
Frontend Contract says:
- getUserName(), getCreditsDisplay() → for Header
- getTotalApiKeysCount(), getScansThisMonth() → for Stats
- getRecentScans() → for Recent Activity
Components:
✓ DashboardHeader (uses getUserName, getCreditsDisplay)
✓ QuickStatsCards (uses getTotalApiKeysCount, getScansThisMonth)
✓ RecentActivityWidget (uses getRecentScans)
Basically, the UI components will be extremely derivative of whatever you have done before. So, if those are of good quality, your Job will be easier here. If the database schema, API contract, state management, and front-end contracts are good. Then components just practically write themselves. And those are user-facing components, so they are extremely important. That means if you mess up in the upstream process, no matter how much you try, this will also be garbage. Whatever you are showing to the world, if it is garbage, then how do you expect to make money? So, everything upstream is extremely important.
Database: Small mistake (forgot a constraint)
↓
API: Allows invalid data through
↓
State: Stores inconsistent data
↓
Selectors: Return garbage
↓
Components: Display garbage to user
↓
User sees broken app, leaves, never pays
So, before you design your components, thoroughly vet out your database, your API contracts, your state management, and your front-end contracts. Once that is dialed down, just look at your mock UI design and go crazy.
Examples of Forgetting Past Context
Forgot Database Design
Database: users.subscription_tier references subscription_tiers.name
Developer later: "Let me store tier price in users table too"
→ Duplicate data
→ Database constraint violated
→ Data drifts out of sync
Forgot API Contract
Contract: POST /api/keys returns { id, key_name, key_prefix, full_key }
Developer later: "I'll make frontend expect { keyId, name, prefix, token }"
→ Frontend and backend don't match
→ Nothing works
Forgot State Management Design
State design: All API keys stored in state.apiKeys
Developer later: "I'll add useState in this component for keys"
→ Same data in two places
→ They get out of sync
→ UI shows inconsistent data
Forgot Component Structure
Component design: KeysList component handles list, KeyCard handles individual key
Developer later: "I'll add key deletion logic to KeysList"
→ Wrong component has the logic
→ Code becomes tangled mess
Project Context Document
## Database Schema
[paste schema]
Key constraints:
- No duplicate data
- All calculations derived, not stored
- Foreign keys enforced
## API Contract
[paste contract]
Key rules:
- Only these endpoints exist
- Request/response shapes are fixed
- No new endpoints without updating this document
## State Management
[paste state design]
Key rules:
- All API data goes through state
- Components read from state only
- No local state for server data
## Component Structure
[paste component hierarchy]
Key rules:
- Each component has one responsibility
- Data flows down, actions flow up
- No business logic in display components
```
Frontend Contract vs State Management Design
| Aspect | State Management Design | Frontend Contract Design |
|---|---|---|
| Purpose | Defines HOW state is structured and managed internally | Defines WHAT interface state exposes to components |
| Audience | Implementation guide (how to build the store) | API documentation (how to use the store) |
| Focus | Internal structure, data flow, cache rules, fetch logic | External interface, selector signatures, action payloads |
| Answers | “How should we organize state?” “When do we fetch?” “How do we cache?” | “What selectors exist?” “What actions exist?” “What are their types?” |
| Content | State shape, fetching strategy, mutation logic, edge cases, cache rules | Selector catalog, protocol/action catalog, input/output types |
| Analogy | Database schema + internal query optimization | API endpoints documentation |
| Tells You | State structure: { user: { credits: { remaining, limit } } } | Selector: getCreditsDisplay() → { remaining, limit, percentage, status } |
| Components Need | No – components shouldn’t know internal state structure | Yes – components use this to know what’s available |
| Changes When | Refactoring internal state organization | Adding new features, changing what components need |
| Example | “Store credits in state.user.credits with lastFetched timestamp” | “Use getCreditsDisplay() selector to get formatted credit info” |