Skip to content

web framework in common lisp made on top of clack. Merges with Djula and Mito to offer a complete experience in development.

License

Notifications You must be signed in to change notification settings

jean0t/wormhole

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Wormhole — a small, opinionated Common Lisp web framework

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.


Status

  • ✅ 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.


Quickstart

;; 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 from handler-user
  • http://localhost:9556/user/2/aliceUser ID: 2, Name: alice

API (concise)

create-app &key :name

Create and return a new Wormhole app instance.

add-route <app> <method> <uri> <handler>

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).

start-app <app> &key :port :server

Starts the Clack server using the app’s handler. Defaults to Hunchentoot and port 9090 (you can pass :port and :server).

stop-app <app>

Stop a running app instance.


Route patterns

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.


Handler signature

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.

Wildcard vs param example (fall-through)

;; 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")))

Design notes & next steps

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

Documentation

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

About

web framework in common lisp made on top of clack. Merges with Djula and Mito to offer a complete experience in development.

Topics

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •