Routing
Give the cats API its routes: a parameter in the path, two parameters, a route that must be registered before the parameterised one, and a DELETE that answers 204.
What you'll learn
- Read a path parameter with c.req.param('id'), and all of them at once with c.req.param()
- Register routes for other verbs, and answer 204 with c.body(null, 204)
- Rely on registration order: Hono tries routes in the order they were added, so a fixed path goes before a parameter that would swallow it
The cats API needs more than fixed paths. A client asks for one cat by its id, deletes another, reads a single field; the path carries which one, and the app has to take that value out of the URL and decide what to do with it. In Hono a route is a verb, a path pattern and a handler, and the pattern is where the URL's variable parts are named.
Parameters in the path
A segment that starts with : is a parameter. It matches exactly one segment, whatever it holds, and the handler reads it by name:
app.get('/users/:id', (c) => {
const id = c.req.param('id');
return c.text(`user ${id}`);
});
GET /users/42 gives '42'. Several parameters are read at once with no argument:
app.get('/posts/:postId/comments/:commentId', (c) => {
const { postId, commentId } = c.req.param();
return c.json({ postId, commentId });
});
TypeScript knows the names from the path string, so c.req.param('nope') on that route is a type error, and c.req.param('id') is typed string.
Everything in a URL is a string
'42' is the characters 4 and 2. Comparing it with a cat whose id is the number 42 with === is always false, and cats.find((cat) => cat.id === id) finds nothing while looking correct. Convert first: Number(c.req.param('id')). A parameter that is not a number, /users/abc, converts to NaN, which equals nothing, so a lookup simply fails; a later lesson turns that into a proper 400. Parameters arrive URL-decoded, so /users/Ada%20Lovelace gives 'Ada Lovelace'.
Verbs, and a response with no body
Each verb has a method: app.get, app.post, app.put, app.patch, app.delete, and app.all for any verb. app.on('PURGE', '/cache', handler) covers a verb that has no method, and app.on(['PUT', 'PATCH'], '/x', handler) registers one handler for several. A DELETE that succeeded often has nothing to say; the honest answer is 204 No Content, which is c.body(null, 204). Neither c.text('', 204) nor c.json(null, 204) is allowed by the types, because a 204 must not carry a body.
Registration order is priority
Hono tries the routes of a request's verb in the order they were registered and the first handler that answers wins. This matters as soon as a fixed path and a parameter can match the same URL:
app.get('/users/:id', (c) => c.text(`user ${c.req.param('id')}`));
app.get('/users/me', (c) => c.text('you'));
GET /users/me never reaches the second route: :id matches me and answers first. Register /users/me above /users/:id. When two routes must both run, a handler can call await next() to hand over to the next match, which is what middleware does; lesson 4 is about that.
More patterns
:id?makes a parameter optional:/cats/:id?matches/catsand/cats/2.:id{[0-9]+}restricts a parameter with a regular expression;/cats/abcthen matches nothing at all.*is a wildcard:/files/*matches/files/a/b/c;app.get('*', handler)matches every path, which is how a fallback route is written.- Methods chain:
app.get('/x', h1).post('/x', h2)registers two routes and returns the app.
Your task
The app in the editor lists three cats. Add the routes the API is missing, on the same array:
GET /cats/newestanswers the last cat in the array.GET /cats/:idanswers the cat with that id, or404with{ "error": "Cat <id> not found" }.GET /cats/:id/:fieldanswers one field of the cat as plain text,/cats/2/namegivingLuna, or404with{ "error": "Cat <id> has no <field>" }when the cat has no such field.DELETE /cats/:idremoves the cat and answers204with no body, or the same 404 as step 2.
Register step 1 before step 2 and read the Endpoints tab for why.
When it fails
GET /cats/newestanswers{ "error": "Cat newest not found" }: the:idroute was registered first and took the request. Move/cats/newestabove it.GET /cats/2is a 404 although Luna exists: the parameter is the string'2'and the ids are numbers.Number()before comparing.DELETE /cats/1answers 200, or the type checker rejectsc.text('', 204): a 204 has no body; the helper isc.body(null, 204).GET /cats/2/nameanswers"Luna"with the quotes: the field was sent withc.json(). Text goes throughc.text().
Remember
:namein a path is a parameter;c.req.param('name')reads one,c.req.param()reads them all, always as strings.- One method per verb,
app.on()for the rest,c.body(null, 204)for no content. - Routes are tried in registration order and the first handler that answers wins; fixed paths go above the parameters that would swallow them.
?,{regex}and*refine a pattern.
Stuck? Show a hint
Every parameter arrives as a string: Number(c.req.param('id')) before comparing with a cat's id. Array.prototype.find and findIndex do the lookups, splice removes. Register /cats/newest above /cats/:id, or :id will match the word newest first.