Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions c.list
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,5 @@
<category id="rails" name="Rails Support" order="2"/>
<category id="ruby" name="Ruby Support" order="0"/>
<category id="testing" name="Testing" order="22"/>
<category id="related" name="Related topics" order="1" />
</categories>
3 changes: 2 additions & 1 deletion ktor.tree
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
</toc-element>

<toc-element toc-title="Developing applications">
<toc-element topic="server-application-structure.md" />
<toc-element toc-title="Creating and configuring a server">
<toc-element topic="server-platforms.md"
accepts-web-file-names="supported-platforms.html"/>
Expand Down Expand Up @@ -73,7 +74,7 @@
accepts-web-file-names="routing.html,tracing-routes.html,resolution-algorithms.html,routing-in-ktor.html"/>
<toc-element topic="server-resources.md"
accepts-web-file-names="type-safe-routing.html, features-locations.html,locations.html, server-locations.html"/>
<toc-element topic="server-application-structure.md"
<toc-element topic="server-routing-organization.md"
accepts-web-file-names="structure.html,structuring-applications.html"/>
<toc-element topic="server-autoheadresponse.md"
accepts-web-file-names="autoheadresponse.html"/>
Expand Down
395 changes: 286 additions & 109 deletions topics/server-application-structure.md

Large diffs are not rendered by default.

92 changes: 83 additions & 9 deletions topics/server-modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,23 @@
</tldr>

<link-summary>Modules allow you to structure your application by grouping routes.</link-summary>
<show-structure for="chapter" depth="2"/>

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
```
Expand All @@ -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.
Expand All @@ -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.

Expand Down Expand Up @@ -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<MyServiceConfig>())
// 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>("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:
Expand All @@ -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:
Expand Down
165 changes: 165 additions & 0 deletions topics/server-routing-organization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
[//]: # (title: Routing organization)

<link-summary>
Learn how to organize and structure your routing logic as your Ktor application grows.
</link-summary>

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
<path>CustomerRoutes.kt</path> and <path>OrderRoutes.kt</path> files:

<tabs>
<tab title="CustomerRoutes.kt">

```kotlin
fun Route.customerByIdRoute() {
get("/customer/{id}") {

}
}

fun Route.createCustomerRoute() {
post("/customer") {

}
}
```
</tab>
<tab title="OrderRoutes.kt">

```kotlin
fun Route.getOrderRoute() {
get("/order/{id}") {

}
}

fun Route.totalizeOrderRoute() {
get("/order/{id}/total") {

}
}
```
</tab>
</tabs>

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.

<tabs>
<tab title="CreateCustomer.kt">

```kotlin
fun Route.createCustomerRoute(service: CustomerService) {
post("/customer") {
val body = call.receive<CustomerDto>()
call.respond(service.create(body))
}
}
```
</tab>
<tab title="CustomerRoutes.kt">

```kotlin
fun Route.customerRoutes(service: CustomerService) {
listCustomersRoute(service)
getCustomerRoute(service)
createCustomerRoute(service)
updateCustomerRoute(service)
}
```
</tab>
</tabs>

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<CustomerDto>())) }
}
}
```
This pattern keeps feature boundaries clear and prevents routing files from growing too large, especially when each
domain area contains many endpoints.
2 changes: 1 addition & 1 deletion topics/server-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
Expand Down
2 changes: 1 addition & 1 deletion topics/welcome.topic
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
<title>Routing</title>
<a href="server-routing.md"/>
<a href="server-resources.md"/>
<a href="server-application-structure.md"/>
<a href="server-routing-organization.md"/>
<a href="server-requests.md"/>
<a href="server-responses.md"/>
<a href="server-static-content.md"/>
Expand Down