Skip to main content
The AdonisJS Starter Kit uses a modular routing system where routes are organized by feature modules for better maintainability.

Route Organization

Routes are defined in feature-specific files:
apps/web/app/
├── auth/
│   └── routes.ts         # Authentication routes
├── users/
│   └── routes.ts         # User management routes
├── marketing/
│   └── routes.ts         # Marketing pages
└── analytics/
    └── routes.ts         # Analytics dashboard
Routes are automatically loaded via the preloads array in adonisrc.ts. No need to manually import them.

Route Loading

Routes are registered in adonisrc.ts:
adonisrc.ts
export default defineConfig({
  preloads: [
    () => import('#start/kernel'),
    
    // Marketing
    () => import('#marketing/routes'),
    
    // Auth
    () => import('#auth/start/view'),
    () => import('#auth/start/events'),
    () => import('#auth/routes'),
    
    // Users
    () => import('#users/start/view'),
    () => import('#users/start/events'),
    () => import('#users/routes'),
    
    // Analytics
    () => import('#analytics/routes'),
  ],
})

Basic Routes

Simple Route

app/marketing/routes.ts
import router from '@adonisjs/core/services/router'

const MarketingController = () => import('#marketing/controllers/marketing_controller')

router.get('/', [MarketingController]).as('marketing.show')

Route with Parameters

router.get('/posts/:id', [PostsController, 'show']).as('posts.show')
router.get('/posts/:slug', [PostsController, 'showBySlug']).as('posts.show.slug')

Authentication Routes

Here are the actual authentication routes from the starter kit:
app/auth/routes.ts
import router from '@adonisjs/core/services/router'
import { middleware } from '#start/kernel'

const SignInController = () => import('#auth/controllers/sign_in_controller')
const SignOutController = () => import('#auth/controllers/sign_out_controller')
const SignUpController = () => import('#auth/controllers/sign_up_controller')
const ForgotPasswordController = () => import('#auth/controllers/forgot_password_controller')
const ResetPasswordController = () => import('#auth/controllers/reset_password_controller')
const SocialController = () => import('#auth/controllers/social_controller')

// Sign in
router.get('/login', [SignInController, 'show'])
  .use(middleware.guest())
  .as('auth.sign_in.show')
router.post('/login', [SignInController])

// Sign out
router.get('/logout', [SignOutController]).as('auth.sign_out.show')

// Sign up
router.get('/sign-up', [SignUpController, 'show'])
  .use(middleware.guest())
  .as('auth.sign_up.show')
router.post('/sign-up', [SignUpController])
  .use(middleware.guest())
  .as('auth.sign_up.handle')

// Password reset
router.get('/forgot-password', [ForgotPasswordController, 'show'])
  .as('auth.forgot_password.show')
  .use(middleware.guest())
router.post('/forgot-password', [ForgotPasswordController])
  .as('auth.forgot_password.handle')

router.get('/reset-password/:token', [ResetPasswordController, 'show'])
  .use(middleware.guest())
  .as('auth.reset_password.show')
router.post('/reset-password/:token', [ResetPasswordController])
  .use(middleware.guest())
  .as('auth.reset_password.handle')

// Social authentication
router.get('/:provider/redirect', [SocialController, 'redirect'])
  .where('provider', /google/)
  .as('social.create')
router.get('/:provider/callback', [SocialController, 'callback'])
  .where('provider', /google/)

// Locale switching
router.get('/switch/:locale', () => {})
  .use(middleware.switchLocale())
Use the guest() middleware to prevent authenticated users from accessing routes like login and signup.

Resource Routes

Resource routes provide CRUD operations:
app/users/routes.ts
import router from '@adonisjs/core/services/router'
import { middleware } from '#start/kernel'

const UsersController = () => import('#users/controllers/users_controller')

// Create all CRUD routes (index, create, store, show, edit, update, destroy)
router.resource('/users', UsersController)
  .use('*', middleware.auth())
  .as('users')

// Or limit to specific actions
router.resource('/users', UsersController)
  .only(['index', 'store', 'update', 'destroy'])
  .use('*', middleware.auth())
  .as('users')
Resource routes generate:
VerbURIActionRoute Name
GET/usersindexusers.index
GET/users/createcreateusers.create
POST/usersstoreusers.store
GET/users/:idshowusers.show
GET/users/:id/editeditusers.edit
PUT/PATCH/users/:idupdateusers.update
DELETE/users/:iddestroyusers.destroy

Nested Routes

Here’s the user settings route structure from the starter kit:
app/users/routes.ts
import router from '@adonisjs/core/services/router'
import { middleware } from '#start/kernel'

const ProfileController = () => import('#users/controllers/profile_controller')
const PasswordController = () => import('#users/controllers/password_controller')
const TokensController = () => import('#users/controllers/tokens_controller')

// Settings redirect
router.get('/settings', ({ response }) => {
  return response.redirect().toRoute('profile.show')
})
  .middleware(middleware.auth())
  .as('settings.index')

