Static Sites, Web Apps, and APIs

Table of Contents

  1. Same HTTP, Different Servers
  2. Static Sites
  3. Server-Rendered Pages
  4. Single-Page Apps
  5. Mixing the Three
  6. APIs: HTTP for Programs
  7. CORS, Briefly
  8. Frontend and Backend
  9. Try It Yourself

1. Same HTTP, Different Servers

Everything in this series so far is true for every website: URL, DNS, connection, request, response, rendering, cookies, caching. What changes from site to site is what the server does between receiving a request and sending the response.

There are three main answers, and you'll hear developers argue about them constantly:

KindWhere the HTML is madeWhen
Static siteAlready made, sitting in filesBefore anyone visits
Server-renderedOn the serverFresh, for every request
Single-page app (SPA)In the browser, by JavaScriptAfter the page loads

2. Static Sites

A static site is a folder of ready-made files. When a request arrives, the server finds the matching file and sends it back. That's all. No code runs, no database is asked anything.

 GET /my-site/about.html  ──▶  server reads about.html from disk  ──▶  200 + the file

Your GitHub Pages site is static: you wrote the HTML files, and GitHub's servers simply hand them out.

Static sites are:

  • Fast — nothing to compute, and every file can be cached by a CDN.
  • Cheap — often free. GitHub Pages, Cloudflare Pages, and Netlify host them at no cost.
  • Secure — there's no server code or database to attack.
  • Simple — nothing to crash at 3 a.m.

The limit: every visitor sees the same files. A static site can't show "Hi Dara" to Dara and "Hi Sokha" to Sokha, or save a comment you type — not on its own.

Static doesn't mean hand-written, either. Many static sites are generated by a tool that turns Markdown or data into HTML files once, at build time. This website is one. Every tutorial, including this chapter, is a Markdown file that Next.js turned into a plain HTML file when the site was built. There's no server program running for it, and no database — which is why it costs almost nothing to host.

3. Server-Rendered Pages

A server-rendered site runs code for every request. The server reads the request (path, cookies, query), asks a database for what it needs, builds HTML from it, and sends it back.

 GET /dashboard
 Cookie: session=a8f3…
        │
        ▼
 server code: who is a8f3…?  → Dara
              get Dara's orders from the database
              build the HTML: "Hi Dara! You have 3 orders…"
        │
        ▼
 200 + HTML made just for Dara

That's how you get pages that are personal and up to date: your inbox, your bank balance, your orders. The browser still receives ordinary HTML — it can't even tell whether a page came from a file or was built a millisecond ago.

The cost is a server that's always running, a database, and more things that can break — the 500s and 502s from chapter 6. Classic examples are PHP sites like WordPress, and frameworks like Django, Ruby on Rails, and Laravel.

4. Single-Page Apps

A single-page app moves the work into the browser. The server sends an almost empty HTML page and a big JavaScript file:

<body>
  <div id="root"></div>
  <script src="/app.81d0e47f.js"></script>
</body>

The JavaScript then builds the whole page itself, in the DOM. When it needs data, it uses fetch() (chapter 7) to ask a server for it. When you click a link, it doesn't load a new page — it changes the DOM and updates the address bar itself.

That's what you saw in chapter 7 when view source and the Elements tab looked completely different.

SPAs feel smooth, like an app on your phone: no white flash between pages, instant reactions to clicks. Gmail, Facebook, and Trello work this way. The costs are a big JavaScript download before anything appears, and pages that search engines and link previews may have trouble reading, because the HTML they receive is empty. React became famous for building SPAs.

5. Mixing the Three

In practice, modern frameworks mix all three — even page by page. With Next.js, which you'll meet on the Frontend track, one site can have:

  • A static blog and landing pages, built ahead of time.
  • Server-rendered dashboard pages that need fresh data.
  • Pages that load as HTML first and then behave like an SPA, once JavaScript takes over in the browser.

That's why the question isn't "which one is best?" but "which one fits this page?" A portfolio that changes once a month should be static. A bank account page must be server-rendered or fetch its data. You'll get a feel for it with practice.

6. APIs: HTTP for Programs

