README.md

April 19, 2026 ยท View on GitHub

๐Ÿ’พ Asset Loader

A simple static asset loader for Scala web servers.

Build status Maven Central Documentation



"The more you sweat in peace, the less you bleed in war." โ€” Norman Schwarzkopf.


โœ”๏ธ Features

This tool is extremely small and minimalistic, with absolutely no bells or whistles.

  1. Loads all static assets (e.g. images, stylesheets, scripts, etc.) into memory.
  2. Allows retrieval by relative path.
  3. Tags each asset with an appropriate Content-Type.
  4. Tags each asset with an ETag for efficient caching and update detection.

โฌ‡๏ธ Installation

Add the following dependency to your build.sbt:

libraryDependencies += "com.alecdorrington" %% "asset-loader" % "0.2.5"

Compiled with Scala 3.8.3, with no intention to explicitly support older versions.

โš™๏ธ Example

This example uses fake Request and Response types to illustrate the idea in a simple manner. The details will depend on your choice of web framework (e.g. Tapir or http4s).

import com.alecdorrington.assetloader.{Asset, AssetLoader}

val assetLoader = AssetLoader(assetsPath = "client/src/main/resources")

def handleRequest(request: Request): Response =
  if request.path.startsWith("assets/") then
    val assetOption: Option[Asset] = assetLoader.getAsset(request.path.dropLeft(7))
    assetOption match
      case Some(asset) => Response(asset)
      case None => Response.NotFound
  else
    // ...

๐Ÿ“ก Server Integration

Currently, a connector exists for only a single web framework: Tapir. In principle, any future connectors will be published as separate dependencies with the name asset-loader-{web-framework}. Contributions are welcome!

Tapir

Tapir is a library to describe HTTP APIs and expose them as a server. A separate connector is provided to easily create a Tapir endpoint that serves static files from Asset Loader. Just add the following additional dependency:

libraryDependencies += "com.alecdorrington" %% "asset-loader-tapir" % "0.2.5"

Observe the following minimal example, using Netty and Cats Effect:

object Main extends ResourceApp.Forever:

  val assets = AssetService[IO](
    externalPath = "assets",
    internalPath = Paths.get("src/main/resources"),
  )

  def run(args: List[String]) =
    NettyCatsServer
      .io()
      .flatMap: server =>
        val service = server
          .host("0.0.0.0")
          .port("8080")
          .addEndpoints(assets.api)
        Resource.make(service.start())(_.stop()).as(())

๐Ÿ–ฅ๏ธ Client Versions

All of the above dependencies are exclusively for the JVM. However, you may wish to access the non-JVM-specific functionality from the client as well. For this reason, each aforementioned dependency is published with a common part that is cross-compiled.

These can be installed as follows:

libraryDependencies += "com.alecdorrington" %%% "asset-loader-common" % "0.2.5"
libraryDependencies += "com.alecdorrington" %%% "asset-loader-tapir-common" % "0.2.5"

Note that you don't need to explicitly include the above if you only use this library on the server.

๐Ÿ‘๏ธ See also