When you create a website, you need to place style sheets, scripts, images, and other files on the server that are not created dynamically. These files are static. Loading the contents of a file every time and sending it as raw data is inconvenient, so Ktor provides a simple way to serve these files using the static plugin.
Serving static files
Consider a project with the following structure:
ktor-static/
├── src/
│ └── main/
│ ├── kotlin/
│ │ └── org.hyperskill.ktor/
│ │ └── Application.kt
│ └── resources/
└── static-assets/
├── style.css
└── scripts/
├── main.js
└── hello.js
First, you should define a route for the static content, for example, assets. Everything under this route (for example, assets/style.css) will be treated as static content:
routing {
static("assets") {
}
}
Next, define a folder where we store static files (the static root) by setting staticRootFolder property:
routing {
static("assets") {
staticRootFolder = File("static-assets")
}
}
Finally, specify files to serve. Define the foldername files("foldername") to serve all files from this folder recursively. In this example, we will serve all files from the static-assets folder:
routing {
static("assets") {
staticRootFolder = File("static-assets")
files(".")
}
}
Now you can access static files using the following paths:
- assets/style.css
- assets/scripts/main.js
- assets/scripts/hello.js
Advanced static files
You may need to serve individual files. In such a case, you need file("filename") (not files()):
routing {
static("assets") {
staticRootFolder = File("static-assets")
file("style.css")
}
}
Sometimes it is convenient to define the default file to be loaded. For example, let's response file scripts/main.js when we receive a request for an assets route:
static("assets") {
staticRootFolder = File("static-assets")
default("scripts/main.js")
}
Embedded application resources
Usually, you don't need to create an assets folder in the project root. You can store static files inside the resources folder. Consider a project with the following structure:
ktor-static/
└── src/
└── main/
├── kotlin/
│ └── org.hyperskill.ktor/
│ └── Application.kt
└── resources/
└── static/
├── style.css
└── scripts/
├── main.js
└── hello.js
Serving static files from the resources folder is similar to the previous way, but now you need to specify the property staticBasePackage (just like staticRootFolder) to define the folder with static files in the resources. Also, the base package property is not File; now, it is String:
routing {
static("assets") {
staticBasePackage = "static"
}
}
Now, let's serve all files from this folder by using resources("foldername"):
routing {
static("assets") {
staticBasePackage = "static"
resources(".")
}
}
Again, you can access static files using the following paths:
- assets/style.css
- assets/scripts/main.js
- assets/scripts/hello.js
Advanced resources
Just like static files, you may need to serve an individual resource:
static("assets") {
staticBasePackage = "static"
resource("style.css")
}
You can define the default resource file to be loaded:
static("assets") {
staticBasePackage = "static"
defaultResource("style.css")
}
Nested routes
You can create nested routes. It works with both static files and resources:
static("assets") {
staticBasePackage = "static"
static("js") {
resources("scripts")
}
}
static("static-assets") {
staticRootFolder = File("static-assets")
static("style") {
file("style.css")
}
}
In this example, you can access static files using the following paths:
- assets/js/main.js
- assets/js/hello.js
- static-assets/style/style.css
Example
To understand how serving static content happens, let's give an example. Consider a project with the following structure:
ktor-static/
└── src/
└── main/
├── kotlin/
│ └── org.hyperskill.ktor/
│ └── Application.kt
└── resources/
└── static/
├── pages/
│ ├── index.html
│ ├── info.html
│ └── contacts.html
├── styles/
│ ├── navbar.css
│ └── layout.css
├── scripts/
│ ├── index.js
│ ├── toast.js
│ └── fetch.js
├── favicon.svg
└── robots.txt
First, let's serve robots.txt and favicon.svg files. The first is used by search engine bots. The second is an icon in the tab of your site. These files should be accessible by the root path:
static {
staticBasePackage = "static"
resource("robots.txt")
resource("favicon.svg")
}
Next, we are going to serve the HTML pages. We can configure routing using static routes. In addition, we want to serve index.html as the default page, so the user just needs to go to the site to open this page:
static {
staticBasePackage = "static"
resource("robots.txt")
resource("favicon.svg")
static("pages/") {
staticBasePackage = "static/pages"
defaultResource("index.html")
static("info") { defaultResource("info.html") }
static("contacts") { defaultResource("contacts.html") }
}
}
As you can see, we use defaultResource so that the user doesn't have to type the .html extension for accessing pages.
Finally, let's serve the CSS files to improve the outlook of our pages. Since these files can be added and deleted, it is much more convenient to serve the entire corresponding folder at once:
static {
staticBasePackage = "static"
resource("robots.txt")
resource("favicon.svg")
static("pages/") {
staticBasePackage = "static/pages"
defaultResource("index.html")
static("info") { defaultResource("info.html") }
static("contacts") { defaultResource("contacts.html") }
}
static("styles/") { resources("styles") }
static("scripts/") { resources("scripts") }
}
To understand how to get which file, you can look at this table:
| File | Location |
|---|---|
| static/robots.txt | /robots.txt |
| static/favicon.svg | /favicon.svg |
| static/pages/index.html | /pages/ |
| static/pages/info.html | /pages/info |
| static/pages/contacts.html | /pages/contacts |
| static/styles/<filename> | /styles/<filename> |
| static/scripts/<filename> | /scripts/<filename> |
Serving pre-compressed files
To improve the transfer speed and bandwidth utilization, you can enable outgoing content compression. Dynamic data compression takes time and server resources, so you can compress static data once using compression algorithms like Gzip, Brotli, or Deflate. To serve these files, you must specify that it is pre-compressed data:
static("assets") {
preCompressed {
files("css")
files("js")
}
}
When you use different compression algorithms, you can specify the priority of one algorithm over another. In the example below, Ktor tries to serve *.br files over *.gz:
static("assets") {
preCompressed(CompressedFileType.BROTLI, CompressedFileType.GZIP) {
files("css")
files("js")
}
}Conclusion
In this topic, you've learned that it's not always a good idea to serve files as raw data, as Ktor simplifies serving static files with the static plugin. Now, you can serve files located anywhere in the project and application resources. Sometimes, you may need to serve pre-compressed files to improve transfer speed and bandwidth utilization.