tiny-routes
tiny-routes copied to clipboard
A tiny routing library for Common Lisp targeting Clack.
#+TITLE: TINY-ROUTES #+AUTHOR: Johnny Ruiz #+EMAIL: [email protected] #+DESCRIPTION: A tiny routing library for Common Lisp targeting Clack #+LANGUAGE: en #+OPTIONS: H:4 num:nil toc:2 p:t ** Introduction TINY-ROUTES is a bare-bones routing library for [[https://lisp-lang.org][Common Lisp]] targeting the [[https://github.com/fukamachi/clack.git][Clack]] web application development. Its only external dependency is [[http://edicl.github.io/cl-ppcre/][CL-PPCRE]] which is used to match and bind dynamic URL parameters.
** Prerequisites The following lists the prerequisites needed to follow this [[https://github.com/jeko2000/tiny-routes/blob/main/README.org][README]] successfully.
- [[https://lispcookbook.github.io/cl-cookbook/web.html][Common Lisp web frameworks]]
- In particular, [[http://edicl.github.io/hunchentoot/][Hunchentoot]] and [[https://github.com/fukamachi/clack.git][Clack]].
- [[https://developer.mozilla.org/en-US/docs/Web/HTTP][HTTP]]
- Specifically, you should be familiar with [[https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods][HTTP methods]].
- [[https://www.quicklisp.org/beta/][Quicklisp]]
- Ideally, you should have [[https://www.quicklisp.org/beta/#installation][Quicklisp installed]] and read through its [[https://www.quicklisp.org/beta/][documentation]].
** Motivation [[https://github.com/fukamachi/clack.git][Clack]]'s killer feature is that it allows us server-agnostic web application. Namely, web applications that are not tied to a specific server library such as [[http://edicl.github.io/hunchentoot/][Hunchentoot]], [[https://github.com/gigamonkey/toot][Toot]], or [[https://github.com/orthecreedence/wookie][Wookie]].
Clack distills a web application down to just a =handler= function that accepts a =request= (or an =environment=) object (i.e., a [[https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node108.html][plist]] containing data like HTTP request method, request URI, among others) and returns an appropriate HTTP response. This HTTP response is represented simply by a list of three elements containing the HTTP status code, HTTP headers, and a response body.
This simplification provides developers with a great amount of flexibility given that we can leverage the entire power of lisp to create these simple list structures representing HTTP responses. However, I quickly found that [[https://github.com/fukamachi/clack.git][Clack]]' itself does not offer tools or guidance on how such request =handler= functions should be written, composed, and ultimately managed.
The purpose of TINY-ROUTES is to provide a small set of helper/utility functions and macros to aid the developer in building clack applications by breaking them up into composable request handlers and middleware functions.
** Definitions The following represents definitions used in this document.
-
=request= or =environemnt= :: A plist representing an HTTP request. Please see [[https://github.com/fukamachi/clack.git][Clack]] for a list of supported fields. Finally, please note that users and/or middleware are allowed (and encouraged!) to add new fields to the request object for new functionality.
-
=response= :: A list or lambda representing an HTTP response. Per the [[https://github.com/fukamachi/clack.git][Clack]] documentation, a response can either be:
-
A list of three elements representing an HTTP status code, HTTP headers, and a response body
-
A lambda accepting a =response= function to be called with a =response= asynchronously.
-
-
=handler= :: A function that accepts a =request= and returns a =response= or nil.
-
=middleware= :: A combinator function that accepts a handler as its first argument, optionally accepts other arguments, and returns a potentially different handler.
** Usage The following application exposes:
- A static endpoint which returns an HTTP 200 response with the body =alive=
- A dynamic endpoint which returns an HTTP 200 response with a string containing the bound value =account-
- A catch-all handler which returns an HTTP 404 response with the body =not-found=. #+begin_src common-lisp (define-routes app ; (1) (define-get "/" () ; (2) (ok "alive")) ; (3) (define-get "/accounts/:account-id" (request) ; (4) (let ((account-id (path-parameter request :account-id))) ; (5) (ok (format nil "Your account id: ~a." account-id)))) ; (6) (define-any "*" () ; (7) (not-found "not-found"))) ; (8) #+end_src Each line in the example is detailed below
- The =define-routes= macro accepts a variable number of handlers and returns a new handler that loops through each handler and returns the first non-nil response it finds.
- The =define-get= macro creates a handler that matches on HTTP GET requests. The macro accepts a =path-template= and a request binding. Please note TINY-ROUTES also exposes similar macros for POST, PUT, HEADER, HEAD, and OPTIONS.
- The =ok= function accepts an optional body and returns a response list with HTTP status 200 OK.
- The =define-get= macro now receives a path-template with a dynamic parameter named =:account-id=. This value associated with this dynamic parameter is made available as part of the request's path-params as seen in line (5).
- The =path-param= selector function can be used to quickly parse a path param from a request object.
- We leverage the =format= function to show that the response body can also be dynamic.
- The =define-any= macro can be used to implement catch all routes.
- The =not-found= function accepts an optional body and returns a response list with HTTP status 404 Not Found.
** Installation To install via [[https://www.quicklisp.org/beta/][quicklisp]] please run the following in your running REPL: #+begin_src common-lisp (ql:quickload "tiny-routes") #+end_src ** Sample code Please see [[https://github.com/jeko2000/tiny-routes-realworld-example-app][tiny-routes-realworld-example-app]] for a full-fledged [[https://github.com/fukamachi/clack.git][Clack]] REST application demonstrating one possible use of [[https://github.com/jeko2000/tiny-routes/][tiny-routes]].
Enjoy!