Should Your Frontend State Mirror Your Database, or Should It Know as Little as Possible?
The Question Behind the Question
A developer on Reddit recently asked something that sounds basic but cuts deeper than most senior engineers realize: if I’m using DTOs that carry only necessary data, why not just send the whole object so my frontend state matches my database?
Underneath that question are actually three questions tangled together. When should the frontend load its data? Why shouldn’t the API just return the full database record? And if DTOs exist to slim things down, what exactly are they protecting against?
Each of these has an answer that changes depending on the size of your app, your user base, your data sensitivity, and how often your backend schema shifts. So instead of handing you a rule, here’s a framework for making the call yourself.
Part One: When to Load State
Three strategies. Each one exists because the other two fail in specific situations.
Eager Loading: One Big Upfront Call
The app fetches most of the data the user will need right when they land. Subsequent page navigations just read from what’s already in memory.
This holds up when total data volume is small (a settings panel, a user profile, a dashboard with fixed widgets), when the user will realistically touch most of that data during their session, and when snappy page transitions matter more than the initial load. It collapses when the dataset is large, when users visit only a fraction of the available pages, or when the data goes stale between the moment it was fetched and the moment someone actually looks at it.
Lazy Loading: Fetch as You Go
The app starts almost empty. Each page or component requests what it needs from the API when it mounts. Stale data is not a problem because every view gets a fresh response.
Libraries like TanStack Query, SWR, and Apollo Client have made this approach the default for most React, Vue, and Angular projects. They treat the server as the authoritative data source, cache responses, deduplicate concurrent requests, and silently refetch in the background when data ages out. Nadia Makarevich, in her 2025 analysis on developerway.com, argues that the split between “server state” (data that lives on the backend) and “client state” (UI toggles, form inputs, selected tabs) is now the single most important architectural decision in frontend work. Server state belongs to the server; the frontend merely holds a cached snapshot.
Lazy loading struggles on unreliable networks, when users flip rapidly between views that each trigger an API call, or when one view depends on data from another view that hasn’t been visited yet.
Hybrid: What Production Apps Actually Do
Core data the user will definitely need gets loaded eagerly at app initialization: auth state, user profile, permissions, navigation structure. Everything else arrives lazily as pages are visited.
Anthony Trivisano’s February 2025 overview of frontend architecture trends confirms that this pattern has become the production default, noting that “server-centric state reduces the amount of client-side state management” while UI-specific state stays local to the component that owns it (anthonytrivisano.com).
Choosing Your Strategy
The table below isn’t a scorecard where you tally columns. Security and data sensitivity always outweigh convenience. If even one row points to “Lean Toward Lazy” for a security or data-size reason, that should carry more weight than three rows pointing the other way.
| Factor | Lean Toward Eager | Lean Toward Lazy | Why It Matters |
|---|---|---|---|
| Total payload size | Small (< 100KB compressed) | Large or unbounded | Large upfront payloads punish mobile users |
| % of data used per session | > 70% | < 30% | Fetching data nobody looks at is pure waste |
| Data sensitivity | Low (public info) | High (PII, financial) | Sensitive data should exist client-side only when actively needed |
| Data change frequency | Rarely changes | Changes often | Eagerly loaded data goes stale; stale data causes bugs |
| Network conditions | Reliable, low latency | Variable or mobile | Many small requests on bad networks feel worse than one big one |
| Number of routes | Few (< 10) | Many (> 20) | More routes = lower probability any single page is visited |
| Session duration | Long (power users, dashboards) | Short (casual visitors) | Long sessions justify upfront investment |
Part Two: Why Not Just Send the Whole Object?
The instinct makes sense. If you send everything, you never run into that frustrating moment where the detail page needs a field the list page didn’t fetch. You avoid extra round trips. Your frontend state looks like a clean mirror of your database.
Here’s why that mirror cracks.
The Security Problem (This One Is Not Theoretical)
OWASP ranks “Broken Object Property Level Authorization” as the #3 API security risk in its 2023 API Security Top 10 (owasp.org). The description is blunt: API endpoints that expose more data properties than required, neglecting the principle of least privilege. OWASP scores its prevalence as “common” and notes it is moderately easy to detect and exploit.
This is not an edge case that only happens to careless startups. Real incidents include:
| Incident | What Happened | Scale |
|---|---|---|
| Twitter/X (2022) | API endpoint returned full user objects including email addresses and phone numbers tied to accounts | 5.4 million user records scraped, another 200M+ exposed in a follow-up dump (Barracuda / OWASP case study) |
| HealthEngine (Australia) | API exposed patient records with PII beyond what the consuming app needed | 59,000 patient records, $2.9M fine (Barracuda) |
| GitHub | Mass assignment exploit via API allowed a researcher to add a public key to a Rails repo and gain admin commits | Full repository access on production codebase (Barracuda / OWASP case study) |
| Spoutible | Social platform serialized full user models to API responses, including password hashes and 2FA secrets | Entire user table exposed (documented by Troy Hunt) |
The pattern is the same in every case: the backend returned the full object, and either the frontend was supposed to “hide” sensitive fields, or nobody thought about what was in the response at all. As Troy Hunt documented in the Spoutible case, the platform shipped entire user models to the client because the backend simply serialized what was in the database rather than constructing purpose-built DTOs.
A DTO is not just a convenience for the frontend. It is a security boundary at the network edge.
The Coupling Problem
When frontend state mirrors the database schema, you have welded two systems together that need to evolve on different timelines. Your backend team renames a column, adds a nested relationship, or changes a date format? Every component that touches that data breaks.
Dario Ghilardi describes this on his engineering blog: passing DTOs directly to UI components “tightly couples your UI components to the data transport structure from the backend” (darios.blog). The profy.dev React architecture series demonstrates a cleaner pattern, where DTOs exist only inside the API layer and get transformed into domain models before anything else in the app sees them.
Here’s what that looks like in practice:
// What the API returns (the DTO)
{
"usr_id": "a1b2c3",
"usr_full_name": "Jane Park",
"usr_email": "[email protected]",
"usr_created_ts": "2025-01-15T08:25:00Z",
"usr_role_cd": 2,
"usr_internal_flags": 48, // internal system flag, meaningless to UI
"usr_pwd_hash": "bcrypt$2b$..." // should never have left the server
}
// The mapper function (lives in your API layer)
function toUserProfile(dto) {
return {
id: dto.usr_id,
name: dto.usr_full_name,
email: dto.usr_email,
memberSince: new Date(dto.usr_created_ts).toLocaleDateString(),
role: dto.usr_role_cd === 1 ? "admin" : "member",
};
}
// What your components actually consume (the view model)
{
"id": "a1b2c3",
"name": "Jane Park",
"email": "[email protected]",
"memberSince": "1/15/2025",
"role": "member"
}
If the backend team renames usr_full_name to display_name next quarter, you update one mapper function. Zero components change. If the backend accidentally starts including a field like usr_pwd_hash (as Spoutible did), a well-designed DTO on the server side would have prevented it from ever being serialized. But even if the server messes up, the mapper on the frontend acts as a second line of defense because your components never look at raw API responses directly.
The Bandwidth Problem
Sending full objects means shipping data the user will never see. On localhost with fast Wi-Fi, the difference between a 2KB and a 20KB response is invisible. On a 3G connection in a crowded train, it’s the difference between content appearing and a spinner hanging.
This compounds. If your app makes 30 API calls in a session and each one carries 15KB of unused fields, that’s 450KB of wasted transfer. Multiply by thousands of users on metered mobile data and the cost becomes tangible. Nordic APIs identifies overfetching as one of the most persistent REST API performance issues, noting that it directly increases parse time, memory usage, and time-to-interactive on the client (nordicapis.com).
The counterargument is valid: fewer round trips. If you send everything once, you don’t need follow-up calls. But the right solution is not “send everything” or “send as little as possible.” It’s “send exactly what this view needs.” A UserListItemDTO with id, name, avatarUrl for the list page. A UserDetailDTO with those fields plus email, bio, joinDate for the detail page. Two endpoints, two DTOs, zero wasted bytes, zero follow-up requests.
The “Source of Truth” Misunderstanding
The Reddit post suggests keeping frontend state similar to the database because the database is “the true single source of truth.” The instinct is understandable, but the conclusion doesn’t follow.
Think about it practically. Not every user who visits the list page will click through to a detail page. If 1,000 users load the user list but only 80 click a profile, you’ve shipped full user objects to 920 people who never needed them. That’s wasted bandwidth for the user and wasted compute for your server serializing fields nobody reads.
Beyond that, frontend state contains things the database doesn’t: which accordion is expanded, what the user typed in a search box but hasn’t submitted, which rows are selected in a table, whether a modal is open. The database contains things the frontend should never hold: password hashes, internal audit flags, replication timestamps, soft-delete markers. These two shapes serve different purposes and forcing them to match creates a structural mismatch that gets more painful as the app grows.
The database is the authority on what data exists. The frontend state is the authority on what the user is currently seeing and doing. Different jobs, different shapes.
Part Three: What About GraphQL and tRPC?
Everything above assumes a traditional REST architecture where the backend team defines fixed DTO shapes per endpoint. But there are tools designed to let the client specify exactly which fields it wants.
GraphQL was created by Facebook precisely to solve the overfetching/underfetching problem. Instead of the backend dictating DTO shapes, the frontend sends a query specifying exactly which fields it needs, and the server returns only those fields. This eliminates the need for multiple DTO variants per entity (list DTO, detail DTO, card DTO) because the client shapes its own response.
tRPC takes a different approach, popular in full-stack TypeScript projects. It lets frontend and backend share type definitions directly, so the data contract is enforced at compile time rather than through manually maintained DTOs.
Both are valid alternatives. But neither eliminates the core principle: the frontend should receive only the data it needs for the current view, and sensitive fields should never cross the network boundary regardless of who controls the query. GraphQL still requires server-side authorization on which fields a given user is allowed to request. tRPC still requires thoughtful API design about what gets exposed.
If you’re starting a new project and have control over the stack, these tools are worth evaluating. If you’re working with an existing REST API, well-shaped DTOs and a frontend mapper layer give you most of the same benefits without a rewrite.
Part Four: The Complete Architecture
For the developer reading this who wants a pattern to take into their next project, here’s the full flow:
Database
↓ (backend query, selects only needed columns)
Backend DTO
↓ (serialized as JSON, sent over HTTP)
API Response
↓ (frontend API layer receives it)
Mapper Function
↓ (transforms backend shape into UI shape)
View Model
↓ (stored in state: React Query cache, Zustand store, etc.)
Component
Each arrow is a transformation point. Each one lets you filter, rename, reshape, and protect without breaking the layers above or below it.
For small apps, the mapper might be a single function that renames a few fields. For large apps, it becomes a full data access layer with TypeScript interfaces for both the DTO and the view model, validation logic, and error handling. The principle scales; only the complexity changes.
The Practical Takeaway
Start with purpose-built DTOs from day one, even if they look almost identical to your database models initially. The discipline of asking “what does this view actually need?” before designing an API response protects you from the three most common traps: shipping sensitive data to the browser, creating brittle coupling between frontend and backend, and bloating payloads with fields nobody reads.
For state population: default to lazy loading with a caching library (TanStack Query, SWR, Apollo). Promote specific data to eager loading only when you have evidence the upfront cost pays off in faster subsequent navigation. Keep client-side state focused on UI concerns (selections, toggles, form drafts) and let server state live where it belongs.
Your database owns your data. Your frontend state owns your user’s experience. They answer to different requirements, and trying to make them identical is like wearing a suit to the beach because you wore one to the office.
Sources
- OWASP, “API3:2023 Broken Object Property Level Authorization,” OWASP API Security Top 10 (2023). Ranked #3; prevalence scored as “common.”
- Barracuda Networks, “OWASP Top 10 API Security Risks: Broken Object Property Level Authorization” (May 2023). Twitter/X 5.4M+ user breach, HealthEngine $2.9M fine, GitHub mass assignment exploit.
- Nadia Makarevich, “React State Management in 2025: What You Actually Need,” developerway.com (2025).
- Anthony Trivisano, “The State of Frontend Development in 2025,” anthonytrivisano.com (February 2025).
- Dario Ghilardi, “Do Not Pass DTOs to UI Components,” darios.blog.
- Johannes Kettmann, “Path To A Clean(er) React Architecture: Domain Entities & DTOs,” profy.dev.
- kait.dev, “Your Frontend Is Not Your Backend: Using Data Transfer Objects to Keep Your Code Focused.” Spoutible case analysis.
- Nordic APIs, “How to Avoid Overfetching and Underfetching,” nordicapis.com.
- InstaTunnel / Medium, “Excessive Data Exposure in APIs” (November 2025). OWASP excessive data exposure mechanics.
- Evojam, “Sane Single Page Apps: Easy Data Handling with DTOs,” evojam.com.
- Salt Security, “OWASP API Security Top 10 2023 Explained” (May 2025). BOLA accounts for ~40% of all API attacks.