Learning Goals
3 minBy the end of this lesson you will be able to:
- Fetch a list from your API with
await fetch(...)andawait res.json(). - Render each item into the page as a live list on screen.
- POST a new item from a form, sending JSON with
method,headersandbody. - Re-draw the list so the new item appears without a page reload.
Warm-Up · Guess the Order
5 minLast lesson you unblocked the browser with CORS. Now the browser can call your API. Read this small snippet, then predict the order the three letters print:
console.log('A')
fetch('/api/students').then(() => console.log('B'))
console.log('C')Predict together
A fetch takes time — the reply travels back over the network. In what order do A, B and C reach the console?
Reveal
The order is A, C, B. Lines 1 and 3 run straight away. The fetch on line 2 does not wait — it starts the request and moves on. The .then(...) code runs later, once the reply arrives. That waiting is exactly what await helps us tidy up today.
New Concept · The Phone Call to the Backend
12 minThink of fetch as a phone call from your front-end to your backend. The page dials a URL, waits for the reply, and hangs up. JSON is the language both sides speak — a shared way to write data as plain text.
Every request takes time, so fetch hands back a promise — an “I owe you a reply” note. We use await to wait politely for that reply before reading it.
The syntax box
Two lines fetch a list and turn it into a real JavaScript array:
const res = await fetch('/api/students')
const data = await res.json()The first await waits for the reply to arrive. The second waits while the JSON text is turned back into objects you can loop over. Any function using await must be marked async.
Sending data back — POST
A plain fetch reads. To create something you pass a second argument describing the request:
await fetch('/api/students', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Priya' })
})Reading (GET)
Just the URL — fetch(url).
GET is the default verb.
Reply comes back as JSON to render.
Creating (POST)
method says which verb to use.
headers says “I am sending JSON”.
body is your data as a JSON string.
JSON.stringify(...) turns a JavaScript object into JSON text, ready to travel. It is the exact opposite of res.json(), which turns text back into objects.
Why it matters
This is how every single-page app talks to its backend. When you open Shopee, the page fetches live prices from an API and draws them in. Your future React app will do the same — the same fetch, moved inside a hook.
Worked Example · A Live Student Board
12 minWe will give Aisyah's students API from Lesson 10 a real face — a page that lists students and lets you add one. Keep your server running with node server.js.
Step 1 — A page with a list and a script
Inside your public folder, make index.html. It has an empty <ul> for the students and loads a script file:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Tuition Centre</title>
</head>
<body>
<h1>Our Students</h1>
<ul id="student-list"></ul>
<script src="app.js"></script>
</body>
</html>Step 2 — Fetch the list and draw it
Next to it, make app.js. On load it fetches the students, clears the list, then adds one <li> per student:
async function loadStudents() {
const res = await fetch('/api/students')
const students = await res.json()
const list = document.querySelector('#student-list')
list.innerHTML = '' // clear before redrawing
for (const student of students) {
const li = document.createElement('li')
li.textContent = student.name
list.appendChild(li)
}
}
loadStudents()The page now shows a real list, built from your API:
Our Students
• Aisyah
• Wei JieStep 3 — Serve the page from the same origin
In server.js, serve the public folder with express.static (from Lesson 7), above app.listen:
app.use(express.static('public'))Now open http://localhost:3000/. The page and the API share one origin, so the browser makes no fuss — no CORS needed at all.
Step 4 — A form that POSTs a new student
Add a small form to index.html, above the script tag:
<form id="add-form">
<input id="name-input" placeholder="New student name" />
<button>Add</button>
</form>Then, at the bottom of app.js, listen for the submit, POST the name, and reload the list:
const form = document.querySelector('#add-form')
form.addEventListener('submit', async (event) => {
event.preventDefault() // stop the page reloading
const name = document.querySelector('#name-input').value
await fetch('/api/students', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name })
})
event.target.reset() // empty the box
loadStudents() // redraw with the new student
})Type Priya, press Add, and she appears in the list at once:
Our Students
• Aisyah
• Wei Jie
• PriyaWhat changed? The page never reloaded. Your form sent JSON to the API, the API stored it, and loadStudents drew the fresh list. That round trip is the whole heart of a modern web app.
A note for later: in a React app (Level 2), the same fetch lives inside a useEffect for the first load, and inside the submit handler for the POST — same idea, tidier home.
Try It Yourself
13 minKeep working in index.html and app.js. Refresh the browser after each change to see it live.
Task 1 — Show a second field
Give each student a
formfield on the server (say “Form 3”), then show it on the page. Change one line inloadStudentsso each row reads the name and the form:li.textContent = student.name + ' — ' + student.formTask 2 — A Delete button
In the loop, add a
<button>to each row. When it is clicked, send a DELETE for that student's id, then callloadStudentsagain to redraw:const del = document.createElement('button') del.textContent = 'Delete' del.addEventListener('click', async () => { await fetch('/api/students/' + student.id, { method: 'DELETE' }) loadStudents() }) li.appendChild(del)Task 3 — A friendly failure
What if the server is off? Wrap the fetch in a
try / catchand show a kind message instead of a blank page. Put the whole body ofloadStudentsinsidetry, and add:} catch (error) { list.innerHTML = '<li>Sorry, could not reach the server.</li>' }
Mini-Challenge · The Self-Updating Board
8 minBuild a tiny page that lists the students from /api/students and adds a new one through a form. Combine today's fetch (GET and POST) with the form events from Level 2.
It works if: you type a name, press Add, and the new student appears at the bottom of the list without a manual browser refresh.
Reveal a sample answer
The page — public/index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Student Board</title>
</head>
<body>
<h1>Our Students</h1>
<ul id="student-list"></ul>
<form id="add-form">
<input id="name-input" placeholder="New student name" />
<button>Add</button>
</form>
<script src="app.js"></script>
</body>
</html>The script — public/app.js:
async function loadStudents() {
const res = await fetch('/api/students')
const students = await res.json()
const list = document.querySelector('#student-list')
list.innerHTML = ''
for (const student of students) {
const li = document.createElement('li')
li.textContent = student.name
list.appendChild(li)
}
}
const form = document.querySelector('#add-form')
form.addEventListener('submit', async (event) => {
event.preventDefault()
const name = document.querySelector('#name-input').value
await fetch('/api/students', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name })
})
event.target.reset()
loadStudents()
})
loadStudents()The trick is the last loadStudents() call inside the submit handler. After the POST succeeds, we fetch the list again, so the new student is drawn in straight away — no refresh.
Recap
3 minfetch(url)reads from your API;await res.json()gives you the data.- To create, pass
method,headersandbodytofetch. JSON.stringify(...)turns an object into JSON text to send.- Any function that uses
awaitmust be markedasync. - Redraw the list after a change so the page updates without a reload.
- In a React app you run the same
fetchinside auseEffect— that is Level 2 and the Next.js Optional.
New words
- fetch — the browser function that calls a URL and returns a promise.
- method / headers / body — the three parts of a request that sends data.
- JSON.stringify — turns a JavaScript object into JSON text.
- async / await — the tidy way to wait for a promise before using its result.
Homework
4 min to brief · ~20 min to doRequired
- Connect a simple front-end page to one of your CRUD APIs. It must fetch and render a list and POST a new item from a form, drawing the fresh list with no reload. Take a screenshot of the working page — before and after adding an item — and bring it next lesson.
Optional stretch
- On paper, sketch how this Worked Example would look as a React component: a
useStatefor the list, auseEffectthat fetches on load, and a submit handler that POSTs. You do not need to run it — just draw the shape. - Add a small
“Loading…”message that shows while the first fetch is in flight, and clears once the list arrives.