Skip to content

Type‐safe routing

Thiago Santos edited this page Aug 13, 2024 · 4 revisions

Kotlin Routing provides the Resources Plugin that allows you to implement type-safe routing. To accomplish this, you need to create a class that should act as a typed route and then annotate this class using the @Resource keyword. Note that the @Resource annotation has @Serializable behavior provided by the kotlinx.serialization library.

sourceSets {
    commonMain.dependencies {
        implementation("dev.programadorthi.routing:resources:$version")
    }
}

Install Resources

To install the Resources plugin to the application, pass it to the install function. The code snippets below show how to install Resources...

val router = routing {
    install(Resources)
    // ...
}

Create resource classes

The same as oficial docs Create resource classes

Define route handlers

To define a route handler for a typed resource, you need to pass a resource class to a handle function. For example, a route handler below responds on the /articles path.

@Resource("/articles")
class Articles()

val router = routing {
    install(Resources)
    
    handle<Articles> { articles ->
        // handle any call to Articles
    }
}

The example below shows how to define route handlers for the Articles resource created in Example: A resource for CRUD operations. Note that inside the route handler you can access the Article as a parameter and obtain its property values.

val router = routing {
    install(Resources)
    
    handle<Articles> { article ->
        // Get all articles ...
    }
    handle<Articles.New> {
        // Show a page with fields for creating a new article ...
    }
    handle<Articles>(method = RouteMethod("SAVE")) {
        // Save an article ...
    }
    handle<Articles.Id> { article ->
        // Show an article with id ${article.id} ...
    }
    handle<Articles.Id.Edit> { article ->
        // Show a page with fields for editing an article ...
    }
    handle<Articles.Id>(method = RouteMethod("UPDATE")) { article ->
        // Update an article ...
    }
    handle<Articles.Id>(method = RouteMethod("DELETE")) { article ->
        // Delete an article ...
    }
}

Build links from resources

The same as oficial docs Build links from resources

Calling resources

Calling by resource

router.call(Articles())

Calling by resource with body

router.callWithBody(Articles(), bodyTypeInstance)

Checking handle by resource

val router = routing { }

router.canHandleByResource<Articles.Id.Edit>()
// or
router.canHandleByResource<Articles.Id.Edit>(method = ...)

Redirects

val router = routing {
    handle("/hello") {
        call.redirectTo(resource = Articles.Id(id = 456))
        // or
        call.redirectTo(resource = Articles.Id(id = 456), method = ...)
    }
}

On Demand resources

Routes can be registered on demand avoiding have to declare all of them on Routing instantiation:

val router = routing { }

// on demand
router.handle<Articles.Id.Edit>(...) {
    // ...
}

Unregister resources

val router = routing { }

router.unregisterResource<Articles>()

Nested Routing

Maybe you have distinct features that have it own lifecycle and know the parent Routing only. This create a hierarchy like a B-Tree that root and leaf knows each other but siblings don't.

Connecting parent and child

@Resource("/parent")
class ParentRouting {

    @Resource("/child")
    class ChildRouting(val parent: ParentRouting = ParentRouting()) {

        @Resource("/destination")
        class Destination(val parent: ChildRouting = ChildRouting())
    }
}

val applicationRouter = routing(rootPath = "/parent") { }

val featureRouter = routing(rootPath = "/child", parent = applicationRouter) {
    handle<ParentRouting.ChildRouting.Destination> {
        // ...
    }
}

// routing inside feature
featureRouter.call(resource = ParentRouting.ChildRouting.Destination())

// routing from application to feature
applicationRouter.call(resource = ParentRouting.ChildRouting.Destination())