Typed Routes
Part 5’s whole routing story gets types for free. Because this
project has a tsconfig.json, marko-run generates
.marko-run/routes.d.ts on every build — dev included — describing
your actual routes. That file (already in the tsconfig’s include)
is what makes the ambient Run namespace smart:
- In a route file,
Run.Contextknows the exact params of the routes that file serves —context.params.idhere is astringbecause the folder is$id, andcontext.params.slugsimply doesn’t exist. Run.GET,Run.POST, and friends infer everything through: validatedsearch/bodytypes and the data passed tonext(...)flow into downstream handlers, layouts, and pages.Run.href("/products/:id", ...)type-checks URLs against the routes the app actually serves.
The handler on the right doesn’t believe any of that yet — it assigns
context.params.id to a number. Run pnpm check: TS2322, straight
from the generated route types.
- Fix the handler: params are strings, so parse it —
const idNum = Number(context.params.id); - Keep the guard: non-numeric ids already
return new Response(...)with a 404. pnpm check— silent. Click a product link in the preview: the page reads the same param via$global.params.id.
Curious what’s inside .marko-run/routes.d.ts? Open it. Two layers,
deliberately: a declare module "@marko/run" block registers your
app’s whole route table with the framework’s types (that’s what
powers Run.href), and then one declare module per routable file
grants that file its ambient Run namespace, narrowed to its
routes — which is why context.params differs per handler. You’ll
also spot a MarkoRun namespace marked deprecated: that’s the same
thing under its older name, kept so existing code keeps checking.
You should see the product pages serving with their ids, and a quiet
check. One lesson left in this chapter: the corners of the Marko
namespace you haven’t met yet.
- Installing dependencies
- Starting dev server