Computer scienceBackendKtorKtor Plugins

Static content

11 minutes read

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")
}

/assets page with scripts/main.js loaded

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")
}

/assets page with style.css loaded

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.

4 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo