Skip to content

Commit 699329b

Browse files
committed
tutorial! part 1
1 parent d7272fa commit 699329b

12 files changed

+1203
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/hugo
2+
*.fasl

content/tutorial/_index.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
+++
2+
title = "Tutorial part 1"
3+
weight = -1
4+
+++
5+
6+
rst
+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
+++
2+
title = "Part 1: the first URL parameter"
3+
weight = 100
4+
+++
5+
6+
As of now we can access URLs like these:
7+
[http://localhost:8899/product/0](http://localhost:8899/product/0)
8+
where 0 is a *path parameter*.
9+
10+
We will soon need *URL parameters* so let's add one to our routes.
11+
12+
We want a `debug` URL parameter that will show us more data. The URL
13+
will accept a `?debug=t` part.
14+
15+
16+
We have this product route:
17+
18+
```lisp
19+
(easy-routes:defroute product-route ("/product/:n") (&path (n 'integer))
20+
(render *template-product* :product (get-product n)))
21+
```
22+
23+
With or without the `&path` part, either is good for now, so let's remove it for clarity:
24+
25+
```lisp
26+
(easy-routes:defroute product-route ("/product/:n") ()
27+
(render *template-product* :product (get-product n)))
28+
```
29+
30+
We removed it because it's as the same place that we will define URL
31+
parameters. Let's add one.
32+
33+
## GET and POST parameters
34+
35+
Our route with a `debug` URL parameter becomes:
36+
37+
```lisp
38+
(easy-routes:defroute product-route ("/product/:n") (&get debug)
39+
(render *template-product* :product (get-product n)))
40+
```
41+
42+
The `&get` is here to say this parameter is accepted only in GET
43+
requests. You can have `&post`, and you can leave aside the `&get` or
44+
`&post` for parameters that are always accepted.
45+
46+
Let's not parse the value of the `debug` variable: if it's present,
47+
it's truthy, and we should display debug information.
48+
49+
Let's add logic to the template.
50+
51+
The `if` tag of Djula is of the form: `{% if %} … {% else %} … {% endif %}`.
52+
53+
So:
54+
55+
```lisp
56+
{% if debug %} debug info! {% endif %}
57+
```
58+
59+
And add arguments to the `render` function:
60+
61+
```lisp
62+
(render *template-product*
63+
:product (get-product n)
64+
:debug debug)
65+
```
66+
67+
Go to
68+
[http://localhost:8899/product/0?debug=t](http://localhost:8899/product/0?debug=t)
69+
and you should see
70+
71+
(0 Product nb 0 9.99) debug info!
72+
73+
Can we do something useful? In my apps, printing debug info and the
74+
output of `describe` for some objects turned useful (grab this output
75+
with `with-output-to-string`).
76+
77+
## Full code
78+
79+
Our app looks like this:
80+
81+
```lisp
82+
83+
(in-package :myproject)
84+
85+
(defvar *server* nil
86+
"Server instance (Hunchentoot acceptor).")
87+
88+
(defparameter *port* 8899 "The application port.")
89+
90+
(defparameter *template-root* "
91+
<title> Lisp web app </title>
92+
<body>
93+
<ul>
94+
{% for product in products %}
95+
<li>
96+
<a href=\"/product/{{ product.0 }}\">{{ product.1 }} - {{ product.2 }}</a>
97+
</li>
98+
{% endfor %}
99+
</ul>
100+
</body>
101+
")
102+
103+
(defparameter *template-product* "
104+
<body>
105+
{{ product }}
106+
107+
{% if debug %} debug info! {% endif %}
108+
</body>
109+
")
110+
111+
(defun products (&optional (n 5))
112+
(loop for i from 0 below n
113+
collect (list i
114+
(format nil "Product nb ~a" i)
115+
9.99)))
116+
117+
(defun get-product (n)
118+
(list n (format nil "Product nb ~a" n) 9.99))
119+
120+
(defun render (template &rest args)
121+
(apply
122+
#'djula:render-template*
123+
(djula:compile-string template)
124+
nil
125+
args))
126+
127+
(easy-routes:defroute root ("/") ()
128+
(render *template-root* :products (products)))
129+
130+
(easy-routes:defroute product-route ("/product/:n") (&get debug &path (n 'integer))
131+
(render *template-product*
132+
:product (get-product n)
133+
:debug debug))
134+
135+
136+
(defun start-server (&key (port *port*))
137+
(format t "~&Starting the web server on port ~a" port)
138+
(force-output)
139+
(setf *server* (make-instance 'easy-routes:easy-routes-acceptor
140+
:port (or port *port*)))
141+
(hunchentoot:start *server*))
142+
```
143+
144+
Everything is contained in one file (don't forget the .asd), and we
145+
can run everything from sources or we can build a self-contained
146+
binary. Pretty cool!
147+
148+
Before we do so, we'll add a great feature: searching for products.

content/tutorial/first-build.md

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
+++
2+
title = "Part 1: the first build"
3+
weight = 300
4+
+++
5+
6+
We have developped a web app in Common Lisp.
7+
8+
At the first step, we opened a REPL and since then, without noticing,
9+
we have compiled variables and function definitions pieces by pieces,
10+
step by step, with keyboard shortcuts giving immediate feedback,
11+
testing our progress on the go, running a function in the REPL or
12+
refreshing the browser window.
13+
14+
{{% notice info %}}
15+
16+
We didn't have to restart any Lisp process, nor any web server.
17+
18+
Think about it, that's awesome!
19+
20+
{{% /notice %}}
21+
22+
However, it *is* useful to start our application from scratch once in a while:
23+
24+
- did we list all our dependencies in the .asd project definition?
25+
- does it work from scratch, do we have any issue with new data?
26+
27+
We can run our app from sources, and we can build a self-contained binary.
28+
29+
## Run from sources

0 commit comments

Comments
 (0)