Routing & Controllers

Routing & Controllers

VIEW

Controllers with Decorators

Lockness uses class-based controllers with decorators for clean, expressive routing:

typescript
import { Context, Controller, Delete, Get, Post, Put } from '@lockness/core'

@Controller('/api/users')
export class UserController {
    @Get('/')
    async index(c: Context) {
        return c.json({ users: [] })
    }

    @Get('/:id')
    async show(c: Context) {
        const id = c.req.param('id')
        return c.json({ id })
    }

    @Post('/')
    async store(c: Context) {
        const body = await c.req.json()
        return c.json(body, 201)
    }

    @Put('/:id')
    async update(c: Context) {
        return c.json({ updated: true })
    }

    @Delete('/:id')
    async destroy(c: Context) {
        return c.json({ deleted: true })
    }
}

Available HTTP Methods

  • @Get
  • @Post
  • @Put
  • @Delete
  • @Patch
  • @Options
  • @Head

Middleware Composition

The @ComposeMiddleware decorator allows you to apply multiple middlewares to a route method in a single, clean declaration. It supports Hono functions, Lockness classes, and named middlewares.

typescript
import { ComposeMiddleware, cors, logger } from '@lockness/core'

@Controller('/api')
export class ApiController {
    @Get('/users')
    @ComposeMiddleware(logger(), AuthMiddleware, 'admin')
    users(c: Context) {
        return c.json({ users: [] })
    }
}

This is the recommended way to apply multiple middlewares to a single route, as it's more concise than using multiple @UseMiddleware decorators.

Dependency Injection

Controllers support automatic dependency injection:

typescript
@Controller('/api/posts')
export class PostController {
    constructor(
        private postService: PostService,
        private postRepository: PostRepository,
    ) {}

    @Get('/')
    async index(c: Context) {
        const posts = await this.postRepository.findAll()
        return c.json({ posts })
    }
}

Quick Scaffolding

Generate controllers quickly with the CLI:

bash
# API controller (JSON responses)
deno task cli make:controller User

# Web controller (renders JSX views)
deno task cli make:controller User --view

Adding Actions to Controllers

Add new actions (methods) to existing controllers:

bash
# Basic action
deno task cli make:action User show

# With specific HTTP method
deno task cli make:action User store --method=post
deno task cli make:action User update --method=put
deno task cli make:action User destroy --method=delete

# With view rendering
deno task cli make:action User create --view

The command automatically:

  • Adds the method to your controller class
  • Follows RESTful conventions for common action names
  • Imports required decorators (@Post, @Put, etc.)
  • Generates appropriate route paths and names
  • Creates views when using --view flag

RESTful Conventions:

ActionMethodPathRoute Name
indexGET/resource.index
showGET/:idresource.show
createGET/createresource.create
storePOST/resource.store
editGET/:id/editresource.edit
updatePUT/:idresource.update
destroyDELETE/:idresource.destroy

Named Routes

Named routes allow you to generate URLs for specific routes using a unique name. This prevents hardcoding paths throughout your application and makes it easier to change URLs later.

Assigning Names

You can assign a name to a route using the options object in the route decorator:

typescript
@Controller('/users')
export class UserController {
    @Get('/:id', { name: 'users.show' })
    async show(c: Context) { ... }
}

File Extension Routes

The extension option allows you to create routes that match file extensions while automatically stripping the extension from parameters. This is perfect for serving static-like content (RSS feeds, sitemaps, documentation files, etc.).

typescript
@Controller('/llms')
export class LlmController {
    @Get('/:name', { extension: '.txt' })
    async serve(c: Context) {
        // URL: /llms/installation.txt
        const name = c.req.param('name') // 'installation' (not 'installation.txt')
        return c.text(await loadDoc(name))
    }
}

How it works:

  1. The decorator transforms /:name to a Hono regex pattern /:name{.+\.txt}
  2. The framework automatically strips the extension from all route parameters
  3. Your handler receives clean parameter values

Supported extensions (with autocompletion):

  • Text: .txt, .md, .html, .xml, .csv
  • Data: .json, .yaml, .yml, .toml
  • Code: .js, .ts, .jsx, .tsx, .css
  • Images: .png, .jpg, .jpeg, .gif, .svg, .webp
  • Feeds: .rss, .atom
  • Custom: Any .{extension} format
typescript
// RSS feed
@Get('/:category', { extension: '.rss' })
async feed(c: Context) {
    const category = c.req.param('category')
    return c.body(generateRSS(category), { headers: { 'Content-Type': 'application/rss+xml' }})
}

// JSON API with explicit extension
@Get('/:resource', { extension: '.json' })
async api(c: Context) {
    const resource = c.req.param('resource')
    return c.json(await loadResource(resource))
}

Generating URLs

Use the route() helper to generate a URL from a route name:

typescript
import { route } from '@lockness/core'

// Simple route
const url = route('users.index') // "/users"

// With parameters
const url = route('users.show', { id: 123 }) // "/users/123"

In a controller redirect:

typescript
return c.redirect(route('auth.login'))

Display All Routes

Use the router:list command to see all registered routes in your application:

bash
deno task cli router:list

This displays a formatted table with:

  • METHOD: HTTP method (color-coded by type)
  • PATH: Route path with parameters
  • CONTROLLER: Controller class name
  • ACTION: Method name
  • MIDDLEWARES: Applied middlewares (decorators and named)

Example output:

bash
📋 Registered Routes (11 total)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┃ METHOD ┃ PATH           ┃ NAME        ┃ CONTROLLER      ┃ ACTION  ┃ MIDDLEWARES
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┃ GET    ┃ /              ┃ home        ┃ AppController   ┃ index   ┃ -
┃ POST   ┃ /api/users     ┃ users.store ┃ UserController  ┃ create  ┃ @Auth, @Validate
┃ GET    ┃ /api/users/:id ┃ users.show  ┃ UserController  ┃ show    ┃ auth
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━