nmunro.github.io

Common Lisp and other programming related things from NMunro

View on GitHub
29 June 2025

Ningle Tutorial 8: Mounting Middleware

by NMunro

Contents

Introduction

Welcome back to this Ningle tutorial series, in this part we are gonna have another look at some middleware, now that we have settings and configuration done there’s another piece of middleware we might want to look at; application mounting, many web frameworks have the means to use apps within other apps, you might want to do this because you have some functionality you use over and over again in many projects, it makes sense to make it into an app and simply include it in other apps. You might also might want to make applications available for others to use in their applications.

Which is exactly what we are gonna do here, we spent some time building a registration view, but for users we might want to have a full registration system that will have:

Creating the auth app

We will begin by building the basic views that return a simple template and mount them into our main application, we will then fill the actual logic out in another tutorial. So, we will create a new Ningle project that has 6 views that simply handle get requests, the important thing to bear in mind is that we will have to adjust the layout of our templates, we need our auth app to use its own templates, or use the templates of a parent app, this means we will have to namespace our templates, if you have use django before this will seem familiar.

Using my project builder set up a new project for our authentication application.

    (nmunro:make-project #p"~/quicklisp/local-projects/ningle-auth/")

This will create a project skeleton, complete with an asd file, a src, and tests directory. In the asd file we need to add some packages (we will add more in a later tutorial).

  :depends-on (:cl-dotenv
               :clack
               :djula
               :envy-ningle
               :mito
               :ningle)

In the src/main.lisp file, we will add the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
(defpackage ningle-auth
  (:use :cl)
  (:export #:*app*
           #:start
           #:stop))

(in-package ningle-auth)

(defvar *app* (make-instance 'ningle:app))

(djula:add-template-directory (asdf:system-relative-pathname :ningle-auth "src/templates/"))

(setf (ningle:route *app* "/register")
    (lambda (params)
        (format t "Test: ~A~%" (mito:retrieve-by-sql "SELECT 2 + 3 AS result"))
        (djula:render-template* "auth/register.html" nil :title "Register")))

(setf (ningle:route *app* "/login")
    (lambda (params)
        (djula:render-template* "auth/login.html" nil :title "Login")))

(setf (ningle:route *app* "/logout")
    (lambda (params)
        (djula:render-template* "auth/logout.html" nil :title "Logout")))

(setf (ningle:route *app* "/reset")
    (lambda (params)
        (djula:render-template* "auth/reset.html" nil :title "Reset")))

(setf (ningle:route *app* "/verify")
    (lambda (params)
        (djula:render-template* "auth/verify.html" nil :title "Verify")))

(setf (ningle:route *app* "/delete")
    (lambda (params)
        (djula:render-template* "auth/delete.html" nil :title "Delete")))

(defmethod ningle:not-found ((app ningle:<app>))
    (declare (ignore app))
    (setf (lack.response:response-status ningle:*response*) 404)
    (djula:render-template* "error.html" nil :title "Error" :error "Not Found"))

(defun start (&key (server :woo) (address "127.0.0.1") (port 8000))
    (djula:add-template-directory (asdf:system-relative-pathname :ningle-auth "src/templates/"))
    (djula:set-static-url "/public/")
    (clack:clackup
     (lack.builder:builder (envy-ningle:build-middleware :ningle-auth/config *app*))
     :server server
     :address address
     :port port))

(defun stop (instance)
    (clack:stop instance))

Just as we did with our main application, we will need to create a src/config.lisp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(defpackage ningle-auth/config
  (:use :cl :envy))

(in-package ningle-auth/config)

(dotenv:load-env (asdf:system-relative-pathname :ningle-auth ".env"))
(setf (config-env-var) "APP_ENV")

(defconfig :common
  `(:application-root ,(asdf:component-pathname (asdf:find-system :ningle-auth))))

(defconfig |test|
  `(:debug T
    :middleware ((:session)
                 (:mito (:sqlite3 :database-name ,(uiop:getenv "SQLITE_DB_NAME"))))))

Now, I mentioned that the template files need to be organised in a certain way, we will start with the new template layout in our auth application, the directory structure should look like this:

➜  ningle-auth git:(main) tree .
.
├── ningle-auth.asd
├── README.md
├── src
│   ├── config.lisp
│   ├── main.lisp
│   └── templates
│       ├── ningle-auth
│       │   ├── delete.html
│       │   ├── login.html
│       │   ├── logout.html
│       │   ├── register.html
│       │   ├── reset.html
│       │   └── verify.html
│       ├── base.html
│       └── error.html
└── tests
    └── main.lisp

So in your src/templates directory there will be a directory called ningle-auth and two files base.html and error.html, it is important that this structure is followed, as when the app is used as part of a larger app, we want to be able to layer templates, and this is how we do it.

base.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!doctype html>
<html lang="en">
    <head>
        <title>{{ title }}</title>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    </head>
    <body>
        <div class="container mt-4">
            {% block content %}
            {% endblock %}
        </div>
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
    </body>
</html>

error.html

1
2
3
4
5
6
7
8
9
10
11
{% extends "base.html" %}

{% block content %}
<div class="container">
    <div class="row">
        <div class="col-12">
          <h1>{{ error }}</h1>
        </div>
    </div>
</div>
{% endblock %}

Now the rest of the html files are similar, with only the title changing. Using the following html, create files for:

delete.html

1
2
3
4
5
6
7
8
9
10
11
{% extends "base.html" %}

{% block content %}
<div class="container">
    <div class="row">
        <div class="col-12">
            <h1>Delete</h1>
        </div>
    </div>
</div>
{% endblock %}

login.html

1
2
3
4
5
6
7
8
9
10
11
{% extends "base.html" %}

{% block content %}
<div class="container">
    <div class="row">
        <div class="col-12">
            <h1>Login</h1>
        </div>
    </div>
</div>
{% endblock %}

logout.html

1
2
3
4
5
6
7
8
9
10
11
{% extends "base.html" %}

{% block content %}
<div class="container">
    <div class="row">
        <div class="col-12">
            <h1>Logout</h1>
        </div>
    </div>
</div>
{% endblock %}

register.html

1
2
3
4
5
6
7
8
9
10
11
{% extends "base.html" %}

{% block content %}
<div class="container">
    <div class="row">
        <div class="col-12">
            <h1>Register</h1>
        </div>
    </div>
</div>
{% endblock %}

reset.html

1
2
3
4
5
6
7
8
9
10
11
{% extends "base.html" %}

{% block content %}
<div class="container">
    <div class="row">
        <div class="col-12">
            <h1>Reset</h1>
        </div>
    </div>
</div>
{% endblock %}

verify.html

1
2
3
4
5
6
7
8
9
10
11
{% extends "base.html" %}

{% block content %}
<div class="container">
    <div class="row">
        <div class="col-12">
            <h1>Verify</h1>
        </div>
    </div>
</div>
{% endblock %}

There is one final file to create, the .env file! Even though this application wont typically run on its own, we will use one to test it is all working, since we did write src/config.lisp afterall!

1
2
APP_ENV=test
SQLITE_DB_NAME=ningle-auth.db

Testing the auth app

Now that the auth application has been created we will test that it at least runs on its own, once we have confirmed this, we can integrate it into our main app. Like with our main application, we will load the system and run the start function that we defined.

(ql:quickload :ningle-auth)
To load "ningle-auth":
  Load 1 ASDF system:
    ningle-auth
; Loading "ningle-auth"
..................................................
[package ningle-auth/config].
(:NINGLE-AUTH)
(ningle-auth:start)
NOTICE: Running in debug mode. Debugger will be invoked on errors.
  Specify ':debug nil' to turn it off on remote environments.
Woo server is started.
Listening on 127.0.0.1:8000.
#S(CLACK.HANDLER::HANDLER
   :SERVER :WOO
   :SWANK-PORT NIL
   :ACCEPTOR #<BT2:THREAD "clack-handler-woo" {1203E4E3E3}>)
*

If this works correctly, you should be able to access the defined routes in your web browser, if not, and there is an error, check that another web server isn’t running on port 8000 first! When you are able to access the simple routes from your web browser, we are ready to integrate this into our main application!

Integrating the auth app

Made it this far? Congratulations, we are almost at the end, I’m sure you’ll be glad to know, there isn’t all that much more to do, but we do have to ensure we follow the structure we set up in the auth app, which we will get to in just a moment, first, lets remember to add the ningle-auth app to our dependencies in our project asd file.

:depends-on (:cl-dotenv
               :clack
               :djula
               :cl-forms
               :cl-forms.djula
               :cl-forms.ningle
               :envy
               :envy-ningle
               :ingle
               :mito
               :mito-auth
               :ningle
               :ningle-auth) ;; add this

Next, we need to move most of our template files into a directory called main, to make things easy, the only two templates we will not move are base.html and error.html; create a new directory src/templates/main and put everything else in there.

For reference this is what your directory structure should look like:

➜  ningle-tutorial-project git:(main) tree .
.
├── ningle-tutorial-project.asd
├── ntp.db
├── README.md
├── src
│   ├── config.lisp
│   ├── forms.lisp
│   ├── main.lisp
│   ├── migrations.lisp
│   ├── models.lisp
│   ├── static
│   │   ├── css
│   │   │   └── main.css
│   │   └── images
│   │       ├── logo.jpg
│   │       └── lua.jpg
│   └── templates
│       ├── base.html
│       ├── error.html
│       └── main
│           ├── index.html
│           ├── login.html
│           ├── logout.html
│           ├── people.html
│           ├── person.html
│           └── register.html
└── tests
    └── main.lisp

With the templates having been moved, we must find all areas in src/main.lisp where we reference one of these templates and point to the new location, thankfully there’s only 4 lines that need to be changed, the render-template* calls, below is what they should be changed to.

(djula:render-template* "main/index.html" nil :title "Home" :user user :posts posts)
(djula:render-template* "main/people.html" nil :title "People" :users users)
(djula:render-template* "main/person.html" nil :title "Person" :user user)
(djula:render-template* "main/register.html" nil :title "Register" :form form)

Here is a complete listing of the file in question.

(defpackage ningle-tutorial-project
  (:use :cl :sxql)
  (:import-from
   :ningle-tutorial-project/forms
   #:email
   #:username
   #:password
   #:password-verify
   #:register)
  (:export #:start
           #:stop))

(in-package ningle-tutorial-project)

(defvar *app* (make-instance 'ningle:app))

(setf (ningle:route *app* "/")
      (lambda (params)
        (let ((user  (list :username "NMunro"))
              (posts (list (list :author (list :username "Bob")  :content "Experimenting with Dylan" :created-at "2025-01-24 @ 13:34")
                           (list :author (list :username "Jane") :content "Wrote in my diary today"  :created-at "2025-01-24 @ 13:23"))))
          (djula:render-template* "main/index.html" nil :title "Home" :user user :posts posts))))

(setf (ningle:route *app* "/people")
      (lambda (params)
        (let ((users (mito:retrieve-dao 'ningle-tutorial-project/models:user)))
          (djula:render-template* "main/people.html" nil :title "People" :users users))))

(setf (ningle:route *app* "/people/:person")
      (lambda (params)
        (let* ((person (ingle:get-param :person params))
               (user (first (mito:select-dao
                              'ningle-tutorial-project/models:user
                              (where (:or (:= :username person)
                                          (:= :email person)))))))
          (djula:render-template* "main/person.html" nil :title "Person" :user user))))

(setf (ningle:route *app* "/register" :method '(:GET :POST))
    (lambda (params)
        (let ((form (cl-forms:find-form 'register)))
          (if (string= "GET" (lack.request:request-method ningle:*request*))
            (djula:render-template* "main/register.html" nil :title "Register" :form form)
            (handler-case
                (progn
                    (cl-forms:handle-request form) ; Can throw an error if CSRF fails
                    (multiple-value-bind (valid errors)
                        (cl-forms:validate-form form)

                      (when errors
                        (format t "Errors: ~A~%" errors))

                      (when valid
                        (cl-forms:with-form-field-values (email username password password-verify) form
                          (when (mito:select-dao 'ningle-tutorial-project/models:user
                                 (where (:or (:= :username username)
                                             (:= :email email))))
                            (error "Either username or email is already registered"))

                          (when (string/= password password-verify)
                            (error "Passwords do not match"))

                          (mito:create-dao 'ningle-tutorial-project/models:user
                                           :email email
                                           :username username
                                           :password password)
                          (ingle:redirect "/people")))))

                (error (err)
                    (djula:render-template* "error.html" nil :title "Error" :error err))

                (simple-error (csrf-error)
                    (setf (lack.response:response-status ningle:*response*) 403)
                    (djula:render-template* "error.html" nil :title "Error" :error csrf-error)))))))

(defmethod ningle:not-found ((app ningle:<app>))
    (declare (ignore app))
    (setf (lack.response:response-status ningle:*response*) 404)
    (djula:render-template* "error.html" nil :title "Error" :error "Not Found"))

(defun start (&key (server :woo) (address "127.0.0.1") (port 8000))
    (djula:add-template-directory (asdf:system-relative-pathname :ningle-tutorial-project "src/templates/"))
    (djula:set-static-url "/public/")
    (clack:clackup
     (lack.builder:builder (envy-ningle:build-middleware :ningle-tutorial-project/config *app*))
     :server server
     :address address
     :port port))

(defun stop (instance)
    (clack:stop instance))

The final step we must complete is actually mounting our ningle-auth application into our main app, which is thankfully quite easy. Mounting middleware exists for ningle and so we can configure this in src/config.lisp, to demonstrate this we will add it to our sqlite config:

1
2
3
4
5
6
(defconfig |sqlite|
  `(:debug T
    :middleware ((:session)
                 (:mito (:sqlite3 :database-name ,(uiop:getenv "SQLITE_DB_NAME")))
                 (:mount "/auth" ,ningle-auth:*app*) ;; This line!
                 (:static :root ,(asdf:system-relative-pathname :ningle-tutorial-project "src/static/") :path "/public/"))))

You can see on line #5 that a new mount point is being defined, we are mounting all the routes that ningle-auth has, onto the /auth prefix. This means that, for example, the /register route in ningle-auth will actually be accessed /auth/register.

If you can check that you can access all the urls to confirm this works, then we have assurances that we are set up correctly, however we need to come back to the templates one last time.

The reason we changed the directory structure, because ningle-auth is now running in the context of our main app, we can actually override the templates, so if we wanted to, in our src/templates directory, we could create a ningle-auth directory and create our own register.html, login.html, etc, allowing us to style and develop our pages as we see fit, allowing complete control to override, if that is our wish. By NOT moving the base.html and error.html files, we ensure that templates from another app can inherit our styles and layouts in a simple and predictable manner.

Conclusion

Wow, what a ride… Thanks for sticking with it this month, although, next month isn’t going to be much easier as we begin to develop a real authentication application for use in our microblog app! As always, I hope you have found this helpful and you have learned something.

In this tutorial you should be able to:

Github

Resources

tags: CommonLisp - Lisp - tutorial - YouTube - web - dev