First steps
An Express app is a list of routes and a call to listen(). Answer the cats as JSON, one cat by its id, a 404 for a missing one, and a name as plain text rather than the HTML Express assumes for a string.
What you'll learn
- Create an app with express(), add routes with app.get(), and start it with app.listen()
- Read path parameters from req.params and the query string from req.query
- Answer with res.json() and res.send(), set the status with res.status() and the content type with res.type()
Express is the web framework most Node servers run on, and has been for over a decade. It is also what NestJS runs on by default: a Nest app's @Req() and @Res() are Express's request and response, and Nest middleware is Express middleware. Learning Express directly shows what those layers are built from, and it is still how a great deal of production code is written. This course uses Express 5, the current major version, running in your tab exactly as it would under Node.
An app is routes and a listen()
import express from 'express';
const app = express();
app.get('/time', (req, res) => {
res.json({ now: new Date().toISOString() });
});
app.listen(3000, () => {
console.log('Listening on http://localhost:3000');
});
express() creates an application. app.get(path, handler) adds a route: when a GET request's path matches, Express calls the handler with the request, req, and the response, res. There is one method per HTTP verb: app.post, app.put, app.patch, app.delete, and app.all for every verb. app.listen(port) starts the server; until it is called, the app answers nothing, and the runtime here says so.
The handler answers through res
In Express, what a handler returns is ignored. It answers by calling a method on res:
| Call | Sends |
|---|---|
res.send('text') | a string, as text/html; a Buffer as binary; an object as JSON |
res.json(value) | any value as JSON, application/json |
res.status(404) | nothing yet: sets the status and returns res, so it chains |
res.type('text/plain') | nothing yet: sets the content type, and chains too |
res.end() | nothing more: ends the response as it stands |
Two consequences shape every Express handler. A handler that never calls one of the sending methods leaves the request hanging until the client gives up; nothing answers for it. And a handler that sends twice fails with Cannot set headers after they are sent to the client, the most common Express error there is. So a handler that answers early, an error for instance, returns right after:
if (!owner) {
res.status(404).json({ error: 'No such owner' });
return;
}
res.json(owner);
Note the default for strings: res.send('Tom') is sent as text/html, because Express was built for web pages. A plain-text answer says so with res.type('text/plain') first.
What the request carries
| Part | In Express |
|---|---|
a path parameter, /owners/:id | req.params.id |
a query value, ?page=2 | req.query.page |
| a header | req.get('accept') or req.headers.accept |
| the method, the path | req.method, req.path |
Everything from the URL is a string: req.params.id for /owners/7 is "7", so a lookup by number converts it first. A query value is undefined when absent, and an array when the key is repeated, ?tag=a&tag=b; checking typeof value === 'string' before using it covers both.
A route path's :name segments match anything up to the next slash, so /cats/:id matches /cats/7 but not /cats/7/name, which needs a route of its own. Express 5 changed the path syntax from version 4 in one place worth knowing: a wildcard is named, /files/*path, not a bare *.
Your task
The app has one example route and calls listen(). Add, on the same cats array:
GET /catsanswers every cat as JSON, or, with?minAge=<n>, only the cats at least that old.GET /cats/:idanswers that cat as JSON, or404with{ "error": "Cat <id> not found" }.GET /cats/:id/nameanswers the cat's name as plain text, withcontent-type: text/plain, or the same404.
When it fails
Request timed out after 5s; the runtime was restarted: a branch of the handler never callsres.send()orres.json(), so nothing ever answered.Cannot set headers after they are sent to the clientin the console: the 404 branch sent its answer and the handler went on to send another.returnafter it.GET /cats/2is a 404:req.params.idis the string"2"and was compared with===to a number.- "A name as text" fails on the content type:
res.send()alone saystext/html.
Remember
express()makes an app,app.get()and friends add routes,app.listen()starts it.- A handler answers through
res; its return value is ignored. - Answer exactly once:
returnafter an early answer. - Params and query values are strings; a string sent with
res.send()is HTML unlessres.type()says otherwise.
Stuck? Show a hint
req.params.id and req.query.minAge are strings (or undefined for a query value that was not sent). res.status(404).json({ … }) sends the error; return after it so the handler does not answer twice. res.type('text/plain').send(name) sends plain text.