So far, responses have been HTML: meant for a browser to show to a person. But HTTP can carry any kind of data. When the client is a program — an SPA, a mobile app, another server — it doesn't want a web page. It wants data.

An API (Application Programming Interface) is a server that answers HTTP requests with data instead of pages, usually as JSON:

curl https://api.github.com/users/octocat
{
  "login": "octocat",
  "name": "The Octocat",
  "public_repos": 8,
  "followers": 21000,
  "created_at": "2011-01-25T18:44:36Z"
}

Same request, same response, same status codes, same headers — just Content-Type: application/json instead of text/html. Everything you learned in chapters 5 and 6 applies directly.

Most APIs follow a style called REST, which uses the URL for what and the method for what to do:

RequestMeaningTypical response
GET /usersList users200 + a JSON list
GET /users/42Get user 42200, or 404 if there's no user 42
POST /usersCreate a user201 Created
PATCH /users/42Update part of user 42200
DELETE /users/42Delete user 42204 No Content

This is exactly what you build in the NestJS Crash Course. And the ABA PayWay and Bakong KHQR tutorials are about calling someone else's API — a bank's — from your own server.

The mobile app on your phone, the banking app, the food delivery app — underneath, they're almost all clients sending HTTP requests to APIs. That's why this series sits at the very start of every track on the roadmap.

7. CORS, Briefly

Sooner or later, every frontend developer meets an error like this in the browser console:

Access to fetch at 'https://api.example.com/data' from origin
'http://localhost:3000' has been blocked by CORS policy

Here's what's going on. An origin is the scheme + host + port of a page: http://localhost:3000 and https://api.example.com are different origins. For safety, browsers don't let JavaScript on one origin read responses from another origin — otherwise, any website you visited could quietly read your email from another tab, using your cookies.

CORS — Cross-Origin Resource Sharing — is how a server says "it's okay, I allow that origin", with a response header:

Access-Control-Allow-Origin: http://localhost:3000

For some requests, the browser first sends an OPTIONS request (the method from chapter 5) — called a preflight — to ask permission before sending the real one.

Two things to remember:

  • CORS is enforced by the browser, not the server. That's why the same request works perfectly in curl and fails in the browser.
  • The fix is on the server that owns the API: it has to send the right headers. You can't fix a CORS error from the frontend.

You'll set this up yourself on the Backend track.

8. Frontend and Backend

You can now put a precise meaning on two words that appear in almost every job ad:

  • Frontend is everything that happens in the browser: the HTML, CSS, and JavaScript from chapter 7 — layout, interaction, calling APIs with fetch().
  • Backend is everything that happens on the server: receiving requests, checking cookies or tokens, talking to the database, and sending responses — chapters 5, 6, and 8 from the server's side.

They meet at exactly one place: the HTTP request and response. A frontend developer and a backend developer who both understand this series can always work out where a problem is. Open the Network tab, find the request, and ask: did the frontend send the right thing? Did the backend send back the right answer?

9. Try It Yourself

  1. Call a real API. Run:

    curl https://api.github.com/users/<your-username>
    curl -I https://api.github.com/users/<your-username>
    

    Find your public_repos count. In the headers, find content-type and the x-ratelimit-remaining header. What do you think it counts? (Remember 429 from chapter 6.)

  2. Get a 404 from an API. Ask for a user that doesn't exist, like curl -i https://api.github.com/users/this-user-does-not-exist-123 (-i shows the headers and the body). APIs send errors as JSON too.

  3. Classify sites. For three sites you use, open view source and the Elements tab, and look at the Fetch/XHR filter in the Network tab. Is each one static, server-rendered, an SPA, or a mix?

  4. Watch an app use its API. Open a site you're logged in to (like GitHub or YouTube) with the Fetch/XHR filter on, then click around. Click one of the requests and look at its Response tab. It's JSON — the frontend talking to the backend.

  5. See CORS headers. Run curl -I https://api.github.com/users/octocat and look for access-control-allow-origin. What does its value mean?

In the final chapter, we'll put everything together and trace one URL, from the moment you press Enter to the moment the page is on your screen.

Buy Me a Coffee

Found this tutorial helpful? Consider buying me a coffee to support more free content!