Wormhole is a tiny web framework built on top of Clack (HTTP server abstraction) that gives you a simple, predictable API to declare an app, register routes and start a development server. It intentionally stays small and composable so you can add middleware, templating and ORM integrations later.
- ✅ App creation & route registration — working
- ✅ Clack/Hunchentoot server integration — working
⚠️ Next steps: middleware stack, templating (Djula), ORM (Mito)
This README documents how to use the framework now. Docs will be separate later due to increasing complexity.
;; define handlers
(defun handler-root (env params)
(declare (ignore env params))
(create-response 200 '(:content-type "text/plain") "Hello Wormhole!"))
(defun handler-user (env params)
(let ((id (getf params :id)))
(create-response 200 '(:content-type "text/plain")
(format nil "User ID: ~a" id))))
(defun handler-user-id-name (env params)
(let ((id (getf params :id))
(name (getf params :name)))
(create-response 200 '(:content-type "text/plain")
(format nil "User ID: ~a, Name: ~a" id name))))
;; create app, register routes and start
(defparameter *app* (create-app :name "example"))
(add-route *app* :get "/" #'handler-root)
(add-route *app* :get "/user" #'handler-user)
(add-route *app* :get "/user/:id/:name" #'handler-user-id-name)
(start-app *app* :port 9556)
Visit:
http://localhost:9556/
→Hello Wormhole!
http://localhost:9556/user
→ list or message fromhandler-user
http://localhost:9556/user/2/alice
→User ID: 2, Name: alice
Create and return a new Wormhole app instance.
Register a route for app
.
<method>
: a single keyword (e.g.:get
,:post
) or a list of methods'(:get :post)
to register the same handler for multiple methods.<uri>
: Sinatra-compatible pattern (/
,/user
,/user/:id
,/files/*
, etc.).<handler>
: a function (see handler signature below).
Returns the handler (useful for chaining/tests).
Starts the Clack server using the app’s handler. Defaults to Hunchentoot and port 9090 (you can pass :port
and :server
).
Stop a running app instance.
Wormhole accepts Sinatra-style URIs:
- Static:
/about
,/assets/logo.png
- Named parameters:
/user/:id
(extracts:id
) - Multiple parameters:
/user/:id/:name
- Wildcard:
/foo/*
matches/foo/anything/here
Behavior note (fall-through): If you have both /foo/:id
and /foo/*
and want the /:id
handler to delegate to the wildcard route when the parameter case does not apply, the handler may call the next-route
continuation (see handler signature). This allows graceful fall-through and pattern chaining instead of immediately returning 404.
A route handler should accept the request environment and params (and optionally the next-route
continuation):
(lambda (env params)
;; env = Clack environment (plist with :request-method, :path-info, headers, etc.)
;; params = plist of named params, e.g. (:id "42" :name "alice")
(create-response 200 '(:content-type "text/plain") "body string"))
Return value: a valid Clack response — either as three values (values status headers body)
or the equivalent list/structure your app helper accepts: (status headers body)
. Handlers in examples return a list of three elements '(status headers body)
for convenience.
Using next-route
:
- When your uri doesn't have the mandatory variable, calling
(next-route)
will continue route matching (useful for fallthrough logic). - If
next-route
is not provided and you want fall-through, design your handler to return a 404 or forward explicitly.
;; more specific param route
(add-route *app* :get "/foo/:id"
(lambda (env params)
(let ((id (getf params :id)))
(if (valid-id-p id)
(create-response 200 '(:content-type "text/plain") (format nil "ID: ~a" id))
(next-route))))) ; try next matching route if id not valid
;; fallback wildcard route
(add-route *app* :get "/foo/*"
(lambda (env params)
(create-response 200 '(:content-type "text/plain") "Generic /foo/* handler")))
Wormhole intentionally aims to be:
- Minimal and composable (use proven pieces such as Clack and MyWay)
- Explicit about the request lifecycle: route matching → handler → response
- Easy to extend: middleware, templating (Djula) and ORM (Mito) are planned next
Planned features:
- Middleware stack (logging, sessions, CSRF)
- Djula integration for templates
- Mito integration for models and migrations
It can be found in the directory docs/
with documentation on each step. (currently inexistent)
My goal with this project is to make web development easy with all the pieces together into a simple framework, just by importing and using its pieces, no scaffolding, you decide how it should be done. That is how I like to work and what I intend with this framework.
- Jean0t