Skip to content

Routing

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

Define a route handler

Based on Ktor Routing

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

All route definition provided by Ktor Routing is supported by Kotlin Routing.

val router = routing {
    handle("/hello") {
        // Handle any call to the "/hello" route
    }
}

It's also possible to define a name to navigate instead of using the path value.

handle(path = "/hello", name = "hello") {
    // ...
}

Route Method

Well, there is no Get, Post and no other HttpMethod related verb functions, of course.

So to distinguish some route from another you can create or use the default route methods.

val router = routing {
    handle("/hello", method = RouteMethod("PUSH")) {
        // Handle any call to the "/hello" route
    }
}

Specify a path pattern

The same as oficial docs Specify a path pattern

Define multiple route handlers

Define routes by paths

If you want to define multiple route handlers, which of course is the case for any application, you can just add them to the routing function:

val router = routing {
    handle("/customer/{id}") {
        // ...
    }
    handle("/customer") {
        // ...
    }
    handle("/order") {
        // ...
    }
    handle("/order/{id}") {
        // ...
    }
}

In this case, each route has its own function and responds to the specific route.

Group routes by paths

An alternative way is to group these by paths, whereby you define the path and then place the handle for that path as nested functions, using the route function:

val router = routing {
    route("/customer") {
        handle {
            // handle call to /customer
        }

        handle("/{id}") {
            // handle call to /customer/id
        }
    }
    route("/order") {
        handle {
            // handle call to /order
        }

        handle("/{id}") {
            // handle call to /order/id
        }
    }
}

Nested routes

Kotlin Routing allows you to have sub-routes as parameters to route functions. This can be useful to define resources that are logically children of other resources.

val router = routing {
    route("/order") {
        route("/shipment") {
            handle {
                // handle call to /order/shipment
            }

            handle("/{id}") {
                // handle call to /order/shipment/id
            }
        }
    }
}

Route extension functions

The same as oficial docs Route extension functions

Calling routes

Calling by URI

router.call(uri = "/hello")

Calling by name

router.call(name = "hello")

Passing path parameters

// by uri
router.call(uri = "/customer/1234")

// by name
router.call(
    name = "named",
    parameters = parametersOf("id", "1234")
)

// by uri and parameters
router.call(
    uri = "/customer",
    parameters = parametersOf("id", "1234")
)

Passing query parameters

Path and Query parameters use the same parameters map to provide and get the values

// by uri
router.call(uri = "/customer?id=1234")

// by name
router.call(
    name = "named",
    parameters = parametersOf("id", "1234")
)

// by uri and parameters
router.call(
    uri = "/customer",
    parameters = parametersOf("id", "1234")
)

Calling by method

router.call(
    // ...,
    routeMethod = RouteMethod("PUSH")
)

Passing a body

Sometimes path and query parameters aren't enough to pass values to the route.

router.callWithBody(
    // ...,
    body = AnyTypeInstance
)

Checking route is available

Checking by path

val router = routing { }

// check current routing only
router.canHandleByPath(path = "/path")

// check current and parent routing when not found on the current
router.canHandleByPath(path = "/path", lookUpOnParent = true)

// check by path and method
router.canHandleByPath(path = "/path", method = RouteMethod("PUSH"), ...)

Checking by name

val router = routing { }

// check current routing only
router.canHandleByName(name = "named")

// check current and parent routing when not found on the current
router.canHandleByName(name = "named", lookUpOnParent = true)

// check by name and method
router.canHandleByName(name = "named", method = RouteMethod("PUSH"), ...)

Handling requests

Kotlin Routing allows you to handle incoming call and perform various actions when handling it.

There is no return or respond* to do as Ktor version does.

General request information

Inside a route handler, you can get access to the call using the call extension property.

val router = routing {
    handle("/hello") {
        val application = call.application // routing application instance
        val routeMethod = call.routeMethod // the called route method
        val name = call.name               // the called route name
        val uri = call.uri                 // the called route uri
        val parameters = call.parameters   // Path or query parameters
    }
}

Body content

This section shows how to receive body contents sent with callWithBody().

val router = routing {
    handle("/hello") {
        val valueOne: AnyTypeInstance = call.receive()          // throw an exception whether there is no value to receive
        val valueTwo: AnyTypeInstance? = call.receiveNullable() // returns null when there is no value to receive
    }
}

Redirects

To redirect from one route to another call the function:

Change route method or append parameters are supported in redirect functions.

Redirect by path

val router = routing {
    handle("/hello") {
        call.redirectToPath(path = "/path-destination")
    }
}

Redirect by name

val router = routing {
    handle("/hello") {
        call.redirectToName(name = "destination-name")
    }
}

On Demand route

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

val router = routing { }

// on demand
router.handle(...) {
    // ...
}

Unregister routes

To avoid duplicated route handlers or other unexpected behaviors, sometimes we require to unregister some handlers.

Unregister by path

val router = routing { }

router.unregisterPath(path = "/path")

Unregister by name

val router = routing { }

router.unregisterNamed(name = "named")

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

val applicationRouter = routing { }

val featureARouter = routing(rootPath = "/feature-a", parent = applicationRouter) {
    handle("/hello-a") {
        // ...
    }
}

val featureBRouter = routing(rootPath = "/feature-b", parent = applicationRouter) {
    handle("/hello-b") {
        // ...
    }
}

val featureCRouter = routing(rootPath = "/feature-c", parent = featureARouter) {
    handle("/hello-c") {
        // ...
    }
}

// routing inside C only
featureCRouter.call(uri = "/feature-c/hello-c")

// routing from A to C
featureARouter.call(uri = "/feature-a/feature-c/hello-c")

// routing from C to A
featureCRouter.call(uri = "/feature-a/hello-a")

// routing from C or A to B throw RouteNotFoundException
(featureCRouter|featureARouter).call(uri = "/feature-b/hello-b")

// routing from application router can go to anyone
applicationRouter.call(uri = "/feature-a/feature-c/hello-c")