๐Ÿ  Home 01 / 18 Next: Python Async โ†’

How the Web Works

Module 1 ยท Foundation ยท โฑ ~20 min
Why this matters: Every feature in JongYang โ€” a customer booking a court, an admin checking their schedule, Omise sending a payment confirmation โ€” is an HTTP request. If you don't know what HTTP is, none of the backend code will make sense.

What Happens When You Type a URL

When you type https://bkk-sports-club.jongyang.xyz and press Enter, a chain of events happens:

  1. DNS lookup: Your computer asks a DNS server "what IP address is bkk-sports-club.jongyang.xyz?" It gets back 195.201.126.8.
  2. TCP connection: Your browser opens a connection to that IP on port 443 (HTTPS).
  3. TLS handshake: Browser and server agree on encryption. The server proves its identity with a certificate (auto-managed by Caddy).
  4. HTTP request: Browser sends a request: "GET / HTTP/1.1" with headers like Host: bkk-sports-club.jongyang.xyz.
  5. Response: The server sends back HTML, CSS, JS. Browser renders it.
You type: https://bkk-sports-club.jongyang.xyz

  [Browser]
     โ”‚
     โ”‚ 1. DNS: "what IP is bkk-sports-club.jongyang.xyz?"
     โ”‚         Answer: 195.201.126.8
     โ”‚
     โ”‚ 2. TCP connect to 195.201.126.8:443
     โ”‚
     โ–ผ
  [Caddy โ€” reverse proxy]
     โ”‚  TLS terminates here. Caddy decrypts the request.
     โ”‚  Sees Host: bkk-sports-club.jongyang.xyz
     โ”‚  Routes to โ†’ nextjs container on port 3000
     โ”‚
     โ–ผ
  [Next.js container :3000]
     โ”‚  Middleware rewrites: bkk-sports-club.jongyang.xyz/
     โ”‚  โ†’ internal route /shop/bkk-sports-club
     โ”‚  Fetches data from FastAPI
     โ”‚
     โ–ผ
  [FastAPI container :8000]  โ†’  [PostgreSQL :5432]
     Returns shop data as JSON

HTTP Methods โ€” The Verbs

HTTP has "methods" that describe what you want to do with a resource. Think of them as verbs:

MethodMeansExample in JongYang
GETRead data โ€” don't change anythingGET /api/shops โ€” list all shops
POSTCreate something newPOST /api/bookings โ€” make a booking
PATCHPartially update somethingPATCH /api/admin/bookings/id/confirm
PUTReplace something entirelyNot used much in REST APIs
DELETERemove somethingDELETE /api/admin/resources/id

A key rule: GET requests must not change data. They are "safe" โ€” you can call them 100 times and the result is the same. POST/PATCH/DELETE may change state.

HTTP Status Codes โ€” The Answers

When a server responds, it includes a 3-digit status code. The first digit tells you the category:

Codes you'll see in JongYang's FastAPI:

CodeMeaningWhen it appears
200 OKSuccess, returns dataGET /api/shops โ€” shop list returned
201 CreatedSomething was createdPOST /api/bookings โ€” booking created
400 Bad RequestMalformed requestWebhook signature invalid
401 UnauthorizedNot logged inAdmin endpoint without valid JWT
404 Not FoundResource doesn't existGET /api/bookings/nonexistent-uuid
409 ConflictState conflictBooking a time slot already taken
422 UnprocessableValidation failedMissing required field, past booking date

JSON โ€” How Data Travels

HTTP requests and responses carry data. REST APIs use JSON (JavaScript Object Notation) โ€” a text format that both humans and computers can read easily. It looks like this:

json
{
  "id": "705f95b6-010f-4038-adf1-427f1e0f5527",
  "shop_id": "fa2fd810-04a8-41f7-bcc1-dcb3889b9ac1",
  "resource_id": "02553923-fb55-443e-b87f-0f28b356b5b7",
  "service_id": null,
  "customer_name": "Test User",
  "customer_phone": "0812345678",
  "booking_date": "2026-06-25",
  "start_time": "07:00:00",
  "end_time": "08:00:00",
  "duration_min": 60,
  "total_price": "150.00",
  "status": "pending_payment",
  "promptpay_qr_url": "https://api.omise.co/charges/chrg_.../documents/docu_.../download",
  "expires_at": "2026-06-22T10:58:07.389599Z",
  "created_at": "2026-06-22T10:43:07.352663Z"
}

This is the JSON returned when a customer creates a booking. Notice:

What Is a REST API?

