Route parameters
Each blog post has its own address: /posts/1, /posts/2 and so on. You cannot write a route for each one, so the variable part of the address is declared as a parameter — it starts with a colon.
A parameter in the address
// start/routes.ts
import router from '@adonisjs/core/services/router'
router.get('/posts/:id', async ({ params }) => {
return `Post number ${params.id}`
})The handler receives the request context object, and destructuring takes its params field. At /posts/5 the page shows "Post number 5". Parameter values always arrive as strings.
Several parameters
// start/routes.ts
router.get('/posts/:postId/comments/:commentId', async ({ params }) => {
return `Comment ${params.commentId} on post ${params.postId}`
})The address /posts/3/comments/12 returns "Comment 12 on post 3".
An optional parameter
A question mark after the name makes the parameter optional:
// start/routes.ts
router.get('/archive/:year?', async ({ params }) => {
const year = params.year ?? 'all years'
return `Archive: ${year}`
})Both addresses work: /archive/2025 shows "Archive: 2025", and /archive shows "Archive: all years".
Checking the format
To make a route match only numbers, give the parameter a check with the where method:
// start/routes.ts
router
.get('/posts/:id', async ({ params }) => {
return `Post number ${params.id}`
})
.where('id', router.matchers.number())Now /posts/15 works and /posts/hello returns 404. The ready-made number() matcher also turns the value into a number, so params.id is a number, not a string. There are others: router.matchers.uuid() and router.matchers.slug().
You can set your own check with a regular expression:
// start/routes.ts
router
.get('/tags/:name', async ({ params }) => `Tag ${params.name}`)
.where('name', /^[a-z]+$/)Linking to a route with a parameter
In a template or in HTML the address is built from the parameter:
{{-- resources/views/pages/home.edge --}}
<a href="/posts/{{ post.id }}">{{ post.title }}</a>The next lesson shows a better way — links by route name.
Common mistake
AdonisJS checks routes in the order they are declared. A route with a parameter declared above will also catch a specific address:
// start/routes.ts
// Wrong: /posts/create goes to the first route with id = "create".
router.get('/posts/:id', async ({ params }) => `Post ${params.id}`)
router.get('/posts/create', async () => 'New post')Put specific addresses above routes with parameters, or restrict the parameter with the number() matcher.