Frontend ↔ Server Map
The server side isn’t a completely unknown world. Many of the ways of thinking you’ve picked up on the frontend have a direct counterpart on the server. Here we line them up side by side so you can approach API design as "an extension of what you already know". If a row catches your interest, follow the link on the right for a deeper article.
How to read this table
The left column is the "shared concern", the middle is "how the frontend shows it", and the right is "how the server shows it". What most rows have in common is that the frontend does it for experience, the server does it for correctness and safety. Noticing that the "purpose" shifts even for the same task makes the division of responsibilities much clearer.
The frontend is the "caller", the server is the "responder". Let’s look at the same single exchange from both the sending and receiving side.
| Shared concern | On the frontend | On the server | Learn more |
|---|---|---|---|
| Fetching data | Call the API with fetch / axios / useSWR and put the returned JSON into state | Define an endpoint, query the DB, shape the result, and return it | Structure of an HTTP message |
| Updating data | Submit a form as POST / PATCH and re-render the screen on success | Validate input, write to the DB, and return a status and body | CRUD ↔ HTTP method mapping |
| Specifying "what to do" | Build a method (GET / POST / ...) and URL to send | Route by method × path to a handler and act on its meaning | HTTP methods, safety & idempotency |
| Shape of the data sent | JSON.stringify an object into the request body | Parse the body and validate its types, required fields, and ranges before accepting it | Input validation basics |
Frontend state is usually "just this screen, gone on reload". Server state persists across processes and users. This difference in lifetime is where the design splits.
| Shared concern | On the frontend | On the server | Learn more |
|---|---|---|---|
| Holding state | useState / a store (Redux, Zustand, ...) — in memory, lost on reload | Stored in a DB / file — survives process restarts | Relational DB basics |
| Temporary caching | React Query / SWR cache — avoids re-fetching the same data | Declares "how much may be reused" via the Cache-Control header | Key HTTP headers |
| Login state | Keep a token in localStorage / cookies and attach it to requests | Validate the token/session and determine "who sent this request" | Auth methods (session/token/JWT/API key) |
Both sides "dispatch based on the URL". The frontend assigns a screen, the server assigns an operation on a resource.
| Shared concern | On the frontend | On the server | Learn more |
|---|---|---|---|
| Branching on the URL | Show the screen matching a path via React Router / Next.js routing | Run the handler (endpoint) matching method × path | REST thinking (resource-oriented) |
| URL design | Represent screen hierarchy in the path, e.g. /users/:id | Represent resource hierarchy with nouns, e.g. /users/:id (no verbs) | URL design & naming |
| Query strings | Keep fine-grained screen state in the URL, e.g. ?tab=profile | Receive filtering/sorting via ?status=active&sort=-created_at | Query design for search/filter/sort/pagination |
Frontend validation is about "kindness", server validation is about "defense". No matter how carefully the frontend checks, the server always checks again itself.
| Shared concern | On the frontend | On the server | Learn more |
|---|---|---|---|
| Purpose of checking input | Instant feedback for the user (better experience) | Stop malicious or invalid input at the boundary (protect the data) | Trust boundaries & defensive design |
| When checks are bypassed | Trivially bypassed by disabling/tampering with JS (not trustworthy) | This is the last line of defense; assume frontend validation may not even be there | Input validation basics |
| How errors are conveyed | A red "Invalid email format" message under the field | Return a 422 with a machine-readable, per-field error array | How to return validation errors (422 / per-field) |
The frontend handles "how to show it on screen", the server handles "conveying what happened with the right code and shape".
| Shared concern | On the frontend | On the server | Learn more |
|---|---|---|---|
| Representing failure | Catch errors with try/catch and show a toast or dialog | Convey the kind via the status code and details via the body | Choosing status codes |
| Shape of the error body | Tends to just read and display error.message | Return type/title/detail consistently, e.g. Problem Details (RFC 9457) | Error response shape (Problem Details / RFC 9457) |
| Info used for branching | Decide how to display things based on status/error codes | Return a machine-readable code scheme, agreed as a contract across the whole API | Consistency in error design |
The frontend carries someone’s proof (a token) around; the server verifies that proof and decides what is allowed. The core of security lives on the server.
| Shared concern | On the frontend | On the server | Learn more |
|---|---|---|---|
| Confirming "who" (authentication) | Login form → receive & store a token → attach it to later requests | Validate the token/session and identify the user; failure is 401 | Authn vs authz (401 vs 403) |
| Deciding "what’s allowed" (authorization) | Show/hide buttons and menus based on role (visual control only) | Always check role/ownership on the server; a violation is 403 | Authorization models (RBAC, ABAC, ownership checks) |
| Accessing another user’s data | Rewriting the id in the URL can trivially send a request for a different id | Always confirm "is this person really allowed to see this resource" (IDOR defense) | IDOR (insecure direct object reference) |
The frontend and server both tackle the same challenge from opposite sides: "show a lot of data, a little at a time, under conditions".
| Shared concern | On the frontend | On the server | Learn more |
|---|---|---|---|
| Displaying large amounts of data | Build infinite scroll / page-by-page UI | Split data into pages using offset / cursor | Pagination response design |
| Filtering & sorting | Reflect filter UI state into the URL query | Validate the query and safely translate it into WHERE / ORDER BY | Query design for search/filter/sort/pagination |
| Guaranteeing "speed" | Reduce fetch counts (React Query caching / deduplication) | Add indexes and fold N+1 queries down to 1-2 queries | Fixing the N+1 problem |
This is about the "contract" between frontend and server — what is returned and what isn’t. Leaving this ambiguous causes incidents and rework.
| Shared concern | On the frontend | On the server | Learn more |
|---|---|---|---|
| Shape of the returned data | Build the screen trusting the API’s type (a TypeScript interface) | Deliberately transform DB rows into a shape that’s safe to return, rather than returning them as-is | Principles of response body design |
| Deciding "which columns are OK to expose" | Tends to assume unused fields can just be ignored | Actively pick fields with a whitelist — sending unused columns lets attackers read them | Deciding which fields to include/exclude |
| Handling sensitive data | The mistaken belief that it’s fine as long as it’s not shown on screen | Things like password_hash or internal notes should never be included in the response at all | Handling sensitive fields |
| Writing multiple things at once | Hard to notice that one "Place order" button = writes to multiple tables | Bundle order/line-items/inventory/payment into one transaction: all succeed or all roll back | Atomicity of multi-table writes |
Lining things up this way shows that much of server-side work is "taking responsibility for things the frontend used to do without much thought". In particular, validation, authorization, and choosing what to return can be skipped on the frontend but never on the server. This is exactly the kind of judgment this site’s API drills repeatedly test. Start with one chapter, whichever theme interests you most.