Skip to main content
Backend & APIs
⚙️ Backend & APIsLesson 1 of 13

What a Backend Actually Is

What a backend really is, and why business logic must live on the server — never the browser.

What a Backend Actually Is

Stage 3 · Backend & APIs · B.U.I.L.D. letter: U

The browser is a public storefront anyone can walk into and rearrange — your server is the locked stockroom where the real work happens.


⚠️ The vibe trap

You built something real: a beautiful UI, smooth animations, buttons that do things. Then you pushed it to Vercel, sent the link, and called it shipped. Here is the trap: if your app's logic — the rules that decide who can do what, the prices, the user data, the secrets — all lives in JavaScript that runs inside the browser, then every single one of your users can open DevTools, pause execution, edit variables, and bypass every guard you wrote. There is no backend "protecting" things; there is just your frontend, fully exposed on their machine. Vibe coding is an incredible way to build fast. Stage 3 is where you learn what "built right" actually means.


🖥️ The two computers in every web request

When you visit a URL, two separate computers are involved:

  • The client — usually a browser running on your user's machine. It renders HTML, runs JavaScript, shows pixels on screen. You have zero control over this machine. The user can modify it, disable scripts, spoof headers, or send completely fake requests.
  • The server — a process running on a machine you (or your cloud provider) control. It receives requests, executes your code, and sends back responses.

These two computers communicate over HTTP: the client sends a request, the server processes it and sends a response. Nothing more, nothing less.

User's Browser (client)          Your Server
        |                               |
        |  --- HTTP Request ----------> |
        |                               |  runs your code
        |                               |  talks to DB
        |  <-- HTTP Response ---------- |
        |                               |
   renders HTML/JSON                 (done)

The critical insight: the server only knows what arrives in the HTTP request. It cannot see what the user's JavaScript is doing, what their screen looks like, or what they intended. It sees a text-based HTTP message and must decide what to do based only on that.

Common mistake: Thinking "I'll just check this on the front end." A check that lives only in the browser can be skipped entirely by sending a raw HTTP request with curl or a script. Your server must enforce every rule independently, as if the client is hostile — because sometimes it is.


🔒 Why business logic and secrets MUST live server-side

Here is the trust boundary, the most important mental model in this entire track:

╔══════════════════════════════╗      ╔══════════════════════════════╗
║         UNTRUSTED            ║      ║           TRUSTED            ║
║    (anything on the client)  ║      ║       (your server)          ║
║                              ║      ║                              ║
║  - JavaScript in the browser ║      ║  - Business rules            ║
║  - LocalStorage              ║      ║  - Price calculations        ║
║  - Cookies (readable by JS)  ║      ║  - Authorization checks      ║
║  - URL parameters            ║      ║  - API keys / secrets        ║
║  - Form values               ║      ║  - Database                  ║
║  - Request body              ║  →   ║  - Source of truth           ║
╚══════════════════════════════╝      ╚══════════════════════════════╝
         Everything here                  Nothing here is visible
         is controllable by                to the client unless
         the user                          you explicitly send it

Three concrete things that MUST live on the server — never in the browser:

1. Secrets (API keys, database passwords). If you put OPENAI_API_KEY in browser JavaScript — even inside an env variable that gets bundled by Vite — it ends up in the JavaScript file that gets delivered to the browser. Anyone can View Source and steal it. The server holds the key; the browser asks the server to do the AI work.

2. Authorization checks. "Can this user see this resource?" is not a UI question. Your React component hiding a button is decoration. The server must independently verify on every request: does this session token belong to someone allowed to do this?

3. The source of truth. The canonical state of your application lives in a database the server controls. A user's balance, their subscription tier, their order history — none of this should be computed or stored solely on the client. The server reads from and writes to the database; the client gets a view of that data for this moment.

Common mistake: Building a pricing page where the price shown to the user is also the price sent to your payment processor. A user can intercept the request and change amount: 2999 to amount: 1. Always re-fetch and recalculate price on the server when processing a payment.


⚙️ What a server, a runtime, and a process actually are

