Expressing "create a new order"
In a REST API, that action is POST /orders.
The method is the verb, where POST means create, and the path is the noun collection the new thing joins. No verbs go in paths, ever, so /createOrder is the shape to avoid.
The order details travel in the request body rather than the path, because they describe the thing being made rather than identifying it. The server assigns the new id and returns it, usually with a 201 status.
Express, which you meet now, is built exactly around this method-plus-path pairing. app.post("/orders", handler) is the framework's way of writing the same sentence.
What Express is
Express is the most-used Node web framework, a thin layer over http that does the chores you have been hand-rolling: route matching, params, query parsing, JSON responses. You install it from npm, Node's package registry:
npm install express
const express = require("express"); const app = express(); app.get("/users/:id", (req, res) => { res.json({ id: req.params.id, name: "Ada" }); }); app.listen(3000);
Everything here is familiar. app.get("/users/:id", handler) is your routing table from lesson 3-2, :id is your matchPath with captures (req.params.id), the handler is a callback, and res.json sets the JSON header and stringifies for you.
What Express hands you on req
For a request to GET /posts?status=draft&page=2 matched by app.get("/posts", ...):
| Property | Value | Filled by |
|---|---|---|
req.params | {} (no :segments here) | the route pattern |
req.query | { status: "draft", page: "2" } | the query string |
req.body | the parsed JSON body | express.json() middleware (next lesson) |
Note every value is a string. page arrives as "2", not 2.
That req.query object comes from parsing status=draft&page=2: split on &, then split each piece on =. You have written parsers twice already (lessons 1-3 and 3-2). Write this one and you will never wonder what a framework does with query strings again.
The query-string parser Express runs for you
parseQuery(qs) turns "status=draft&page=2" into an object, and an empty string into {}.
It is two splits. The outer split("&") separates the pairs, and the inner split("=") separates each key from its value. The empty-string guard comes first, because "".split("&") returns [""] rather than [], which would otherwise add a bogus "" key.
function parseQuery(qs) { const result = {}; if (qs === "") return result; for (const pair of qs.split("&")) { const [key, value] = pair.split("="); result[key] = value; } return result; } console.log(JSON.stringify(parseQuery("status=draft&page=2"))); console.log(JSON.stringify(parseQuery("q=node"))); console.log(JSON.stringify(parseQuery("")));
Output
{"status":"draft","page":"2"}
{"q":"node"}
{}qs.split("&") gives ["status=draft", "page=2"], and destructuring each pair with const [key, value] = pair.split("=") names both halves in one line before result[key] = value stores them.
Every value comes out as a string, including "2", which is why req.query.page is never a number. Converting is the handler's job, and it is the same string-versus-number trap as route params.
The real parser handles cases this one skips, and knowing which ones is the useful part. A repeated key like tag=a&tag=b should become an array, percent-encoded characters need decoding, and a bare flag with no = gives undefined here rather than an empty string.
Note that this function is why nothing about req.query should feel mysterious. A framework's convenience properties are all small parsers like this one, run before your handler and attached to the request object.
A route handler's real work
An Express handler for GET /users/:id receives req.params.id as the string "2", never the number 2.
So getUser(params) does three things in order: convert params.id with Number(...), look the user up in the array, and return either a 200 with the user or a 404 carrying the error shape from lesson 4-3. Checking for the missing user first and returning early keeps the success path at the end where it is easy to read.
const users = [{ id: 1, name: "Ada" }, { id: 2, name: "Grace" }]; function getUser(params) { const id = Number(params.id); const user = users.find((u) => u.id === id); if (!user) return { status: 404, body: { error: { code: "user_not_found" } } }; return { status: 200, body: user }; } console.log(JSON.stringify(getUser({ id: "2" }))); console.log(JSON.stringify(getUser({ id: "99" })));
Output
{"status":200,"body":{"id":2,"name":"Grace"}}
{"status":404,"body":{"error":{"code":"user_not_found"}}}Without Number(...), users.find((u) => u.id === "2") never matches, because 2 === "2" is false. That is the single most common bug in a first Express handler, and it presents as a 404 for an id that plainly exists.
users.find(...) returns the user or undefined, so the if (!user) guard covers the miss. Reaching for users[id] instead would work only while ids happen to match array positions, which stops being true after the first delete.
The early return puts the failure case first and leaves the success case unindented at the bottom. That ordering scales, since three checks stack as three guards rather than three levels of nesting.
Number("99") succeeds and finds nothing, while Number("abc") gives NaN and also finds nothing, so both land on the same 404. Strictly, a non-numeric id is a malformed request and deserves a 400, and lesson 5-3 adds that validation layer.
Note that the function returns data instead of touching res, which is the same separation as lesson 3-1. The Express handler around it becomes one line, const { status, body } = getUser(req.params); res.status(status).json(body);, and the decision logic stays testable.