Ktor 3.4.0 Help

Routing organization

One of Ktor's strong points is its flexibility and that it does not enforce a single routing organization strategy. Instead, you can organize routes in a way that best fits the size and complexity of your application, and many projects combine the patterns described below.

This page shows common patterns for organizing routing code as your project grows.

Group by file

One way to organize routing is to place related routes in separate files. This keeps route definitions small and readable.

For example, if your application is managing customers and orders, you could split the routing logic between CustomerRoutes.kt and OrderRoutes.kt files:

fun Route.customerByIdRoute() { get("/customer/{id}") { } } fun Route.createCustomerRoute() { post("/customer") { } }
fun Route.getOrderRoute() { get("/order/{id}") { } } fun Route.totalizeOrderRoute() { get("/order/{id}/total") { } }

Each file groups route handlers that belong to the same domain area. Then, you can register each group in your routing block:

routing { customerRoutes() orderRoutes() }

This approach works well for small or medium-sized projects where each domain area only contains a few routes.

Group by package (folders)

As a file grows, it can become harder to navigate. To keep routing logic small and focused, you can distribute routing logic into multiple files inside a dedicated package:

customer/ ListCustomers.kt CreateCustomer.kt GetCustomer.kt UpdateCustomer.kt CustomerRoutes.kt

Each file contains a small part of the routing logic, while the folder represents the domain.

fun Route.createCustomerRoute(service: CustomerService) { post("/customer") { val body = call.receive<CustomerDto>() call.respond(service.create(body)) } }
fun Route.customerRoutes(service: CustomerService) { listCustomersRoute(service) getCustomerRoute(service) createCustomerRoute(service) updateCustomerRoute(service) }

This structure keeps each route definition small and easy to navigate as the number of endpoints increases. Therefore, it is ideal for large applications, such as APIs with many endpoints.

Group routes by path and nest resources

You can organize routes by grouping all handlers for the same path and nesting related resources. Nested routing is useful when an endpoint includes multiple operations on the same resource:

routing { route("/customer") { get { /* list customers */ } post { /* create customer */ } route("/{id}") { get { /* get customer */ } put { /* update customer */ } delete { /* delete customer */ } } } }

Grouping by path keeps related endpoints visually close and makes the routing structure easier to understand from an HTTP API standpoint.

Group by feature or domain

As your application grows, grouping by domain or feature becomes more scalable.

routes/ customer/ CustomerRoutes.kt Create.kt Details.kt order/ OrderRoutes.kt Shipment.kt

Each feature has its own package containing only the routing code relevant to that domain.

For example, the CustomerRoutes file from the above example structure may contain the following route definitions:

fun Route.customerRoutes( service: CustomerService ) { route("/customer") { get("/{id}") { call.respond(service.get(call.parameters["id"]!!)) } post { call.respond(service.create(call.receive<CustomerDto>())) } } }

This pattern keeps feature boundaries clear and prevents routing files from growing too large, especially when each domain area contains many endpoints.

23 January 2026