// Profile settings
router.get('/settings/profile', [ProfileController, 'show'])
  .middleware(middleware.auth())
  .as('profile.show')
router.put('/settings/profile', [ProfileController])
  .middleware(middleware.auth())

// Password settings
router.get('/settings/password', [PasswordController, 'show'])
  .middleware(middleware.auth())
  .as('password.show')
router.put('/settings/password', [PasswordController])
  .middleware(middleware.auth())

// API tokens
router.resource('/settings/tokens', TokensController)
  .only(['index', 'destroy'])
  .middleware('*', middleware.auth())
  .as('tokens')
router.post('/api/tokens', [TokensController, 'store'])
  .middleware(middleware.auth())

// Appearance settings
router.get('/settings/appearance', ({ inertia }) => {
  return inertia.render('users/appearance')
})
  .middleware(middleware.auth())
  .as('appearance.show')

Middleware

Applying Middleware

router.get('/dashboard', [DashboardController])
  .middleware(middleware.auth())

Available Middleware

The starter kit includes:
  • auth() - Require authentication
  • guest() - Require no authentication
  • switchLocale() - Switch user locale
  • detectUserLocale() - Auto-detect locale
  • initializeBouncer() - Load authorization
  • containerBindings() - Initialize container

Named Routes

Always name your routes for easy reference:
router.get('/users/:id', [UsersController, 'show'])
  .as('users.show')

router.post('/users/:id/follow', [FollowController])
  .as('users.follow')

Using Named Routes

import router from '@adonisjs/core/services/router'

export default class PostsController {
  async store({ response }: HttpContext) {
    // ...
    return response.redirect().toRoute('posts.show', { id: post.id })
  }
}

Route Parameters

Required Parameters

router.get('/users/:id', [UsersController, 'show'])
router.get('/posts/:slug', [PostsController, 'showBySlug'])

Optional Parameters

router.get('/search/:query?', [SearchController])

Parameter Constraints

router.get('/users/:id', [UsersController])
  .where('id', /^[0-9]+$/)

router.get('/:provider/callback', [SocialController])
  .where('provider', /google|github|facebook/)

Route Groups

Group related routes:
router.group(() => {
  router.get('/profile', [ProfileController, 'show'])
  router.put('/profile', [ProfileController, 'update'])
  router.get('/password', [PasswordController, 'show'])
  router.put('/password', [PasswordController, 'update'])
})
  .prefix('/settings')
  .middleware(middleware.auth())
  .as('settings')

API Routes

Define API routes separately:
router.group(() => {
  router.post('/tokens', [TokensController, 'store'])
  router.delete('/tokens/:id', [TokensController, 'destroy'])
  
  router.get('/users', [ApiUsersController, 'index'])
  router.get('/users/:id', [ApiUsersController, 'show'])
})
  .prefix('/api')
  .middleware([middleware.auth()])
The starter kit uses Inertia.js for most routes. API routes are primarily used for token management and AJAX requests.

Redirects

// Simple redirect
router.get('/settings', ({ response }) => {
  return response.redirect().toRoute('profile.show')
})

// Conditional redirect
router.get('/dashboard', ({ auth, response }) => {
  if (!auth.user) {
    return response.redirect().toRoute('auth.sign_in.show')
  }
  // ...
})

Route Testing

Test your routes:
import { test } from '@japa/runner'

test.group('Auth routes', () => {
  test('GET /login displays login page', async ({ client }) => {
    const response = await client.get('/login')
    response.assertStatus(200)
  })
  
  test('POST /login authenticates user', async ({ client }) => {
    const response = await client.post('/login').json({
      email: '[email protected]',
      password: '123',
    })
    response.assertRedirectsTo('/')
  })
  
  test('authenticated users cannot access /login', async ({ client }) => {
    const response = await client
      .get('/login')
      .loginAs(user)
    
    response.assertRedirectsTo('/')
  })
})

Listing Routes

View all registered routes:
node ace list:routes
Output:
┌──────────┬───────────────────────────┬─────────────────────┬──────────────┐
│ Method   │ Route                     │ Handler             │ Name         │
├──────────┼───────────────────────────┼─────────────────────┼──────────────┤
│ GET      │ /                         │ MarketingController │ marketing... │
│ GET      │ /login                    │ SignInController    │ auth.sign... │
│ POST     │ /login                    │ SignInController    │              │
│ GET      │ /users                    │ UsersController     │ users.index  │
└──────────┴───────────────────────────┴─────────────────────┴──────────────┘
Filter routes by pattern: node ace list:routes --pattern=/users

Best Practices

Named routes make refactoring easier and prevent broken links when URLs change.
Resource routes follow RESTful conventions and reduce boilerplate.
Keep routes in their respective feature modules, not in a single file.
Apply authentication and authorization middleware to protect routes.
Each route file should handle a specific feature or domain area.

Next Steps

Models

Learn about Lucid ORM models

Frontend

Build UIs with React and Inertia.js

Build docs developers (and LLMs) love