Clerk is a web development library targeting the Chez Scheme programming language. It is a robust, decently performant, and powerful tooling library that is intended for the programmer to use as a foundation for their own abstractions. It relies on extremely little, intentionally so, and is designed to remain as small–footprinted as is reasonable for such a multi–faceted system. The library contains the following presently:
(clerk desk).
(clerk web).
(clerk web paper).
n
locations, in (clerk ledger).
There are also features that are slated to be added, as the need arises in personal use of this library. These features mostly pertain to database connectivity, due to that being the last piece needed for Clerk to be fully usable across a wide range of web development needs. A short list of these possible features are:
HEAD requests.textual-input-port response
content.
Using Clerk should be as simple as 1-2-3:
make
within the cloned directory.
(clerk web)
and (clerk web paper).
A simple example FastCGI server is as follows:
#!/usr/bin/env scheme-script
(import (except (chezscheme) map div meta)
(clerk web paper)
(clerk web))
(web-clerk
"unix://./example.fcgi"
(GET "/*" (lambda (r)
(document "en"
(head (title "Hello, Clerk!"))
(body (h1 "Hello, Clerk!"))))))
Below are the relevant procedures exported by the different Clerk libraries.
(clerk attention)
(clerk attention) provides a simple green
thread/fiber concurrency system utilizing Scheme
continuations. All Clerk handlers should operate within
an Attention locus in order to run properly.
This is done automatically by the (clerk...)
and (web-clerk...) procedures documented
below.
(yield &optional task)
Yields control back to the runtime, which will then
choose another fiber to run. task is a
procedure that takes no arguments, which is then added
to the evaluation chain. For a more robust way to add
tasks to the evaluation chain, see (async...)
and (await...).
(async procedure &optional arguments ...)
Yields control back to the runtime, providing a new fiber
to evaluation in the form of
(procedure arguments...). The fiber calling
async is readded to the evaluation
chain, and will not wait for the completion of the newly
created fiber. Additionally, no communication layer is
provided between fibers, so the result of the new fiber
should not be relied upon within the calling context.
(await procedure &optional arguments ...)
Yields control back to the runtime, but does not readd the
current fiber to the evaluation chain. Instead, it returns
a new fiber that will return back to the currently evaluating
fiber. This suspends evaluation of the fiber calling
await until such a time as when a return value
is ready.
(locus procedure &optional arguments ...)
Spawns and evaluates a new evaluation chain, with the first
fiber on the chain being of the form
(procedure arguments ...). This procedure will
block until the evaluation chain is empty.
(clerk desk)
This library exports the high-level FastCGI system. This is protocol-agnostic, and simply provides a request handler with the information and ports provided by the underlying FastCGI layer.
The protopath is a URI-like string that takes
the form "scheme://path". Two protopaths are
presently supported by Clerk:
"unix://file/path"
(Creates a Unix domain socket)
"tcp://ip:port"
(Creates a TCP server socket)
A (clerk desk) handler takes the following form:
(lambda (context input output error) ...)
Where the arguments are as follows:
context:
The CGI environment provided to Clerk by the upstream.
input:
A binary-input-port that reads from the
FastCGI standard input stream.
output:
A binary-output-port that emits to the
FastCGI standard output stream.
error:
A binary-output-port that emits to the
FastCGI standard error stream.
(parameter context name)
Attempts to collect the value of the environment variable
named name, returning #f if it
was not found.
(clerk protopath handler)
Creates and evaluates a new Clerk system, bound to the
given protopath, and passing all FastCGI
requests to the procedure handler.
This procedure blocks indefinitely.
(clerk web)
This library creates an HTTP-specific FastCGI layer, built
directly on top of (clerk desk). Handlers within
this library take on a different form compared to its more
agnostic counterpart:
(lambda (request) ... response)
Where the handler receives a web-request object
that can be queried with the procedures below, and it is
expected to return the relevant response to that request.
Responses in (clerk web) can be one of the
following types:
web-response:
A full HTTP response, built using the syntax
respond, as defined below.
html-document:
An HTML document as constructed by
(clerk web paper)'s document
procedure. Content type is assumed to be
text/html.
html-tag:
An HTML tag as constructed by
(clerk web paper)'s document
procedure. This allows the return of partial HTML
documents, which can then be used by frontend systems
like HTMX, if so desired.
Content type is assumed to be text/html.
string:
A string. Content type is assumed to be
text/plain.
bytevector:
A bytevector. Content type is assumed to be
application/octet-stream.
input-port:
An input port, which is then transferred to the output
of the request. Content type is assumed to be
application/octet-stream.
If the response is not a full web-response
record, then the content type is inferred, and the status
is assumed to be 200.
Additionally, path objects in this library,
while seen as strings, are a list of path parts that are
compared when handling a request. This process is automatic,
but can be used manually through
(clerk web path) for further routing within
a handler. More information on these paths is documented
below.
(request-method request)
Returns a string of the request method.
CGI equivalent is REQUEST_METHOD.
(request-path request)
Returns a string of the request path.
CGI equivalent is SCRIPT_NAME.
(query-string request)
Returns a list representing the parsed query string.
CGI equivalent is QUERY_STRING.
(content-type request)
Returns a string of the request content type.
CGI equivalent is CONTENT_TYPE.
(content-length request)
Returns a string of the request content length.
CGI equivalent is CONTENT_LENGTH.
(n-content request n)
Returns a bytevector of n-length, that contains part
of the request body. This will continue to read
beyond the length of content-length, so any handler
using this should stop at that length.
(all-content request)
Returns a bytevector of length content-length, which
contains the entire unprocessed request body.
(form-urlencoded request)
Returns a form-data list, which is the result of parsing
the retrieved request body as a form-urlencoded string.
Handlers should check content type before calling this procedure.
(parameter request name)
Returns the value of the CGI variable represented
by name, or #f if not found.
(query request name)
Returns the values provided by the query string that
matches name, as a list.
(respond ...)A syntax that takes on multiple forms, dependent upon what is being returned. The forms it supports are as follows:
(respond status body)(respond status type body)(respond status (header ...) body)(respond status type (header ...) body)
Where status is the HTTP response code, as
an integer, type is the response content type,
as a string, header is a cons pair
representing an HTTP header name and value, and finally the
body is a valid content body, as defined above.
An example response:
(respond
200
"text/plain"
(("Date" "Tue Jul 14 01:23:13 PM EDT 2026")
("X-Custom" "Custom header!"))
"Hello, world!")
(GET path handler)
Returns a request-handler for the
GET method, and the given path.
(POST path handler)
Returns a request-handler for the
POST method, and the given path.
(PUT path handler)
Returns a request-handler for the
PUT method, and the given path.
(PATCH path handler)
Returns a request-handler for the
PATCH method, and the given path.
(DELETE path handler)
Returns a request-handler for the
DELETE method, and the given path.
(web-clerk protopath handlers ...)
Blocks, running a web-clerk environment at the
given protopath, and with the provided
handlers. Handlers are checked from first to
last, and as such more general routes should be placed lower
in this syntax. Otherwise, a more general route may supercede
a more specific one.
(clerk web paper)
This library exports symbols representing every HTML 4.01 tag, as declared in the specification. Additionally, it exports a simple helper procedure as well as a couple duplicate exports due to name collisions. All tag procedures take either of these forms:
(tag &optional body...)(tag (attribute ...) &optional body ...)
Attribute follows a similar form to response headers,
where it simply follows the form of
(name value). Both name and value should
evaluate to strings.
(document lang head body)
A helper procedure to emit valid HTML 4.01 documents.
lang is a string language identifier.
(clerk web path)
This library outputs a simple path comparison
system for (clerk web). Paths are a list of
path segments, with an optional wildcard that indicates
the path should be checked as a prefix. Placing a wildcard
in the middle of a path is undefined.
(string->path string)Convert a string into a path list. For example:
(string->path "/a/b/c") ; => ("a" "b" "c")
(path->string path)Collapses a path list back into its string representation. Usually just used for logging/display.
(path-matches? matcher matchee)
Returns #t if the matcher matches the matchee,
otherwise returns #f. If the matcher path ends
with a wildcard ("*"), it checks if it is the
prefix of matchee. Otherwise, it only accepts exact matches.
(clerk web form)
This library provides simple utilities for parsing
form-urlencoded content types, encoding
and decoding strings using URL rules, and querying the
resulting parsed values of form-urlencoded.
(url-encode string)
Encodes the provided string to be URL-safe. Uses both percent
encoding as well as + in place of space characters.
(url-decode string)
Decode the provided URL-safe string. Supports both percent
encoding as well as + in place of space characters.
(parse-form-urlencoded string)Parses the provided form string into a list with the form:
(("name" . "value") ("name2" . "value2") ...)
Form arrays are supported by adding the name multiple times,
each time with a value to be added to the list. This is represented
in the returned list by containing multiple name/value pairs for
that name, which is then collapsed into a list by
query-form
(url-encode-form form-data)
Encodes the given form-data list as a valid, URL-safe
string.
(query-form form-data name)Queries the given form data. This procedure always returns a list of all values associated with the given name. If the name is not found, an empty list is returned.
(clerk ledger)
The ledger logging library, which enables a simple write-through model of logging. It utilizes a list of ledgers that are emitted to, and a simple way to stack a new ledger on top within a scope.
(remark level message &optional arguments ...)
Emits the given message + arguments (ran through format)
into the current write stack.
Supported levels are DEBUG, INFO, WARN, and ERROR.
(with-ledger name buckets proc)
Stacks a new ledger, named name, and emitting to the
buckets specified by the list buckets, to the write
stack. It then calls proc, and will remove the ledger
from the stack on termination of the procedure given.
(make-bucket port closable)
Takes in a binary-output-port, and whether or not it is
closable, and returns a new bucket procedure. If it is marked as
closable, it will close the port if told to close by
(clerk ledger).
Copyright © 2026 Harald Sorenson
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.