# Your first Busker server This tutorial takes you from an empty directory to a running HTTP server that answers a `curl` request and shuts down cleanly. It uses plain HTTP with no certificates. You only need JDK 25 or later and the Clojure CLI. The finished project is in [`examples/first-server`](https://github.com/outskirtslabs/busker/tree/main/examples/first-server). ## Requirements * JDK 25 or later * The [Clojure CLI](https://clojure.org/guides/install_clojure) ## Set up the project Create a directory and a `deps.edn` in it. The file adds Busker and one native package that matches your operating system and CPU. Busker ships its native code as a separate jar for each supported platform. ```clojure {:paths ["."] :deps {org.clojure/clojure {:mvn/version "1.12.5"} ;; Choose the package for your platform. com.outskirtslabs.busker/linux-x86-64 {:mvn/version "0.0.5"} com.outskirtslabs/busker {:git/url "https://github.com/outskirtslabs/busker" :git/sha "eb221c9d1bc76d5549dc5252b6c9c577af584bc6"}} :aliases {:repl {:jvm-opts ["--enable-native-access=ALL-UNNAMED"]}}} ``` The example uses the Linux x86-64 package. Replace that line with the matching entry below if you use another platform: ```clojure com.outskirtslabs.busker/linux-x86-64 {:mvn/version "0.0.5"} com.outskirtslabs.busker/linux-aarch64 {:mvn/version "0.0.5"} com.outskirtslabs.busker/macos-x86-64 {:mvn/version "0.0.5"} com.outskirtslabs.busker/macos-aarch64 {:mvn/version "0.0.5"} ``` The `--enable-native-access=ALL-UNNAMED` JVM option lets Busker call its native code without a startup warning. Run this command now, and run it again every time you update the Busker commit hash: ```bash clj -X:deps prep ``` ## Write the handler and config A Busker handler is an ordinary synchronous Ring handler: it takes a request map and returns a response map. See [Ring requests and responses](ROOT:ring-reference.adoc) for the request keys Busker supplies and the response forms it accepts. Create `main.clj`. ```clojure (ns main (:require [ol.busker :as busker])) (defn handler [req] {:status 200 :headers {"content-type" "text/plain; charset=utf-8"} :body (str "Hello from Busker over " (:protocol req) ".\n" "Busker can compress this response automatically when the client asks for it.\n")}) (def config {:entrypoints {:http {:bind "127.0.0.1:8080" :tls false}} :dispatch [{:handler handler}]}) (def server (atom nil)) (defn start! [] (reset! server (busker/start! config)) nil) (defn stop! [] (busker/stop! @server) (reset! server nil)) ``` The config is a plain map. `:entrypoints` names one listener bound to `127.0.0.1:8080` with TLS turned off, so the server speaks plain HTTP. `:dispatch` is the request pipeline; here it holds one dispatcher whose `:handler` is your Ring handler. See [Configuration](ROOT:configuration.adoc) for the full config shape. `start!` saves Busker’s opaque server handle in the `server` atom. `stop!` uses that handle to stop the server and then clears the atom. ## Run it Start a Clojure REPL from the project directory: ```bash clojure -M:repl ``` Load the example and start the server: ```clojure (require '[main :as app] '[ol.busker :as busker]) (app/start!) ``` The server now listens on `http://127.0.0.1:8080`. Keep this REPL open. ## Verify the response In another terminal, send a request with `curl`: ```bash curl -i http://127.0.0.1:8080/ ``` You get a `200` response whose body names the protocol curl negotiated: ```text HTTP/1.1 200 OK content-type: text/plain; charset=utf-8 content-length: 110 Hello from Busker over HTTP/1.1. Busker can compress this response automatically when the client asks for it. ``` ## Try built-in compression Busker compresses eligible responses when the client sends an `accept-encoding` header. Compression is enabled by default, so the server config needs no changes. Ask for gzip and tell curl to decompress the body: ```bash curl --compressed -H 'accept-encoding: gzip' -i http://127.0.0.1:8080/ ``` The response headers show that Busker selected gzip. Curl prints the decompressed body because of `--compressed`. ```text HTTP/1.1 200 OK content-type: text/plain; charset=utf-8 content-encoding: gzip vary: accept-encoding Hello from Busker over HTTP/1.1. Busker can compress this response automatically when the client asks for it. ``` Busker can also negotiate Brotli and Zstandard when a client advertises `br` or `zstd`. ## Inspect the server state `app/server` is an atom containing the handle returned by [`ol.busker/start!`](ROOT:api/ol-busker.adoc#start-BANG-). Evaluate this in the same REPL: ```clojure (select-keys (busker/state @app/server) [:phase]) ;; => {:phase :running} ``` [`ol.busker/state`](ROOT:api/ol-busker.adoc#state) returns the current phase and the normalized config snapshot. Evaluate `(busker/state @app/server)` without `select-keys` to inspect the full map. ## Stop the server Stop the server from the same REPL: ```clojure (app/stop!) @app/server ;; => nil ``` `app/stop!` calls [`ol.busker/stop!`](ROOT:api/ol-busker.adoc#stop-BANG-), waits for the server to stop, and clears the atom. ## Where to go next * [Configuration](ROOT:configuration.adoc) covers TLS, HTTP/3, multiple entrypoints, and every other config key * [Ring compatibility](ROOT:guides/ring-compatibility.adoc) explains handlers, middleware, and streaming