Learning Goals
3 minBy the end of this lesson you will be able to:
- Read a value from the URL with
req.queryand send it back. - Return data as JSON with
res.json({...})and see it in the browser. - Set the right status code by chaining
res.status(404).json({...}). - Install a REST client and send a
POSTwith a JSON body.
Warm-Up · Read the URL
5 minLast lesson you learned routes and req.params — the parts baked into the path, like the 7 in /user/7.
This route reads the other half of a URL — the bit after the ?. Read it, then predict what the visitor sees.
app.get('/search', (req, res) => {
res.json({ food: req.query.food })
})
// visit: /search?food=nasi+lemakPredict together
- What is the value of
req.query.food? - The URL has a
+in it. What do you think happens to it?
Reveal
The part after ? is the query string. Here it holds food=nasi+lemak, so req.query.food is the text "nasi lemak". Express turns the + back into a space for you. The browser shows { "food": "nasi lemak" }.
New Concept · The Letter and the Reply
12 minPicture the post. A letter lands on your desk — it says who is asking and what they want. That letter is req, the request. You write a reply and seal it in an envelope. That envelope is res, the response you send back.
Every Express route handler is handed both: (req, res) => { ... }. You read from req, and you reply through res.
Reading the request
Two ways to pull values out of the URL:
req.params
Values baked into the path itself.
Route /user/:id · URL /user/7
req.params.id is "7".
req.query
Values after the ?, as key=value pairs.
URL /search?name=Aisyah&city=KL
req.query.name is "Aisyah", req.query.city is "KL".
Writing the reply
Two ways to answer. They are not the same:
res.send('Hello') // plain text or HTML
res.json({ hello: 'world' }) // proper JSONres.send replies with text or an HTML page — great for a browser to display. res.json replies with JSON and tells the browser “this is data, not a page”. JSON is the format every real API speaks.
Setting the status code
Every reply carries a status code — a small number that says how it went. 200 means OK. 404 means “not found”. You set it by chaining res.status(...) before .json(...):
res.status(404).json({ error: 'Not found' })Why it matters
A backend rarely returns a web page. It returns data, and the React app you built in Level 2 turns that data into buttons and lists. That data travels as JSON, with a status code telling the frontend whether it worked. That is res.json and res.status — the two you will use all level.
Worked Example · A Search Route
12 minWe will build a tiny /search route with Aisyah. Start from the Express server you already know. Save this as server.js and run it with node server.js.
Step 1 — Read req.query and reply with JSON
// server.js — a search route
const express = require('express')
const app = express()
app.get('/search', (req, res) => {
const food = req.query.food
res.json({ youAskedFor: food })
})
app.listen(3000, () => {
console.log('Server on http://localhost:3000')
})Step 2 — Open it in the browser
With the server running, visit this address (the %20 is how a browser writes a space):
http://localhost:3000/search?food=nasi%20lemakThe page shows raw JSON — no HTML, just data:
{ "youAskedFor": "nasi lemak" }Step 3 — Reply with a status code when nothing was asked
What if a visitor opens /search with no ?food=? Then req.query.food is undefined. Send back a 404 and a helpful message. Update the route:
app.get('/search', (req, res) => {
const food = req.query.food
if (!food) {
return res.status(404).json({ error: 'No food given. Try /search?food=satay' })
}
res.json({ youAskedFor: food })
})Now visiting /search with nothing after it shows:
{ "error": "No food given. Try /search?food=satay" }What changed? Step 1 always replied 200 OK. Now the route reads the request first and picks the right status: 200 with data, or 404 with an error. That is exactly how real APIs behave.
Step 4 — Install a REST client
Everything so far went through the address bar. But a browser can only send GET that way — type a URL, press Enter, and it fetches. There is no box for a POST body.
So we need a tool that can. Install the REST Client extension in VS Code:
- Open VS Code.
- Click the Extensions icon in the left sidebar, or press
Ctrl+Shift+X(on a Mac,Cmd+Shift+X). - Type
REST Clientin the search box. - Pick the one published by Huachao Mao — it is the first result, with millions of installs.
- Click Install. No restart needed.
You could use the curl command instead, and Level 3 shows that later. The extension is friendlier to start with: your requests live in a file you can save, re-run and share, and there are no quoting rules that differ between Windows and Mac.
Step 5 — Write a request file
Next to server.js, make a new file called requests.http. The .http ending is what wakes the extension up.
### Search for a dish
GET http://localhost:3000/search?food=nasi lemak
### Add a dish — this request has a body
POST http://localhost:3000/dishes
Content-Type: application/json
{
"name": "Char kway teow",
"price": 8.5
}Three rules decide whether this works:
###separates one request from the next. Without it the extension reads them as a single muddled request.Content-Type: application/jsontells the server what the body is written in.- One blank line between the headers and the body. Miss it and the body is never sent. This is far and away the most common mistake.
A small Send Request link appears just above each ### block. Click the one above the GET first — the reply opens in a pane beside your file, showing the status line, the headers and the body. That is the same JSON you saw in the browser.
Step 6 — Send the POST and read what arrives
Your server has no /dishes route yet, so add one. It logs what came in and echoes it back:
app.post('/dishes', (req, res) => {
console.log('body:', req.body)
res.status(201).json({ received: req.body })
})201 is the status for “I created something new” — the right answer to a successful POST. Choosing codes well gets a lesson of its own, WEB-L3-11.
Stop the server with Ctrl + C and start it again with node server.js, so it picks up the new route. Then click Send Request above the POST.
The response pane shows:
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
{}And your terminal shows:
body: undefinedRead that carefully, because nothing is broken. The 201 proves your route ran, so the request definitely arrived — body and all.
But req.body is undefined. Express does not read the body unless you tell it to, and you have not told it yet. The reply looks empty for the same reason: res.json quietly drops any key whose value is undefined, so { received: undefined } goes out as {}.
One line of middleware fixes it — express.json() — and it is the first thing you meet in WEB-L3-06. Keep requests.http; you will click Send on this exact request next lesson and watch the dish appear.
Try It Yourself
13 minAdd these routes to your running server.js. Test each one by opening its address in the browser and reading the JSON.
Task 1 — An echo route
Make a route
/echothat readsreq.query.msgand replies withres.json({ msg: ... }). Visit/echo?msg=teh+tarikand check the browser shows{ "msg": "teh tarik" }.Task 2 — Bring back req.params
Make a route
/user/:idthat readsreq.params.idand replies withres.json({ id: ... }). Visit/user/42and confirm you see{ "id": "42" }. Notice the value is text, not a number.Task 3 — Guard a route with 400
Make a route
/greetthat needs a?name=query. If it is missing, reply withres.status(400).json({...})and an error message. If it is present, reply with a friendly greeting as JSON. Status400means “bad request” — the visitor left something out.
Mini-Challenge · Ringgit to Sen
8 minBuild a /convert route that turns ringgit into sen. There are 100 sen in one ringgit. It should read req.query.rm, do the maths, and reply with JSON.
It works if: visiting /convert?rm=10 shows { "rm": 10, "sen": 1000 } in the browser.
Hint: a query value always arrives as text. Wrap it in Number(...) before you multiply, or the maths goes wrong.
Reveal a sample answer
// add this route to server.js
const SEN_PER_RINGGIT = 100
app.get('/convert', (req, res) => {
const rm = Number(req.query.rm)
res.json({ rm: rm, sen: rm * SEN_PER_RINGGIT })
})Visiting /convert?rm=10 shows:
{ "rm": 10, "sen": 1000 }The Number(...) turns the text "10" into the number 10, so rm * 100 gives 1000, not a jumbled string. Reading with req.query, replying with res.json, and a little maths in between.
Recap
3 min- req is the request that arrived; res is the reply you send back.
req.queryreads thekey=valuepairs after the?in the URL.req.paramsreads values baked into the path, like:id.res.json({...})replies with JSON — the format real APIs use.- Chain
res.status(code).json({...})to send the right status, like404. - A browser address bar can only send
GET. Use a REST client forPOST,PUTandDELETE. - In a
.httpfile:###between requests, and one blank line before the body. - A
POSTbody arrives, butreq.bodyisundefineduntil you add middleware — next lesson.
New words
req.query— an object of the values after?in the URL.req.params— an object of the values captured from the path.res.json— sends the reply as JSON data, not an HTML page.- Status code — a number saying how the reply went (
200OK,404not found). req.body— data sent inside the request; it needs one line of middleware, coming next lesson.- REST client — a tool for sending requests a browser cannot, and saving them in a
.httpfile. 201 Created— the status that says “yourPOSTmade something new”.
Homework
4 min to brief · ~20 min to doRequired
- Add one route to
server.jsthat reads a query param and replies with JSON built from it. For example,/hello?name=Faizcould return{ "message": "Hi Faiz!" }. - Run the server, open your route in the browser, and take a screenshot of the JSON. Bring it to next lesson.
- In
requests.http, add aGETfor that same route and send it from the REST client. Screenshot the response pane so you can compare it with the browser.
Optional stretch
- Look up what the status codes
200,400,404and500mean. Write one plain-English line for each. - Give your
/convertroute a404reply whenrmis missing, usingres.status(404).json({...}).