How the Web Works
What Happens When You Type a URL
When you type https://bkk-sports-club.jongyang.xyz and press Enter, a chain of events happens:
- DNS lookup: Your computer asks a DNS server "what IP address is bkk-sports-club.jongyang.xyz?" It gets back
195.201.126.8. - TCP connection: Your browser opens a connection to that IP on port 443 (HTTPS).
- TLS handshake: Browser and server agree on encryption. The server proves its identity with a certificate (auto-managed by Caddy).
- HTTP request: Browser sends a request: "GET / HTTP/1.1" with headers like
Host: bkk-sports-club.jongyang.xyz. - 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:
| Method | Means | Example in JongYang |
|---|---|---|
GET | Read data โ don't change anything | GET /api/shops โ list all shops |
POST | Create something new | POST /api/bookings โ make a booking |
PATCH | Partially update something | PATCH /api/admin/bookings/id/confirm |
PUT | Replace something entirely | Not used much in REST APIs |
DELETE | Remove something | DELETE /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:
- 2xx โ Success
- 4xx โ Your fault (bad request, not authorized, not found)
- 5xx โ Server's fault
Codes you'll see in JongYang's FastAPI:
| Code | Meaning | When it appears |
|---|---|---|
| 200 OK | Success, returns data | GET /api/shops โ shop list returned |
| 201 Created | Something was created | POST /api/bookings โ booking created |
| 400 Bad Request | Malformed request | Webhook signature invalid |
| 401 Unauthorized | Not logged in | Admin endpoint without valid JWT |
| 404 Not Found | Resource doesn't exist | GET /api/bookings/nonexistent-uuid |
| 409 Conflict | State conflict | Booking a time slot already taken |
| 422 Unprocessable | Validation failed | Missing 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:
{
"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:
- Keys are strings in double quotes
- Values can be strings, numbers, booleans,
null, arrays, or nested objects nullmeans "no value" (the service_id is null because this is an asset booking)- Numbers don't need quotes; strings do
- The
total_priceis"150.00"as a string โ because JSON numbers lose decimal precision, and money must be exact
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:
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:
- Frontend (Next.js) โ runs in the browser (and partially on the Next.js server). Renders the UI. Knows nothing about databases.
- Backend (FastAPI) โ runs on the server. Handles business logic, talks to the database, calls Omise, sends LINE messages.
They communicate via HTTP: the frontend makes API calls to https://api.jongyang.xyz/... and gets JSON back. This separation means:
- You can change the UI without touching the database code
- You can add a mobile app later that uses the same API
- The database is never directly exposed to the browser
- Different teams can work independently
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?
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?
3. In JSON, why is total_price stored as the string "150.00" instead of the number 150.00?
4. What's the difference between a path parameter and a query parameter? Give one example of each from the JongYang API.
/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?