REST (Representational State Transfer) is a pattern for organizing web APIs. The key idea: treat everything as a resource, give it a URL, and use HTTP methods to operate on it.

JongYang's API is organized like this:

text
Resource: shops
  GET    /api/shops              โ†’ list all shops
  GET    /api/shops/{slug}       โ†’ get one shop by slug

Resource: availability
  GET    /api/availability/{resource_id}?date=2026-06-25&duration_min=60

Resource: bookings
  POST   /api/bookings           โ†’ create a booking
  GET    /api/bookings/{id}      โ†’ get a booking

Resource: admin bookings
  GET    /api/admin/bookings     โ†’ list shop's bookings (requires JWT)
  POST   /api/admin/bookings/{id}/confirm  โ†’ manually confirm

Notice /api/shops/{slug} โ€” the {slug} is a path parameter. It's part of the URL. Compare with ?date=2026-06-25 โ€” that's a query parameter, appended after ?. Path parameters identify which resource you want; query parameters filter or configure the response.

Why Separate Frontend and Backend?

JongYang has two separate applications:

They communicate via HTTP: the frontend makes API calls to https://api.jongyang.xyz/... and gets JSON back. This separation means:

Project Trace: Customer Books a Court

Let's walk through what happens when a customer fills out the booking form and clicks "Confirm & Pay":

BROWSER (React, running in customer's phone)
  โ”‚
  โ”‚  POST https://api.jongyang.xyz/api/bookings
  โ”‚  Body: {"resource_id":"...", "booking_date":"2026-06-25",
  โ”‚         "start_time":"07:00:00", "duration_min":60,
  โ”‚         "customer_name":"Test User", "customer_phone":"0812345678"}
  โ”‚
  โ–ผ
CADDY (reverse proxy)
  โ”‚  Sees: Host: api.jongyang.xyz
  โ”‚  Routes to: fastapi container on port 8000
  โ”‚
  โ–ผ
FASTAPI (create_booking function in routers/public/bookings.py)
  โ”‚  1. Validates the request body (Pydantic)
  โ”‚  2. Looks up the Resource in PostgreSQL
  โ”‚  3. Calculates end_time (07:00 + 60min = 08:00)
  โ”‚  4. Checks availability โ€” is 07:00 actually free?
  โ”‚  5. Creates Booking row in PostgreSQL (status: "pending_payment")
  โ”‚  6. Calls Omise API โ†’ gets PromptPay QR URL
  โ”‚  7. Returns JSON with booking details + QR URL
  โ”‚
  โ–ผ
BROWSER
  โ”‚  Receives 201 Created + booking JSON
  โ”‚  Shows the PromptPay QR code + 15-minute countdown timer
  โ”‚
  โ–ผ  (customer scans QR with their banking app)
  โ”‚
OMISE API
  โ”‚  Receives payment โ†’ sends webhook to our server
  โ”‚
  โ–ผ
FASTAPI (webhooks/omise.py)
     Updates booking.status = "confirmed"
     Sends LINE push notification to customer

๐Ÿง  Self-Check Quiz

1. A customer requests GET /api/shops 50 times. How many times does the data change in the database?

Zero. GET requests are "safe" โ€” they must not change any state. All 50 calls just read the data and return it.

2. The booking API returns status 409 when you try to book a time slot. What does 409 mean and why does it happen here?

409 Conflict โ€” the requested state conflicts with the current state of the resource. Here it means the time slot is already booked (another customer has a pending_payment or confirmed booking at that time). It's not your fault (400) and it's not a server error (500) โ€” it's a logical conflict.

3. In JSON, why is total_price stored as the string "150.00" instead of the number 150.00?

JSON numbers are floating-point, which can introduce tiny rounding errors (0.1 + 0.2 = 0.30000000000000004). Money must be exact. Storing prices as strings (or using Python's Decimal type) avoids this. The database also uses NUMERIC(10,2), not float.

4. What's the difference between a path parameter and a query parameter? Give one example of each from the JongYang API.

Path parameter: part of the URL path, identifies a specific resource. Example: /api/shops/{slug} where slug is bkk-sports-club.

Query parameter: appended after ?, filters or configures the response. Example: /api/availability/{id}?date=2026-06-25&duration_min=60.

5. Why do we have a separate frontend (Next.js) and backend (FastAPI) instead of one single application?

Separation of concerns: the frontend handles UI rendering, the backend handles business logic and data. Benefits: you can change the UI without touching DB code, add a mobile app using the same API, keep the database unexposed to browsers, and let different teams work independently. The database never gets direct calls from a user's browser.