# Serving static files with Ktor

DevFeed: [Serving static files with Ktor](<https://devfeed.tech/articles/serving-static-files-with-ktor-21777.md>)

Original publisher: [Read original article](<https://enoent.fr/posts/serving-static-files-with-ktor/>)

Author: Marc Plano-Lesay

Published: 2022-09-03T09:00:00Z

Content type: tutorial

Language: en

Sources: [Marc Plano-Lesay](<https://devfeed.tech/sources/marc-plano-lesay.md>)

Topics: [servers](<https://devfeed.tech/topics/servers.md>), [Routing (disambiguation)](<https://devfeed.tech/topics/routing.md>), [web applications](<https://devfeed.tech/topics/web-applications.md>), [Web Development](<https://devfeed.tech/topics/web-development.md>)

Tags: [files](<https://devfeed.tech/tags/files.md>), [handler](<https://devfeed.tech/tags/handler.md>), [html](<https://devfeed.tech/tags/html.md>), [http](<https://devfeed.tech/tags/http.md>), [kotlin](<https://devfeed.tech/tags/kotlin.md>), [ktor](<https://devfeed.tech/tags/ktor.md>), [plugin](<https://devfeed.tech/tags/plugin.md>), [port](<https://devfeed.tech/tags/port.md>), [route](<https://devfeed.tech/tags/route.md>), [routing](<https://devfeed.tech/tags/routing.md>), [web](<https://devfeed.tech/tags/web.md>)

## AI overview

This tutorial explains how to serve static files and nested index.html files with Ktor. It replaces the default static-file handling with a custom route that resolves files and directory fallbacks, then uses a Ktor plugin to handle trailing slashes.

## Source excerpt

Serving static files with Ktor for a static website seems easy enough: fun main() { embeddedServer(Netty, port = 8080) { routing { static { files("static-data") } } }.start(wait = true) } Done? Not quite. Opening http://localhost:8080 will only get you a 404, even if you have an index.html file in the static-data folder. You have to let Ktor know you want to opt-in serving a default file. Let's try again (omitting anything outside the static block): static { files("static-data") default("static-data/index.html") } Surely now this works? Well, kind of. The root folder will have a default index.html, but sadly if you have any sub-folders with index.html files inside, they won't be served by default - that default() function only works for the top-level folder. Let's work around that. Let's store our static-data directory into a File variable, replace the files function call by our own extension. Note that this extension will handle the fallback to index.html in all cases, so we can also remove the call to default(): val staticData = File("/path/to/static-data") static { filesWithDefaultIndex(staticData) } fun Route.filesWithDefaultIndex(dir: File) { } In there, we'll need a get handler: fun Route.filesWithDefaultIndex(dir: File) { get("{static_path...}") {} } This will catch anything inside the static path, and let us access the requested path through call.parameters.getAll("static_path"). This gets us a list of URL segments that we need to join with File.separator: val relativePath = call.parameters .getAll("static_path") ?.joinToString(File.separator) ?: return@get Now, we have three options: The requested path points to an existing file. Great! We can just return it. The requested path points to a folder. If there's an index.html file in there, let's serve it. In any other case (the requested path doesn't exist, or points to a folder without an index.html file), we let Ktor handle that (most likely returning a 404). We can easily access the corresponding local file