These words get thrown around interchangeably. They mean different things.

  • Runtime — the engine that executes your code. Node.js is a JavaScript runtime: it takes your .js files and runs them outside the browser, using Google's V8 engine plus a library of OS-level capabilities (file system, network, etc.).
  • Process — a running instance of your program. When you type node server.js, the OS starts a process: it allocates memory, loads your code, and begins executing. That process is listening on a network port, waiting for HTTP requests.
  • Server — loosely, either the machine the process runs on, or the process itself. In conversation, "the server" usually means the process.

Here is the smallest possible Node.js HTTP server, zero dependencies:

// server.js
const http = require('http');

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello from the server\n');
});

server.listen(3000, () => {
  console.log('Server running at http://localhost:3000');
});
node server.js
# → Server running at http://localhost:3000

curl http://localhost:3000
# → Hello from the server

What just happened:

  1. http.createServer registered a callback function — the request handler.
  2. server.listen(3000) told the OS: "reserve TCP port 3000 for this process."
  3. When curl connected, the OS handed the incoming data to our process.
  4. Our callback ran, built a response, and sent it back.

The browser hitting your deployed URL does the exact same thing, just over the public internet instead of your laptop's loopback network.

Common mistake: Confusing the port with the server. Port 3000 is just a number — a door label. Multiple processes can't use the same door at once. If you get EADDRINUSE, another process already owns that port. Kill it or pick a different port.


🛣️ The request → server → response lifecycle in full detail

Every HTTP interaction follows this sequence. Understanding it cold is the foundation of everything in this track.

1. Client builds a request
   ├── Method: GET, POST, PUT, DELETE, PATCH …
   ├── URL: https://api.yourapp.com/users/42
   ├── Headers: Authorization, Content-Type, Accept …
   └── Body: JSON, form data, file bytes … (POST/PUT only)

2. DNS resolves the hostname to an IP address

3. TCP connection opens to that IP on port 443 (HTTPS) or 80 (HTTP)

4. Request bytes travel to the server

5. Server framework parses the request into a usable object

6. Route matching: "does /users/42 with method GET match any handler I registered?"

7. Your handler runs:
   ├── Read data from the request
   ├── Validate inputs
   ├── Apply business logic
   ├── Query / update the database
   └── Build a response

8. Server sends the response
   ├── Status code: 200, 201, 400, 401, 404, 500 …
   ├── Headers: Content-Type, Set-Cookie, Cache-Control …
   └── Body: JSON, HTML, empty …

9. Connection closes (or is reused for the next request)

10. Client code receives the response and decides what to do

Here is that same lifecycle expressed in Express.js, the framework you will use throughout this track:

// npm install express
const express = require('express');
const app = express();

app.use(express.json()); // parse JSON request bodies

// GET /users/:id — fetch a single user
app.get('/users/:id', async (req, res) => {
  const userId = req.params.id;         // from the URL
  const token  = req.headers.authorization; // from the headers

  // 1. Authentication check (simplified — real version in Lesson 7)
  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }

  // 2. Business logic / DB call (simplified)
  const user = await db.findUserById(userId); // your DB layer
  if (!user) {
    return res.status(404).json({ error: 'User not found' });
  }

  // 3. Only send back what the client is allowed to see
  res.status(200).json({
    id:    user.id,
    name:  user.name,
    email: user.email,
    // NOT: user.passwordHash, user.internalScore, etc.
  });
});

app.listen(3000, () => console.log('Listening on port 3000'));

Walk through this line by line:

  • app.get('/users/:id', ...) — registers a route: "when a GET request arrives for any path matching /users/<something>, run this function."
  • req is the request object: .params, .query, .headers, .body all parsed and ready.
  • res is the response builder: .status() sets the HTTP status code, .json() serializes the object and sets Content-Type: application/json.
  • Returning early with return res.status(401).json(...) short-circuits: nothing after that line runs.

Common mistake: Calling res.json() twice in the same handler. The second call will throw "Cannot set headers after they are sent." Use return every time you send a response so the function exits immediately.


🌐 "It works in my browser" is not "it's real"

"It works in my browser" means: when you, logged in as yourself, on your machine, with your dev server running, click this button, the result looks correct on screen.

That is not the same as:

  • It works when a user in a different timezone hits the deployed URL
  • It works when they send the request twice simultaneously
  • It works when they modify the request with a browser extension
  • It works when your database is under load
  • It works when the third-party API you call is slow
  • It works when an unauthenticated attacker probes the endpoint

Your server is the only place where "it works" can be rigorously defined, because the server is the only part of the system you control. Every guarantee your app makes — "the price will be correct," "only the owner can delete their data," "this action will be recorded" — has to be enforced in server code.

The frontend is a mirror. The backend is the thing being reflected.

Here is a tiny but complete server you can actually run to feel this:

# In a new folder:
mkdir my-first-server && cd my-first-server
npm init -y
npm install express
// index.js
const express = require('express');
const app = express();
app.use(express.json());

// In-memory "database" for now
const items = [
  { id: 1, name: 'Notebook', price: 499 },
  { id: 2, name: 'Pen',      price: 199 },
];

app.get('/items', (req, res) => {
  res.json(items);
});

app.get('/items/:id', (req, res) => {
  const item = items.find(i => i.id === Number(req.params.id));
  if (!item) return res.status(404).json({ error: 'Not found' });
  res.json(item);
});

app.post('/items', (req, res) => {
  const { name, price } = req.body;

  // Business rule enforced server-side — the client does NOT decide the price format
  if (typeof price !== 'number' || price <= 0) {
    return res.status(400).json({ error: 'price must be a positive number (in cents)' });
  }

  const newItem = { id: items.length + 1, name, price };
  items.push(newItem);
  res.status(201).json(newItem);
});

app.listen(3000, () => console.log('http://localhost:3000'));
node index.js

# In another terminal:
curl http://localhost:3000/items
# [{"id":1,"name":"Notebook","price":499},{"id":2,"name":"Pen","price":199}]

curl http://localhost:3000/items/1
# {"id":1,"name":"Notebook","price":499}

curl -X POST http://localhost:3000/items \
  -H "Content-Type: application/json" \
  -d '{"name":"Marker","price":149}'
# {"id":3,"name":"Marker","price":149}

# Try to break the business rule:
curl -X POST http://localhost:3000/items \
  -H "Content-Type: application/json" \
  -d '{"name":"Free stuff","price":-1}'
# {"error":"price must be a positive number (in cents)"}

Notice what just happened: the business rule rejected the bad price regardless of what the client sent. The client has zero ability to override that. That is a backend.


🛠️ Your mission

Take any app you have already built (or start a blank one). Add a real server process:

  1. Create a new folder, run npm init -y && npm install express.
  2. Write an index.js with at least two GET routes and one POST route that accepts a JSON body.
  3. Add one server-side business rule — a check the client cannot bypass — that returns a 400 error when violated.
  4. Test every route with curl from a separate terminal window, including sending a request that deliberately violates your business rule to confirm the server rejects it.
  5. Identify one piece of logic in your existing app that currently runs only in the browser and write a comment explaining why it belongs on a server instead.

The goal is not to finish a feature. The goal is to feel the difference between code that runs in your browser and a process that runs on a machine you control.


✅ You're done when…

  • You can start your server with node index.js and hit it from curl without touching the browser at all.
  • Your POST route validates its input on the server side and returns a 400 with a descriptive error when the input is invalid — not a 500, not silence (Pre-Ship Checklist).
  • You can name, out loud, one piece of logic from your current project that must move server-side and explain why (hint: secrets, authorization, or source-of-truth).
  • You understand that return res.json() exits the handler and why forgetting the return causes a "headers already sent" crash (Production-Readiness Checklist).

➡️ Next: HTTP, Deeply.

Build It Right, Or Don't Build It At All. 🏛️

Always-on rigor toolkit

🏛️ Build It Right, Or Don't Build It At All.