Backend for frontend devs
The server-side vocabulary a UI dev needs to direct an AI to build a real backend — runtimes, endpoints, data, auth, jobs — and where each piece lives.
You own the UI. Now the app needs to save things, log people in, and talk to other services — that’s the backend. This is the server-side vocabulary a frontend dev needs to direct an AI to build a real backend, and to know where each piece lives. (Databases get the deep treatment in Module 3; auth in 2.3.)
CRUD is the whole shape
Almost every backend is the same four operations on data — Create, Read,
Update, Delete — each with a natural HTTP method and SQL statement. Learn the
mapping once and most endpoints write themselves. When you ask an AI for “an endpoint
to archive a todo,” what you’re really asking for is an Update — a PATCH — and
naming it that way gets you the right verb and status code on the first try.
One endpoint, end to end — both sides
A request arrives at a route, a handler validates it, the ORM writes a row, and a response goes back. Here’s the server side and the client side of the very same call, so you can see how they meet.
// SERVER — the handler behind POST /api/todos
app.post("/api/todos", async (req, res) => {
const user = await requireAuth(req); // authN: who is this?
const { title } = req.body;
if (!title?.trim()) {
return res.status(400).json({ error: "title required" });
}
const todo = await db.todo.create({ // ORM writes the row
data: { title, userId: user.id },
});
res.status(201).json(todo); // 201 Created + the new row
});// CLIENT — the browser fetch that hits it
const res = await fetch("/api/todos", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title: "Buy milk" }),
});
const todo = await res.json(); // { id, title, userId, ... }Note the server order: authenticate → validate input → do the work → respond with
the right status code. That shape repeats for every write endpoint you’ll ever
build. And note what the client sends: a method, a Content-Type header, and a JSON
body — the exact three things the handler reads back out.
Why an ORM instead of SQL strings
You can write "SELECT * FROM todos WHERE id = " + id — and you’ll have just opened
the door to a SQL-injection attack and a typo-prone mess. An ORM lets you write
db.todo.findUnique({ where: { id } }): it builds safe, parameterized SQL for you,
maps rows to typed objects, and gives the AI a schema it can autocomplete against.
You still need to understand SQL (Module 3) — the ORM just stops you hand-gluing it.
Where does the backend live?
“Backend” isn’t one shape. Pick how it runs based on the workload — and know that the same CRUD code can usually run in any of these.
| Style | What it is | Reach for it when | Watch out |
|---|---|---|---|
| Serverless function | code that spins up per request | spiky traffic, simple APIs, quick start | cold starts; no long-running work |
| Long-running server | an always-on process (Express, Fastify) | websockets, background work, steady traffic | you manage scaling + uptime |
| Managed platform / BaaS | hosted DB + auth + APIs (Supabase, Firebase) | you want a backend now, small team | lock-in; less control |
The split that trips people up: serverless functions don’t stay running between requests. That’s great for spiky traffic (you pay per call, it scales to zero) but it means there’s no place to keep a websocket open or run a long job — for those you want an always-on server. Which brings us to the work that can’t fit in a request.
Background jobs, queues, and where the DB sits
Some work is too slow to finish before you respond: sending a welcome email, generating a thumbnail, charging a card. You don’t want the user’s request hanging for ten seconds. The pattern is a queue: the handler drops a job on the queue and responds immediately; a separate worker picks it up and does the slow work later.
The database sits behind the API, reachable by both the handler and the worker — but never by the browser directly (see 7.1). Its connection string is a secret, held in an env var, and it’s the one component whose data has to survive everything else restarting.
01 Learning objectives
0 / 6 done02 Curated reading
03 Knowledge check
- 01easy
In CRUD, “Create” maps to which HTTP method?
- 02easy
What does an ORM do?
- 03medium
Authentication vs authorization:
- 04medium
A validation check in the frontend is enough to keep an endpoint secure.
04 Interview questions
browse all ↗What gets asked on this topic — tap a card for how to approach it, the follow-ups, and the trap. Company tags are best-effort & sourced.
-
What can server-side code do that browser code can't?
The server runs on a machine you control, so it can hold secrets (API keys, DB credentials) the user never sees, reach the database directly, touch the filesystem, and call other services with trusted credentials.
Browser code is shipped to and runs on the user's machine — it's fully visible and editable by anyone, so it can't be trusted to keep secrets or enforce rules. Any check that matters (auth, pricing, permissions) must happen server-side.
Follow-ups they push on- Why isn't a check in the frontend enough to secure an action?
- What's a runtime — Node, Python — in this context?
Red flag Putting an authorization check only in the frontend. The user can bypass it; the server must re-check everything.
source: MDN — Server-side programming: first steps ↗ -
Map CRUD to HTTP methods. Which methods are idempotent?
CRUD ↔ HTTP: Create → POST, Read → GET, Update → PUT/PATCH, Delete → DELETE.
Idempotent means calling it N times has the same effect as calling it once. GET, PUT, and DELETE are idempotent; POST is not (two POSTs create two records). PATCH is generally not guaranteed idempotent. This matters for retries: it's safe to retry a GET or PUT after a timeout, but retrying a POST may double-charge or double-create.
Follow-ups they push on- What's the difference between PUT and PATCH?
- Why does idempotency matter when a request times out?
Red flag Saying GET is idempotent because 'it doesn't change anything' — that's safety. Idempotency is about repeated calls having one effect (a correct GET is also safe, but the concepts differ).
source: InterviewBit — REST API Interview Questions ↗ -
What is an ORM, and why do people warn against hand-concatenating SQL strings?
An ORM (Object-Relational Mapper, e.g. Prisma) lets you work with database rows as objects in your language instead of writing raw SQL — it generates the SQL for you and maps results back to typed objects.
Hand-concatenating SQL from user input invites SQL injection: if you build
"SELECT * FROM users WHERE name = '" + input + "'", a crafted input can break out of the string and run arbitrary SQL. ORMs (and parameterized queries) bind values separately from the query text, so input can never become executable SQL.Follow-ups they push on- What's a parameterized/prepared query?
- When might you drop to raw SQL anyway?
Red flag Thinking an ORM is required for safety — the real fix is parameterized queries; an ORM is one convenient way to get them.
source: Prisma — What is an ORM? ↗ -
Authentication vs authorization — what's the difference?
Authentication is proving who you are (login, a token, a session). Authorization is what you're allowed to do once you're known (can this user delete that post?).
Mnemonic: authentication is the bouncer checking your ID at the door; authorization is the rule about which rooms your ticket lets you into. You authenticate once, then authorize every sensitive action.
Follow-ups they push on- Where must these checks run — frontend or backend?
- Can you be authenticated but not authorized for an action?
Red flag Using the words interchangeably. A logged-in user (authenticated) still must be authorized per action; conflating them leads to privilege bugs.
source: MDN — Server-side programming: first steps ↗ -
Serverless vs a long-running server — when does each fit?
Serverless (functions that spin up per request) shines for spiky, event-driven, or low-traffic workloads: you pay per invocation and scale to zero, but each call can have a cold start and there's no in-memory state between calls. A long-running server fits steady traffic, long-lived connections (websockets), background work, and cases where keeping things warm in memory matters.
Orientation-level takeaway: serverless trades always-on cost and statefulness for automatic scaling and pay-per-use.
Follow-ups they push on- What is a 'cold start'?
- Why is a websocket server awkward to run serverless?
Red flag Assuming serverless is always cheaper — at sustained high traffic a long-running server is often cheaper and lower-latency.
source: MDN — Server-side programming: first steps ↗ -
Sketch a REST API for a 'notes' resource. What does the full set of CRUD endpoints look like?
REST organizes the API around a resource (notes) and uses HTTP methods for the verbs. The standard set:
GET /notes(list),POST /notes(create),GET /notes/:id(read one),PUT/PATCH /notes/:id(update),DELETE /notes/:id(delete).The pattern that makes it 'RESTful': the noun lives in the URL (the resource) and the verb lives in the HTTP method — never
POST /createNoteorGET /deleteNote/1. The same path/notes/:idserves read, update, and delete by varying the method. That convention is why anyone can guess your API once they know the resource.What a strong answer coversResource in the URL (
/notes), action in the HTTP method.List
GET /notes, createPOST /notes, readGET /notes/:id, updatePUT/PATCH /notes/:id, deleteDELETE /notes/:id.Avoid verbs in the path (
/createNote,/deleteNote/1) — that's the anti-pattern.Predictable: knowing the resource lets a caller guess the endpoints.
Quick self-checkWhich is the RESTful way to delete the note with id 42?
-
Wrong — GET should be safe/read-only, and the verb shouldn't be in the path.
-
Wrong — verb-in-URL and wrong method; not RESTful.
-
Correct — the DELETE method on the resource URL; verb in the method, noun in the path.
-
Wrong — the specific resource should be addressed by URL: /notes/42.
Follow-ups they push on- Why is `POST /notes/123/delete` considered un-RESTful?
- How does this map back to CRUD and to SQL operations?
Red flag Putting the verb in the URL (`GET /getNotes`, `POST /deleteNote`) — it breaks the REST convention and the method↔CRUD mapping.
source: MDN — HTTP request methods ↗ -
What's the difference between a session and a JWT for keeping a user logged in?
Both answer 'how does the server know it's still you on the next request'. A session stores the auth state on the server (a session record) and gives the browser an opaque session ID (usually in a cookie); the server looks it up each request — easy to revoke, but it's stateful. A JWT is a signed token the server hands back that *contains* the claims (user id, expiry); the server just verifies the signature, no lookup needed — stateless and scalable, but hard to revoke before it expires.
Tradeoff in one line: sessions are easy to invalidate but require server state; JWTs are stateless and scale well but you can't easily 'log someone out' until the token expires.
What a strong answer coversSession: state lives server-side; browser holds an opaque ID; easy to revoke, but stateful.
JWT: signed token carries the claims; server verifies signature, no lookup; stateless.
JWT scales well (no shared session store) but is hard to revoke before expiry.
Both typically ride in a cookie or Authorization header on each request.
Follow-ups they push on- Why is logging a user out harder with JWTs?
- Why should a JWT have a short expiry?
Red flag Storing sensitive data in a JWT thinking it's hidden — a JWT is signed, not encrypted; its payload is readable by anyone who has the token.
source: MDN — HTTP authentication ↗ -
Why validate input on the server even when the frontend already validates the same form?
Frontend validation is a UX feature — it gives instant feedback so users fix mistakes fast — but it provides zero security, because the client is fully under the user's control. Anyone can bypass the form entirely and POST raw data with cURL, disabled JavaScript, or DevTools.
So the server must re-validate everything it receives as if no frontend existed: required fields, types, ranges, formats, and authorization. The two layers aren't redundant — they serve different jobs: the frontend for friendliness, the server for trust. Skipping server validation is how malformed and malicious data reaches your database.
What a strong answer coversFrontend validation = UX (fast feedback); it is not security.
The client is user-controlled — attackers bypass the form and POST directly.
The server must re-validate every request as if no frontend existed.
Both layers coexist: friendliness on the client, trust on the server.
Quick self-checkYour signup form checks the email format in JavaScript before submitting. Is server-side email validation still needed?
-
Wrong — the frontend can be bypassed entirely with a direct request.
-
Correct — client validation is UX only; the server must independently validate.
-
Wrong — HTTPS encrypts transit; it doesn't validate the payload.
-
Wrong — HTML5 validation also runs client-side and is bypassable.
Follow-ups they push on- How would someone bypass your frontend validation?
- Is server validation enough on its own (no frontend checks)?
Red flag Trusting client-side validation as a security boundary — it's trivially bypassed; the server is the only place validation actually protects you.
source: MDN — Form data validation ↗ -
Why move slow work (sending email, resizing an image, calling a slow API) to a background job instead of doing it in the request?
An HTTP request should return fast. If you do slow work inline — sending a welcome email, resizing an upload, calling a slow third-party API — the user waits the whole time, the request may time out, and a failure in that work fails the whole request.
The pattern is to enqueue the slow work and return immediately: accept the request, push a job onto a queue, respond '202 accepted / we're on it', and let a separate worker process the job later (with retries on failure). The user gets a snappy response; the slow, flaky, or retryable work happens out of band where a failure doesn't break the user's request.
What a strong answer coversRequests should be fast; slow inline work blocks the user and risks timeouts.
Enqueue the work, respond immediately, let a separate worker process it.
Background jobs can retry on failure without re-running the user's request.
Good fits: email, image/video processing, slow external API calls, report generation.
Follow-ups they push on- What does a queue + worker setup look like at a high level?
- How does a background job report success or failure back to the user?
Red flag Doing slow/flaky work inline in the request handler — one slow third-party call makes every user wait and turns a transient failure into a failed request.
source: MDN — Server-side programming: first steps ↗ -
What is CORS, and why does the browser block your frontend from calling an API on a different origin?
CORS (Cross-Origin Resource Sharing) is a browser security mechanism. By default the same-origin policy stops JavaScript on
app.example.comfrom reading responses from a different origin (different scheme, host, or port) — this prevents a malicious page from quietly calling APIs as you. CORS is the *opt-in* by which a server says 'these specific other origins are allowed', viaAccess-Control-Allow-Originand related response headers.Key nuance for builders: CORS is enforced by the browser, on the server's say-so. A CORS error isn't your frontend misbehaving — it means the API you're calling hasn't allow-listed your origin. The fix is on the server (or a proxy), not in the browser.
What a strong answer coversSame-origin policy blocks cross-origin reads by default; CORS is the server's opt-in to relax it.
Enforced by the browser, but configured via the server's response headers.
Access-Control-Allow-Originnames which origins may read the response.A CORS error means the target server hasn't allowed your origin — fix it server-side.
Follow-ups they push on- What makes two URLs the 'same origin'?
- Why can server-to-server requests ignore CORS entirely?
Red flag Trying to 'fix CORS' in the frontend code — the browser enforces it from the server's headers; the change must happen on the API or via a backend proxy.
source: MDN — Cross-Origin Resource Sharing (CORS) ↗