Liking cljdoc? Tell your friends :D
All platforms.

koine.server

Inbound HTTP, portable. One path, one handler fn, every host.

Deliberately minimal. The use case is a JSON-RPC endpoint (toolnexus SPEC §7B/§7C): a request comes in on ONE path, a string body goes in, a string body comes out. There is no routing table, no middleware, no static files, no websockets, no TLS — every one of those multiplies the surface that has to agree across four hosts, and none of them is needed.

(def h (server/serve (fn [req] {:status 200 :body (:body req)})
                     {:port 0}))
(server/port h)   ; => 54321
(server/stop! h)  ; => nil, idempotent

handler : request-map -> response-map request {:method :post :path "/" :headers {"content-type" "…"} :body "…"} response {:status 200 :headers {…} :body "…"} ; :status defaults to 200, ; :headers to {}, :body to ""

Normalised across hosts: :method is always a lower-case keyword, :headers keys are always lower-case strings with a single string value (first value wins on repeats), :body is always a string — never nil.

Host support (measured 2026-07-27, see the porting notes at the bottom):

hostserve:port 0stop!:host
jvmyesyesyesyes
cljgoyesyesyesno
glojureyesNOyesyes
let-goyesNONOyes

Where a host cannot do something it throws a named, actionable error at the point of use rather than pretending.

Inbound HTTP, portable. One path, one handler fn, every host.

Deliberately minimal. The use case is a JSON-RPC endpoint (toolnexus
SPEC §7B/§7C): a request comes in on ONE path, a string body goes in, a
string body comes out. There is no routing table, no middleware, no
static files, no websockets, no TLS — every one of those multiplies the
surface that has to agree across four hosts, and none of them is needed.

    (def h (server/serve (fn [req] {:status 200 :body (:body req)})
                         {:port 0}))
    (server/port h)   ; => 54321
    (server/stop! h)  ; => nil, idempotent

handler : request-map -> response-map
  request  {:method :post :path "/" :headers {"content-type" "…"} :body "…"}
  response {:status 200 :headers {…} :body "…"}   ; :status defaults to 200,
                                                    ; :headers to {}, :body to ""

Normalised across hosts: `:method` is always a lower-case keyword,
`:headers` keys are always lower-case strings with a single string value
(first value wins on repeats), `:body` is always a string — never nil.

Host support (measured 2026-07-27, see the porting notes at the bottom):

| host    | serve | :port 0 | stop! | :host |
|---------|-------|---------|-------|-------|
| jvm     | yes   | yes     | yes   | yes   |
| cljgo   | yes   | yes     | yes   | no    |
| glojure | yes   | NO      | yes   | yes   |
| let-go  | yes   | NO      | NO    | yes   |

Where a host cannot do something it throws a named, actionable error at
the point of use rather than pretending.
raw docstring

portclj/s

(port handle)

The port the server is actually bound to. The reason :port 0 is worth having: on the JVM and cljgo this is the OS-assigned port.

The port the server is actually bound to. The reason `:port 0` is worth
having: on the JVM and cljgo this is the OS-assigned port.
raw docstring

serveclj/s

(serve handler {:keys [port host path] :or {port 0 host "127.0.0.1" path "/"}})

Start an HTTP server that sends every request on path to handler.

opts: {:port 8080 ; 0 = pick a free port, where the host allows it :host "127.0.0.1" :path "/"} ; catch-all; see the caveat below

Returns an opaque handle for port and stop!.

The default :path of "/" is a prefix/catch-all on all four hosts and is the only value that behaves identically everywhere — let-go's http/serve takes no pattern at all, so a non-"/" path is NOT enforced there. The handler always receives :path, so filter in the handler if it matters.

Start an HTTP server that sends every request on `path` to `handler`.

opts: {:port 8080          ; 0 = pick a free port, where the host allows it
       :host "127.0.0.1"
       :path "/"}          ; catch-all; see the caveat below

Returns an opaque handle for `port` and `stop!`.

The default `:path` of "/" is a prefix/catch-all on all four hosts and is
the only value that behaves identically everywhere — let-go's `http/serve`
takes no pattern at all, so a non-"/" path is NOT enforced there. The
handler always receives `:path`, so filter in the handler if it matters.
raw docstring

stop!clj/s

(stop! handle)

Shut the server down. Returns nil. Idempotent — the second call is a no-op, so teardown can be unconditional.

Throws on let-go, every time, because there is nothing to stop there; the flag is only set once the underlying shutdown actually succeeded, so an unsupported host stays loudly unsupported rather than going quiet after the first attempt.

Shut the server down. Returns nil. Idempotent — the second call is a
no-op, so teardown can be unconditional.

Throws on let-go, every time, because there is nothing to stop there;
the flag is only set once the underlying shutdown actually succeeded, so
an unsupported host stays loudly unsupported rather than going quiet
after the first attempt.
raw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close