So, after the last you now have a lisp repl and a wish to do a web app?
I assume you know your basic http- and client-server stuff. You have maybe done things with php, whatever java used to call its low level http library, or clojure's ring. Ring is (in theory :D) my favourite model of writing http apis. However as there are a lot of pain points in clj, I don't really write ring if not paid to do it.
I'm going to focus on backend here, because I don't know yet of any lisp you could command with slime and run inside a browser. I just assume there's a cljs or some other compiler generating a blob of js you could serve as a resource from a backend api.
Hunchentoot
I'm not intimately familiar with the hierarchy and history of lisp web libraries and servers, but Hunchentoot seems to be one of the oldest of those relevant today. Many "higher level" libraries are based on it (or are just a pile of macro hacks that transform into hunchentoot code :D).
I use hunchentoot abstracted behind the router called mmontone/easy-routes. Unlike in ring, in which every detail of the http-response is found inside the handler's return value, easy-routes' handler returns only the body of the response and others (like http statuc code, mime type, headers) are setf'able inside the handler's dynamic scope.
There's also no magic like clojure's transit. In my apps I expect frontend to either serialize its request bodies into json (which lets me throw whatever
Database
This blog has always stored its data inside a PostgreSQL instance. As there is no jdbc in lisp, connecting to a database takes a little creativity. In this blog I connect to psql with Postmodern. Blog's db-middleware has some magic (in db-config lambda) reading connection settings from the docker container's environment (in prod) or defaulting into constants when in dev. The next function sets the db connection to work always in dev repl (so that I can call
(postmodern:query "SELECT 1,2,3*4")and it gets correctly ran in the dev pgsql). The next macro, with-db, bridges the previous config magic into postmodern's setup macro. The last function is a easy-route middleware that sets up the connection for the request, tries to handle it gracefully without killing the app if the db craps out unexpectedly, sets up the transaction and calls the inner request handler.
There is a self-written migration library that tries to mimic flyway. It's used this way, every migration file listed with defmigration gets to be ran in order (if not already ran).
This system is really bad. Don't do what I have done. Were I to rewrite this, I'd just reintroduce a jvm dependency and see if doing migrations with flyway's cli before starting the main app would be less moronic.
There's also a self-written contraption that tries to do whatever HugSQL does. The idea is that sql is stored in .sql files in repo, and this thing would read those files during compile and write lisp functions that, when called, ran those queries against the db and did some rudimentary parsing on whatever the database happened to return. This piece of code isn't too bad, biggest problems are that it's not exactly documented and sql-files that work in hugsql are not 1:1 compatible with halisql.lisp. If you find this idea interesting, and are somewhat familiar with postmodern, exploring /resources/sql should give you a clear...ish picture of how this is used.
Frontend
There are domain specific macros for html- and css generation. I tend to prefer serving my resources from the file system, though.
As I haven't yet found the way to convince easy-routes to parse "/:js-route.js" or "/:css-route.css" as real routes, I have used this kind of macro to generate handlers for each js found in /resources/js:
(defvar *js-resource-path* (pathname (format nil "~aresources/js/" (asdf:system-source-directory "linnarope-resource-handler")))) (defun list-all-js-resources () (cl-fad:list-directory *js-resource-path*)) (defun find-js (filename) (format t "Slurping ~a~%" filename) (let ((body (linnarope.middleware:js-resource filename))) (if body (progn (setf (hunchentoot:content-type*) "text/javascript") body) (progn (setf (hunchentoot:return-code*) 404) "")))) (defmacro js-routes () "Iterates all the *.js files in /resource_handler/resources/js/ and creates a defroute delegating the actual finding into find-js function. This macro is necessary due to \"/:filename.js\" being impossible to represent in the route matching syntax" (let ((resources (binding-arrows:->> (list-all-js-resources) (remove-if-not (lisp-fixup:compose (lisp-fixup:partial #'equalp "js") #'pathname-type))))) `(progn ,@(mapcar (lambda (resource) (let* ((fname (pathname-name resource)) (symbol (intern (format nil "~a-route" fname)))) `(defroute ,symbol (,(format nil "/~a.js" fname)) () (find-js ,(format nil "~a.js" fname))))) resources))))
Similar hack could be done by replacing js with css in that snippet :D