Stop 09 · Connecting Frontend & BackendTopics · 7/8

Full-Stack Data Flow

Part of the Full Stack Developer Roadmap

In short

'My todo didn't save' could mean the form, the fetch, the route, the validation or the SQL broke. Knowing the full path of one piece of data tells you where to look.

Tracing a single piece of data's entire journey — user input, frontend state, API request, backend validation, database write, response, and UI update — the mental model that ties the whole stack together.

The blob character puts an answer sheet into a tied bundle beside a register book and a stack of sheets.

Think of it like this

An answer sheet's round trip

You write your answer sheet, the invigilator collects it, the evaluator checks it, marks go into the register, and the result comes back to you. Lose it at any step and you get no marks. Data in your app makes the same trip: form, request, validation, database, response, screen.

Watch it happen

Saving one todo, end to end

You
React app
Express API
Postgres

types 'Buy milk' + Enter

1/7The form's onSubmit fires. React is holding the text in state.

The big ideas

What you need to know

Validate on the server

Browser checks are for convenience. Anyone can skip them with curl, so the API must validate every input again.

The database owns the truth

Ids, timestamps and defaults come from the database. Use what the API returns, not what you guessed in the browser.

One shape on both ends

Share the Todo type or schema between frontend and backend, so a renamed field breaks the build, not production.

Debug by following the data

Check each hop in order: React state, the Network tab request, the server log, then the table itself.

See it in code

Walk through the code

ts
1// server.ts (Express)2const TodoInput = z.object({ title: z.string().trim().min(1).max(200) });3 4app.post("/api/todos", async (req, res) => {5  const parsed = TodoInput.safeParse(req.body);6  if (!parsed.success) return res.status(400).json({ error: parsed.error.issues });7  const { rows } = await pool.query(8    "INSERT INTO todos (title) VALUES ($1) RETURNING id, title, done",9    [parsed.data.title],10  );11  res.status(201).json(rows[0]);12});13 14// TodoForm.tsx (React)15const res = await fetch("/api/todos", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title }) });16if (res.ok) setTodos((prev) => [...prev, await res.json()]);

1/4Zod checks the body. Bad input gets a 400 with the reasons, and nothing touches the database.

The server half needs app.use(express.json()) so req.body is parsed. The $1 placeholder keeps the title safe from SQL injection.

Save yourself the pain

Common mistakes

Oops

Adding the todo to the list with a made-up id before the API replies.

Do this instead

Use the row the API returns. If you want instant UI, that's optimistic updating, and you must roll back on failure.

Oops

Validating only in the React form.

Do this instead

Validate in the form for a nice experience and again on the server for safety. The server check is the one that counts.

Your mission

Trace a todo

~35 min

Quick check

A user adds a todo and sees it on screen, but after a refresh it's gone. Where is the bug most likely?

Go deeper

  1. 1Pick one feature and diagram every step data takes from click to database and back.
  2. 2Identify where validation happens at each layer and why it's duplicated (client UX, server security).
  3. 3Debug a full-stack bug by tracing this same path, layer by layer.

More topics at this stop

Stuck on this topic? Ask an Insider

Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.

Download
InsideEdge