diff --git a/c.list b/c.list
index 3f575584e..be60bd0bb 100644
--- a/c.list
+++ b/c.list
@@ -62,4 +62,5 @@
+
\ No newline at end of file
diff --git a/ktor.tree b/ktor.tree
index 5d7f71849..e60fd7313 100644
--- a/ktor.tree
+++ b/ktor.tree
@@ -33,6 +33,7 @@
+
@@ -73,7 +74,7 @@
accepts-web-file-names="routing.html,tracing-routes.html,resolution-algorithms.html,routing-in-ktor.html"/>
-
diff --git a/topics/server-application-structure.md b/topics/server-application-structure.md
index 75d937bd3..d3a1ffaa1 100644
--- a/topics/server-application-structure.md
+++ b/topics/server-application-structure.md
@@ -1,171 +1,348 @@
[//]: # (title: Application structure)
-Learn how to structure your application to keep it maintainable as the application grows.
+
+Learn how to organize your Ktor application for maintainability, modularity, and dependency injection.
+
-One of Ktor's strong points is in the flexibility it offers in terms of structuring our application. Different to many other server-side frameworks, it doesn't force us into a specific pattern such as having to place all cohesive routes in a single class name `CustomerController` for instance. While it is certainly possible, it's not required.
+
-In this section, we're going to examine the different options we have to structure our applications.
+Ktor applications can be organized in several ways depending on project size, domain complexity, and deployment
+environment. While Ktor is intentionally unopinionated, there are common patterns and best practices that help keep your
+application modular, testable, and easy to extend.
-## Group by file {id="group_by_file"}
+This topic describes common structures used in Ktor projects and provides practical recommendations for choosing and
+applying one.
-One approach is to group routes that are related in a single file. If our application is dealing with Customers and Orders for instance, this would mean having a `CustomerRoutes.kt` and an `OrderRoutes.kt` file:
+> This page focuses on application-level structure. For more information on structuring routes, see
+> [](server-routing-organization.md).
+>
-
-
+## Default project structure
-```kotlin
-fun Route.customerByIdRoute() {
- get("/customer/{id}") {
-
- }
-}
-
-fun Route.createCustomerRoute() {
- post("/customer") {
+When you generate a Ktor project using [the Ktor project generator](https://start.ktor.io/), the resulting project uses
+a single-module structure. This layout is minimal and intended to get you up and running quickly with a working Ktor
+application.
- }
-}
```
-
-
+project/
+└─ src/
+ ├─ main/
+ │ ├─ kotlin/
+ │ │ └─ Application.kt // Application entry point
+ │ └─ resources/
+ │ └─ application.conf // Application configuration
+ └─ test/
+ └─ kotlin/ // Unit and integration tests
+├─ build.gradle.kts // Gradle build file
+└─ settings.gradle.kts // Gradle settings file
+```
-```kotlin
-fun Route.getOrderRoute() {
- get("/order/{id}") {
+Although suitable for small applications, this structure does not scale well as the project grows. For larger projects,
+it is recommended to organize functionality into logical packages and modules, as described in the following sections.
- }
-}
-
-fun Route.totalizeOrderRoute() {
- get("/order/{id}/total") {
+## Choosing an application structure {id="choosing_structure"}
- }
-}
-```
-
-
+Selecting the right structure depends on the characteristics of your service:
+- Small services often work well with only a few [modules](#modular_architecture) and simple dependency injection.
+- Medium-sized applications typically benefit from a consistent [feature-based structure](#feature_modules) that groups related routes,
+services, and data models together.
+- Large or domain-heavy systems can adopt [a domain-driven approach](#ddd), which provides clearer boundaries and
+organizes business logic around domain concepts.
+- [Microservice architectures](#microservice-oriented-structure) normally use a hybrid style, where each service
+represents a domain slice and is internally modular.
-What would happen with sub-routes? Such as order/shipment for instance? It somewhat depends on what we understand by this URL.
-If we're talking about these as resources (which they are), shipment itself could therefore be a resource, and could easily map
-to another file `ShipmentRoutes.kt`.
+It’s worth noting that these structures are not mutually exclusive. You can combine multiple approaches — for example,
+using a feature-based organization within a domain-driven architecture, or applying modularity in a microservice-oriented
+system.
-## Group routing definitions {id="group_routing_definitions"}
+## Layered structure {id="layered_structure"}
-One advantage of this approach is that we can also group the routing definitions, and potentially functionality, per file.
-For instance, let's assume that we follow the group per file layout as above. Even though routes are in a different file, we need to declare them at the Application level.
-As such, our app would look something like the following:
+A layered architecture separates your application into distinct responsibilities: configuration, plugins, routes,
+business logic, persistence, domain models, and data transfer objects (DTOs). This approach is common in enterprise
+applications and provides a clear starting point for maintainable code.
-```kotlin
-routing {
- customerRouting()
- listOrdersRoute()
- getOrderRoute()
- totalizeOrderRoute()
-}
+```
+src/main/kotlin/com/example/app/
+├─ config/ // Application configuration and environment setup
+├─ plugins/ // Ktor plugins (authentication, serialization, monitoring)
+├─ controller/ // Routes or API endpoints
+├─ service/ // Business logic
+├─ repository/ // Data access or persistence
+├─ domain/ // Domain models and aggregates
+└─ dto/ // Data transfer objects
```
-If we have tons of routes in our app, this could quickly become long and cumbersome.
-Since we have however routes grouped by file, we can take advantage of this and define the routing in each file also.
-For this we could create an extension for [Application](https://api.ktor.io/ktor-server-core/io.ktor.server.application/-application/index.html) and define the routes:
+## Modular architecture {id="modular_architecture"}
-
-
+Ktor encourages modular design by allowing you to define multiple application modules. A module is a function extending
+`Application` that configures part of the application:
```kotlin
-fun Application.customerRoutes() {
- routing {
- listCustomersRoute()
- customerByIdRoute()
- createCustomerRoute()
- deleteCustomerRoute()
- }
+fun Application.customerModule() {
+ //…
}
```
-
-
+
+Each module can install plugins, configure routes, register services, or integrate infrastructure components. Modules
+can depend on each other or remain fully independent, which makes this structure flexible for both monoliths and
+microservices.
+
+Dependencies are typically injected at module boundaries:
```kotlin
-fun Application.orderRoutes() {
+fun Application.customerModule(customerService: CustomerService) {
routing {
- listOrdersRoute()
- getOrderRoute()
- totalizeOrderRoute()
+ customerRoutes(customerService)
}
}
```
-
-
+A modular structure helps you:
+
+- Separate concerns and isolate feature logic
+- Enable configuration or plugin installation only where needed
+- Improve testability by instantiating modules in isolation
+- Support microservice-friendly or plugin-friendly code organization
+- Introduce dependency injection at module boundaries
+A typical multi-module structure might look like this:
-Now in our actual `Application.module` startup, we'd simply call these functions, without the need for the `routing` block:
+```
+db/
+├─ core/ // Database abstractions (interfaces, factories)
+├─ postgres/ // Postgres implementation (JDBC, exposed)
+└─ mongo/ // MongoDB implementation
+
+server/
+├─ core/ // Shared server utilities and common modules
+├─ admin/ // Admin-facing domain and routes
+└─ banking/ // Banking domain and routes
+```
+
+Below is an example build.gradle.kts file for the `server/banking` module:
```kotlin
-fun Application.module() {
- // Init....
- customerRoutes()
- orderRoutes()
+plugins {
+ id("io.ktor.plugin") version "3.3.3"
+}
+
+dependencies {
+ implementation(project(":server:core"))
+ implementation(project(":db:core"))
+
+ // Storage implementations are loaded at runtime
+ runtimeOnly(project(":db:postgres"))
+ runtimeOnly(project(":db:mongo"))
}
```
-We can even take this one step further - install plugins per application, as needed, especially for instance when we're using
-the Authentication plugin which depends on specific routes. One important note however is that Ktor will detect if a
-plugin has been installed twice by throwing an `DuplicateApplicationPluginException` exception.
+In this structure, the banking module does not compile against any database implementation. It only depends on
+`db/core`, keeping the domain separate from infrastructure details.
+
+
+> For a full example of a modular, layered Ktor server application, see the [Ktor Chat](https://github.com/ktorio/ktor-chat)
+> sample project. It demonstrates a modular architecture with separate domain, application, and infrastructure layers,
+> as well as dependency injection and routing organization.
-### A note on using objects
+## Feature-based modules {id="feature_modules"}
-Using objects to group routing functions doesn't provide any kind of performance or memory benefits, as top-level functions in Ktor are
-instantiated a single time. While it can provide some sort of cohesive structure where we may want to share common functionality, it isn't
-necessary to use objects in case we're worried about any kind of overhead.
+Feature-based organization groups code by feature or vertical slice. Each feature becomes a
+self-contained module, containing its routes, services, data transfer objects (DTOs) and domain logic.
-## Group by folders {id="group_by_folder"}
+```
+app/
+├─ customer/
+│ ├─ CustomerRoutes.kt // Routing for customer endpoints
+│ ├─ CustomerService.kt // Business logic for customer feature
+│ └─ CustomerDto.kt // Data transfer objects for customer feature
+└─ order/
+ ├─ OrderRoutes.kt // Routing for order endpoints
+ ├─ OrderService.kt // Business logic for order feature
+ └─ OrderDto.kt // Data transfer objects for order feature
+```
-Having everything in a single file can become a bit cumbersome as the file grows.
-What we could do instead is use folders (i.e. packages) to define different areas and then have each route in its own file.
+This structure scales well in medium-to-large monoliths or when splitting individual features into microservices later.
+Each feature can be migrated or versioned independently. A typical feature module may look like this:
+
+```kotlin
+fun Application.customerModule(service: CustomerService) {
+ routing {
+ route("/customer") {
+ get("/{id}") { call.respond(service.get(call.parameters["id"]!!)) }
+ post {
+ val dto = call.receive()
+ call.respond(service.create(dto))
+ }
+ }
+ }
+}
+```
-{width="350" border-effect="rounded"}
+In the above example, the module has no knowledge of how `CustomerService` is created — it only receives it, which keeps
+dependencies explicit.
-While this does provide the advantage of a nice layout when it comes to routes and the individual actions, it could certainly
-lead to “package overload”, and potentially having tons of filenames named the same, making navigation somewhat more difficult.
- On the other hand, as we'll see in the next example, we could also merely prefix each file with area (i.e. `CustomerCreate.kt` for instance).
-## Group by features {id="group_by_feature"}
+## Domain-driven design (DDD) approach {id="ddd"}
-Frameworks such as ASP.NET MVC or Ruby on Rails, have the concept of structuring applications using three folders - Model, View, and Controllers (Routes).
+A domain-driven structure organizes your application around the core business capabilities it represents. For large
+projects with complex business rules, it is helpful to separate domain logic from transport, persistence, and
+infrastructure concerns:
-{width="350" border-effect="rounded"}
+```
+domain/
+├─ customer/
+│ ├─ Customer.kt // Domain entity
+│ ├─ CustomerService.kt // Domain service
+│ ├─ CustomerRepository.kt // Domain repository interface
+│ └─ CustomerRoutes.kt // Feature routes exposing domain functionality
+├─ order/
+│ ├─ Order.kt
+│ ├─ OrderService.kt
+│ └─ OrderRepository.kt
+infrastructure/
+├─ persistence/
+│ ├─ ExposedCustomerRepository.kt // Concrete persistence implementation
+│ └─ ExposedOrderRepository.kt
+├─ messaging/ // Event messaging infrastructure
+└─ config/ // Application configuration for infrastructure
+events/
+├─ DomainEvents.kt // Domain event definitions
+└─ EventPublisher.kt // Event publishing utilities
+```
+### Domain layer
+The domain layer remains independent of Ktor. It defines the business rules through the following elements:
-This isn't far-fetched with the schema we have above which is grouping routes in their own packages/files, our views in the `resources` folder in the case of Ktor, and of course, nothing prevents us from having a package model where we place any data we want to display or respond to HTTP endpoints with.
+- _Entities_ represent identifiable domain objects:
+```kotlin
+data class Customer(
+ val id: CustomerId,
+ val contacts: List
+)
+```
+- _Value objects_ express immutable concepts such as identifiers or validated fields:
+```kotlin
+@JvmInline
+value class CustomerId(val value: Long)
+```
+- _Aggregates_ group related entities under a single consistency boundary:
+```kotlin
+class CustomerAggregate(private val customer: Customer) {
-While this approach may work and is similar to other frameworks, some would argue that it would make more sense to group things by features, i.e. instead of having the project
-distributed by routes, models and views, have these groups by specific behavior/features, i.e. `OrderProcessPayment`, `CustomerAddressChange`, etc.
+ fun addContact(contact: Contact): Customer =
+ customer.copy(contacts = customer.contacts + contact)
+}
+```
-{width="350" border-effect="rounded"}
+- _Repositories_ abstract persistence and expose operations for retrieving or saving aggregates. Their implementations
+live in the infrastructure layer, but the interfaces belong to the domain.
+```kotlin
+interface CustomerRepository {
+ suspend fun find(id: CustomerId): Customer?
+ suspend fun save(customer: Customer)
+}
+```
+- _Domain services_ coordinate business logic that spans multiple aggregates or does not naturally belong to a single
+entity.
+```kotlin
+class CustomerService(
+ private val repository: CustomerRepository,
+ private val events: EventPublisher
+) {
+ suspend fun addContact(id: CustomerId, contact: Contact): Customer? {
+ val customer = repository.find(id) ?: return null
+ val updated = CustomerAggregate(customer).addContact(contact)
+ repository.save(updated)
+ events.publish(CustomerContactAdded(id, contact))
+ return updated
+ }
+}
+```
+- _Domain events_ represent meaningful business changes. They allow other parts of the system to react to these events
+without directly coupling to the service that produced them.
+```kotlin
+interface DomainEvent
-With many frameworks, this kind of organization of code isn't viable without seriously hacking the underlying conventions. However, with Ktor, given how flexible it is,
-in principle it shouldn't be a problem. With one caveat - when we're using a [template engine](server-templating.md), resources could be an issue. But let's see how we could solve this.
+data class CustomerContactAdded(
+ val id: CustomerId,
+ val contact: Contact
+) : DomainEvent
+```
+These elements together support a rich domain model while keeping infrastructure details separate.
-How this problem is solved very much depends on what is used for Views. If our application is merely an HTTP backend, and we're using client-side technology, then usually all rendering is
-client-side. If we're using Kotlinx.HTML, then once again it's not an issue as the page can be generated from any Kotlin file placed anywhere.
+### Application and routing layer
-The issue arises more when we're using a templating engine such as FreeMarker. These are peculiar in how and where template files should be located.
-Fortunately, some of them offer flexibility in how templates are loaded.
+You expose each domain through its own route file or module function, injecting services that manage both logic and
+state:
-For instance, with FreeMarker, we can use a MultiTemplateLoader and then have templates loaded from different locations:
+```kotlin
+// domain/customer/CustomerRoutes.kt
+fun Application.customerRoutes(service: CustomerService) {
+ route("/customers") {
+ post("/{id}/contacts") {
+ val id = call.parameters["id"]!!.toLong()
+ val contact = call.receive()
+ val updated = service.addContact(CustomerId(id), contact)
+ call.respond(updated ?: HttpStatusCode.NotFound)
+ }
+
+ get("/{id}") {
+ val id = call.parameters["id"]!!.toLong()
+ val customer = service.findById(CustomerId(id))
+ call.respond(customer ?: HttpStatusCode.NotFound)
+ }
+ }
+}
+```
```kotlin
-install(FreeMarker) {
- val customerTemplates = FileTemplateLoader(File("./customer/changeAddress"))
- val loaders = arrayOf(customerTemplates)
- templateLoader = MultiTemplateLoader(loaders)
+// Application.kt
+fun Application.module() {
+ install(ContentNegotiation) {
+ json()
+ }
+
+ val customerRepository: CustomerRepository = ExposedCustomerRepository()
+ val eventPublisher: EventPublisher = EventPublisherImpl()
+
+ val customerService = CustomerService(customerRepository, eventPublisher)
+
+ routing {
+ customerRoutes(customerService)
+ }
}
```
-Obviously this code isn't ideal as it uses relative paths amongst other things, but it's not hard to see how we could actually have
-this loop through folders and load templates, or even have a custom build action that copies views to our `resources` folder prior to execution.
-There are quite a number of ways to solve the issue.
+> For a complete code example of a domain-driven application, see the [Ktor DDD example](https://github.com/antonarhipov/ktor-ddd-example/tree/main).
+
+## Microservice-oriented structure
+
+Ktor applications can be organized as microservices, where each service is a self-contained module that can be deployed
+independently.
+
+Microservice repositories often use a hybrid of modular architecture, DDD for domain isolation and Gradle multi-module
+builds for infrastructure isolation.
+
+```
+service-customer/
+├─ domain/ // Domain models and aggregates
+├─ repository/ // Persistence layer for customer service
+├─ service/ // Business logic
+├─ dto/ // Data transfer objects
+├─ controller/ // Routes or API endpoints
+├─ plugins/ // Ktor plugin installation for this service
+└─ Application.kt // Entry point for the service
+
+service-order/
+├─ domain/ // Domain models and aggregates
+├─ repository/ // Persistence layer for order service
+├─ service/ // Business logic
+├─ dto/ // Data transfer objects
+├─ controller/ // Routes or API endpoints
+├─ plugins/ // Ktor plugin installation for this service
+└─ Application.kt // Entry point for the service
+```
-The benefit of this approach is that we can group everything related to the same functionality in a single location, by feature, as opposed to
-the technical/infrastructure aspect of it.
\ No newline at end of file
+In this structure, each service owns an isolated domain slice and remains modular internally, integrating with service
+discovery, metrics, and external configuration.
\ No newline at end of file
diff --git a/topics/server-modules.md b/topics/server-modules.md
index b0a44e5af..9e6b5fd03 100644
--- a/topics/server-modules.md
+++ b/topics/server-modules.md
@@ -9,8 +9,23 @@
Modules allow you to structure your application by grouping routes.
+
-Ktor allows you to use modules to [structure](server-application-structure.md) your application by defining a specific set of [routes](server-routing.md) inside a specific module. A module is an _[extension function](https://kotlinlang.org/docs/extensions.html)_ of the [Application](https://api.ktor.io/ktor-server-core/io.ktor.server.application/-application/index.html) class. In the example below, the `module1` extension function defines a module that accepts GET requests made to the `/module1` URL path.
+Ktor allows you to use modules to structure your application by defining a specific set of [routes](server-routing.md) inside a
+specific module. A module is an extension function on `Application` that sets up routes, installs plugins, and
+configures services. Using modules helps you:
+
+- Group related routes and logic together.
+- Keep features or domains isolated.
+- Enable easier testing and modular deployment.
+
+> For more information on architectural patterns and module organization, see [](server-application-structure.md)
+
+## Defining a module {id="defining-a-module"}
+
+A module is an _[extension function](https://kotlinlang.org/docs/extensions.html)_ of the
+[`Application`](https://api.ktor.io/ktor-server-core/io.ktor.server.application/-application/index.html) class. In the
+example below, the `module1` extension function defines a module that accepts GET requests made to the `/module1` URL path.
```kotlin
```
@@ -20,7 +35,8 @@ Loading modules in your application depends on the way used to [create a server]
> Note that [plugins](server-plugins.md#install) installed in a specified module are in effect for other loaded modules.
-## embeddedServer {id="embedded-server"}
+## Loading modules {id="loading-modules"}
+### Embedded server {id="embedded-server"}
Typically, the `embeddedServer` function accepts a module implicitly as a lambda argument.
You can see the example in the [](server-create-and-configure.topic#embedded-server) section.
@@ -35,7 +51,7 @@ You can find the full example here: [embedded-server-modules](https://github.com
-## Configuration file {id="hocon"}
+### Configuration file {id="hocon"}
If you use the `application.conf` or `application.yaml` file to configure a server, you need to specify modules to load using the `ktor.application.modules` property.
@@ -80,7 +96,59 @@ A fully qualified module name includes a fully qualified name of the class and a
You can find the full example here: [engine-main-modules](https://github.com/ktorio/ktor-documentation/tree/%ktor_version%/codeSnippets/snippets/engine-main-modules).
-## Concurrent module loading
+
+## Module dependencies
+
+Modules often need to share common services, repositories, or configuration. Injecting dependencies rather than creating
+them inside a module improves testability and flexibility. Ktor offers several approaches depending on the complexity
+of your project.
+
+### Passing dependencies through parameters
+
+The simplest way to pass dependencies is to declare them as parameters of module functions:
+
+```kotlin
+fun main() {
+ embeddedServer(CIO, port = 8080, host = "0.0.0.0") {
+ // Instantiate your dependency
+ val myService = MyService(property())
+ // Inject it into your modules as a parameter
+ routingModule(myService)
+ schedulingModule(myService)
+ }.start(wait = true)
+}
+```
+
+This works well for small or medium applications and keeps dependencies clear. However, modules become tightly coupled
+at compile time and cannot be easily swapped at runtime.
+
+### Using application attributes
+
+You can use `Application.attributes` - a type-safe map available to all modules:
+
+```kotlin
+val customerServiceKey = AttributeKey("CustomerService")
+
+fun Application.servicesModule() {
+ attributes[customerServiceKey] = CustomerService()
+}
+
+fun Application.customerModule() {
+ val service = attributes[customerServiceKey]
+ routing {
+ get("/customers") { call.respond(service.all()) }
+ }
+}
+```
+
+This creates loose coupling by avoiding direct references between modules.
+
+### Using dependency injection {id="dependency_injection"}
+
+Ktor includes a [dependency injection (DI) plugin](server-dependency-injection.md), which allows you to declare and resolve dependencies
+directly inside your Ktor application using a lightweight container.
+
+## Concurrent modules
You can use suspendable functions when creating application modules. They allow events to run asynchronously when
starting the application. To do that, add the `suspend` keyword:
@@ -96,19 +164,25 @@ This allows for non-sequential loading for dependency injection and, in some cas
### Configuration options
-The following Gradle configuration properties are available:
+The following configuration properties are available:
| Property | Type | Description | Default |
|-----------------------------------------|-----------------------------|----------------------------------------------------------|--------------|
| `ktor.application.startup` | `sequential` / `concurrent` | Defines how application modules are loaded | `sequential` |
-| `ktor.application.startupTimeoutMillis` | `Long` | Timeout for application module loading (in milliseconds) | `100000` |
+| `ktor.application.startupTimeoutMillis` | `Long` | Timeout for application module loading (in milliseconds) | `10000` |
### Enable concurrent module loading
-To opt into concurrent module loading, add the following property to your `gradle.properties` file:
+To opt into concurrent module loading, add the following to your server configuration file:
-```none
-ktor.application.startup = concurrent
+```yaml
+# application.conf
+
+ktor {
+ application {
+ startup = concurrent
+ }
+}
```
For dependency injection, you can load the following modules in order of appearance without issues:
diff --git a/topics/server-routing-organization.md b/topics/server-routing-organization.md
new file mode 100644
index 000000000..2bb061c9e
--- /dev/null
+++ b/topics/server-routing-organization.md
@@ -0,0 +1,165 @@
+[//]: # (title: Routing organization)
+
+
+Learn how to organize and structure your routing logic as your Ktor application grows.
+
+
+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 {id="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:
+
+
+
+
+```kotlin
+fun Route.customerByIdRoute() {
+ get("/customer/{id}") {
+
+ }
+}
+
+fun Route.createCustomerRoute() {
+ post("/customer") {
+
+ }
+}
+```
+
+
+
+```kotlin
+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:
+
+```kotlin
+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) {id="group_by_folder"}
+
+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:
+
+```Generic
+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.
+
+
+
+
+```kotlin
+fun Route.createCustomerRoute(service: CustomerService) {
+ post("/customer") {
+ val body = call.receive()
+ call.respond(service.create(body))
+ }
+}
+```
+
+
+
+```kotlin
+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 {id="group_by_path"}
+
+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:
+
+```kotlin
+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 {id="group_by_feature"}
+
+As your application grows, grouping by domain or feature becomes more scalable.
+
+```generic
+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:
+
+```kotlin
+fun Route.customerRoutes(
+ service: CustomerService
+) {
+ route("/customer") {
+ get("/{id}") { call.respond(service.get(call.parameters["id"]!!)) }
+ post { call.respond(service.create(call.receive())) }
+ }
+}
+```
+This pattern keeps feature boundaries clear and prevents routing files from growing too large, especially when each
+domain area contains many endpoints.
\ No newline at end of file
diff --git a/topics/server-routing.md b/topics/server-routing.md
index 4545bf789..1994bbbe5 100644
--- a/topics/server-routing.md
+++ b/topics/server-routing.md
@@ -297,7 +297,7 @@ fun Route.totalizeOrderRoute() {
For the full example demonstrating this approach,
see [legacy-interactive-website](https://github.com/ktorio/ktor-documentation/tree/%ktor_version%/codeSnippets/snippets/legacy-interactive-website).
-> For our application to scale when it comes to maintainability, it is recommended to follow certain [structuring patterns](server-application-structure.md).
+> For our application to scale when it comes to maintainability, it is recommended to follow certain [structuring patterns](server-routing-organization.md).
## Trace routes {id="trace_routes"}
diff --git a/topics/welcome.topic b/topics/welcome.topic
index 57a1cb4b5..064327c5b 100644
--- a/topics/welcome.topic
+++ b/topics/welcome.topic
@@ -49,7 +49,7 @@
Routing
-
+