got a nice quote for myself, I just made it. “Database shall be single source of truth, and code shall be maintaining the said single source of truth. If either fails, then both code and database are useless.”
Well in the previous articles we address just that how to design database and how to design contracts. Now we are moving on to the frontend.
Essentially, the database was an entity laid contract, where actions were laid out, and states would be needed. What is the data you want to display and update? You figure out your UI – what pages you’re going to have and what you’re going to show in them. Once you have a list of demands, you can organize it, sequence what to fetch and when. You also have to record and update things – basically trades and posts. It is the front-end side aggregator that talks with the API to fulfill the UI needs and helps maintain the database as a source of truth. UI takes action and state management communicates that to the back-end, thus it is an important link in keeping the database as a single source of truth.
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. Just as deliberately we planned out the relationship between database, and just as deliberately we thought of all the actions a user might take, we have to deliberately think about all the data we must display and the UI pages. Both our database and the API contract can lend us some metrics to display:
- The API contract can give us clues about which action statistics we can show
- Our database design can give us entity-based information
Beyond that, everything else can be computed on run-time, received from an external API, or captured from the user’s own actions like tapping or amount of time spent on website for pop-ups, etc.
Since this is almost a database that exists on the front-end, we can borrow a lot from our existing database and APIs.
- Identify which entities and actions can be counted or aggregated.
- Identify which types of relationships are there? Like, if a user has a one-to-many relationship with something, then we can show how many he actually has and maybe compare with the platform or peers metrics
- For things like Spotify rap, you can look at The number of times a song was played, The number of messages sent, The number of playlists , Anything action related
There might be 1000 different other things you can show right now, but let’s move on. When you fetch the data from your backend, it will be first stored in your state management, and your UI will fetch it from that. When you take an action via some API call, the action directly goes to the backend, but your state management still updates it after the fact. And that allows you to dynamically see changes happening. Database is still the source of truth, but state management is the latent source of truth and confirmation that action was taken. It is not duplicating, it is mirroring.
state management is about setting a proper fetching of data, storing it, also storing interactions or other data from the frontend and also may be from third party external things that are going on. We collect that data also in state and then we show it. The whole point is to be a reservoir of data from: 1. Back-end 2. Front-end 3. Third-party APIs
This means you have to be very conscious about all the data points you want to incorporate and how you call them. So, more often than not, State Management is a process where you once again decode what data you need based on the needs of the customer, rather than how you did it in the database design phase where it was based on entities. You make a database that is based on what the user will need.
State Management Design Checklist
Step 1: List All Pages/Screens
- Dashboard
- API Keys page
- Scans page
- Settings page
- Billing page
Step 2: For Each Page, Ask “What Does User See?”
Dashboard:
- User name and email
- Subscription tier with "Upgrade" button
- Credit count with usage bar
- List of 5 most recent scans
- Quick stats (total keys, total scans)
Step 3: For Each Data Point, Ask “Where Does It Come From?”
User name ← Backend (GET /api/user/me)
Subscription tier ← Backend (GET /api/user/me)
Credit count ← Backend (GET /api/user/me)
Recent scans ← Backend (GET /api/scans?limit=5)
Total keys ← Backend (GET /api/keys) then COUNT in state
Total scans ← Backend (GET /api/scans/count)
"Upgrade" button visibility ← Frontend logic (if tier === 'free')
Usage bar percentage ← Frontend calculation (used/limit * 100)
Step 4: Organize Into State Structure
javascript
state = {
// From backend
user: {
name: 'John',
email: 'john@email.com',
tier: 'free',
credits: 47
},
scans: {
recent: [ ... ], // last 5 scans
totalCount: 123
},
keys: {
items: [ ... ],
activeCount: 3
},
// From external API
billing: {
stripeStatus: 'active',
nextBillingDate: '2026-02-15'
},
// From frontend
ui: {
showUpgradePrompt: true,
onboardingStep: 2
}
}
```
Step 5: Define How Each Gets Populated
```
On page load:
- fetchUser() → state.user
- fetchScans(limit: 5) → state.scans.recent
- fetchScanCount() → state.scans.totalCount
- fetchKeys() → state.keys.items
- fetchBillingStatus() → state.billing (from Stripe)
On user interaction:
- User dismisses prompt → state.ui.showUpgradePrompt = false
- User completes step → state.ui.onboardingStep += 1
```
You will have a set of all data that you can fetch and store, but not all pages require all the data. So you have to decide what data to fetch for each page. Some data will be such that regardless of what page you are on, you will need it. Then some will be such that because you are on a specific page, you will need to have it. And some will be such that it should be fetched upon specific interaction.
## Data Loading Strategy
### Global (Fetch Once on App Load)
- User profile: GET /api/user/me
- Subscription: GET /api/subscription
- Notification count: GET /api/notifications/unread/count
### Page-Specific (Fetch When Page Loads)
Dashboard:
- Recent scans: GET /api/scans?limit=5
- Usage stats: GET /api/usage/current
Keys Page:
- API keys list: GET /api/keys
Scans Page:
- Scans list: GET /api/scans?limit=20&offset=0
### On-Demand (Fetch When User Clicks)
- Scan details: GET /api/scans/:id
- Key usage stats: GET /api/keys/:id/stats
- Invoice details: GET /api/billing/invoices/:id
### Cache Rules
- User profile: Cache until logout
- API keys list: Cache for 5 minutes OR invalidate on create/delete
- Scans list: Cache for 1 minute OR invalidate on new scan
- Detail views: Cache until page refresh
Why AI Fails at This
AI has no memory of your past decisions.
Every time you ask AI to add a feature:
- It doesn’t remember your database schema
- It doesn’t remember your API contract
- It doesn’t remember your state design
- It doesn’t remember your component structure
So it just does whatever is easiest in the moment, which violates your constraints.
Result: Duplicate endpoints, inconsistent data, broken architecture.