Skip to content

Commit 1db947c

Browse files
committed
tutorial! part 1 ++
1 parent faf1f80 commit 1db947c

File tree

12 files changed

+119
-93
lines changed

12 files changed

+119
-93
lines changed

content/tutorial/_index.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,33 @@ title = "Tutorial part 1"
33
weight = -1
44
+++
55

6-
rst
6+
7+
Are you ready to build a web app in Common Lisp? Welcome.
8+
9+
In this first tutorial we will build a simple app that shows a web
10+
form that will search and display a list of products.
11+
12+
![](web-app.png?lightbox=false&shadow=true)
13+
14+
15+
In doing so, we will see many necessary building blocks to write web apps in Lisp:
16+
17+
- how to start a server
18+
- how to create routes
19+
- how to define and use path and URL parameters
20+
- how to define HTML templates
21+
- how to run and build the app, from our editor and from the terminal.
22+
23+
In doing so, we'll experience the interactive nature of Common Lisp
24+
and we'll learn important commands: running `sbcl` from the command
25+
line with a couple options, what is `load`, how to interactively
26+
compile our app with a keyboard shortcut, how to structure a project
27+
with an .asd definition and a package, etc.
28+
29+
We expect that you have Quicklisp installed. If not, please see the [Cookbook: getting started](https://lispcookbook.github.io/cl-cookbook/getting-started.html).
30+
31+
Now let's go.
32+
33+
But beware. There is no going back.
34+
35+
*(look at the menu and don't miss the small previous-next arrows on the top right)*

content/tutorial/first-URL-parameters.md

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
+++
2-
title = "Part 1: the first URL parameter"
2+
title = "the first URL parameter"
33
weight = 100
44
+++
55

@@ -56,7 +56,7 @@ So:
5656
{% if debug %} debug info! {% endif %}
5757
```
5858

59-
And add arguments to the `render` function:
59+
And pass the variable to the `render` function:
6060

6161
```lisp
6262
(render *template-product*
@@ -108,15 +108,13 @@ Our app looks like this:
108108
</body>
109109
")
110110
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-
117111
(defun get-product (n)
118112
(list n (format nil "Product nb ~a" n) 9.99))
119113
114+
(defun products (&optional (n 5))
115+
(loop for i from 0 below n
116+
collect (get-product i)))
117+
120118
(defun render (template &rest args)
121119
(apply
122120
#'djula:render-template*
@@ -137,11 +135,11 @@ Our app looks like this:
137135
(format t "~&Starting the web server on port ~a" port)
138136
(force-output)
139137
(setf *server* (make-instance 'easy-routes:easy-routes-acceptor
140-
:port (or port *port*)))
138+
:port port))
141139
(hunchentoot:start *server*))
142140
```
143141

144-
Everything is contained in one file (don't forget the .asd), and we
142+
Everything is contained in one file, and we
145143
can run everything from sources or we can build a self-contained
146144
binary. Pretty cool!
147145

content/tutorial/first-bonus-CSS.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
+++
22
title = "Bonus: pimp your CSS"
3-
weight = 1000
3+
weight = 250
44
+++
55

66
## Bonus: pimp your CSS
@@ -54,8 +54,15 @@ So, for instance:
5454
")
5555
```
5656

57-
Note how our root template is benefiting from the CSS, and not the
58-
product page. The two pages should inherit from a base template. It's
59-
about time we setup our templates in their own directory.
60-
6157
Refresh [http://localhost:8899/?query=one](http://localhost:8899/?query=one). Do you enjoy the difference?!
58+
59+
I see this:
60+
61+
![](/tutorial/web-app.png?lightbox=false&shadow=true)
62+
63+
 
64+
65+
However note how our root template is benefiting from the CSS, and the
66+
product page isn't. The two pages should inherit from a base
67+
template. It's about time we setup our templates in their own
68+
directory.

content/tutorial/first-build.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
+++
2-
title = "Part 1: the first build"
2+
title = "the first build"
33
weight = 300
44
+++
55

@@ -83,7 +83,8 @@ To load "myproject":
8383
myproject
8484
; Loading "myproject"
8585
..............
86-
Starting the web server on port 8899 While evaluating the form starting at line 5, column 0
86+
Starting the web server on port 8899
87+
While evaluating the form starting at line 5, column 0
8788
of #P"/home/vince/projets/web-apps-in-lisp/walk/walk-book/content/tutorial/run.lisp":
8889
8990
debugger invoked on a USOCKET:ADDRESS-IN-USE-ERROR in thread
@@ -141,7 +142,7 @@ To use another port, what would you prefer?
141142
- we can give the port as an argument on the command line, like `--port 9000`.
142143
- we can find the next available port.
143144

144-
Let's start with the latter.
145+
Let's do the latter.
145146

146147

147148
## Find a port number
@@ -174,13 +175,11 @@ Our .asd file now looks like:
174175
:djula ;; HTML templates
175176
176177
;; utils
177-
:str ;; strings library
178178
:find-port ;; <------- added
179179
)
180180
:components ((:module "src" ;; a src/ subdirectory
181181
:components
182182
(
183-
(:file "packages") ;; = src/packages.lisp
184183
(:file "myproject") ;; = src/myproject.lisp
185184
)))
186185
@@ -313,8 +312,9 @@ But wait, do we *really* want to develop our app from this limited
313312
terminal REPL? No! If you don't already, it's time you understand the
314313
usefulness of the `load` function.
315314

316-
What we want is to edit our .lisp source file, instead of the REPL,
317-
and then to reload the app. The reloading is done with
315+
What we want is to edit our .lisp source file, instead of copy-pasting
316+
stuff in the REPL, and then to reload the app. The reloading is done
317+
with
318318

319319
* (load "src/myproject.lisp")
320320

@@ -356,7 +356,7 @@ compression, they get to ±20MB. As your application grows, they'll
356356
stay roughly this size. An app of mine, with dozens of dependencies
357357
and all the application code, templates and static assets (JS and CSS)
358358
is 35MB. LispWorks binaries, reduced in size with their tree shaker,
359-
are know to be smaller, a hello world being ±5MB, a web app around
359+
are known to be smaller, a hello world being ±5MB, a web app around
360360
10MB. This tree shaker isn't in the free version.
361361

362362
Enough talk, let's do it. Create a new `build.lisp` file. We need these steps:
@@ -528,7 +528,7 @@ app. Congrats!
528528

529529
## Closing words
530530

531-
We are only scratching the surface of what we can do with a real app:
531+
We are only scratching the surface of what we'll want to do with a real app:
532532

533533
- parse CLI args
534534
- handle a `C-c` and other signals

content/tutorial/first-form.md

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
+++
2-
title = "Part 1: the first form"
2+
title = "the first form"
33
weight = 200
44
+++
55

@@ -41,8 +41,8 @@ As a consequence, your route should have a parameter named `query`.
4141
{{% notice info %}}
4242

4343
It's best to declare your parameters, but rest assured that
44-
Hunchentoot allows you to get the value of any URL parameter with the
45-
function `hunchentoot:parameter`.
44+
Hunchentoot allows you to get the value of any URL parameter of the
45+
current web request with the function `hunchentoot:parameter`.
4646

4747
{{% /notice %}}
4848

@@ -102,14 +102,12 @@ Our products are named "product nb …" and we will search for the
102102
We can make things a lil' bit more interesting with this small change:
103103

104104
```lisp
105-
(defun products (&optional (n 5))
106-
(loop for i from 0 below n
107-
collect (list i
108-
(format nil "Product nb ~r" i)
109-
9.99)))
105+
106+
(defun get-product (n)
107+
(list n (format nil "Product nb ~r" n) 9.99))
110108
```
111109

112-
Did you notice? `format … "~r"`, for the Radix directive. It prints numbers in english.
110+
Did you notice? `format … "~r"` instead of `"~a"`, for the Radix directive. It prints numbers in english.
113111

114112
```lisp
115113
MYPROJECT> (products)
@@ -189,7 +187,9 @@ I did this for the template:
189187
")
190188
```
191189

192-
And this for the route:
190+
I used a `results` variable, which is a list of product objects.
191+
192+
I did this for the route:
193193

194194
```lisp
195195
(easy-routes:defroute root ("/") (query)
@@ -272,14 +272,12 @@ Our app now look like this:
272272
args))
273273
274274
;;; Models.
275+
(defun get-product (n)
276+
(list n (format nil "Product nb ~r" n) 9.99))
277+
275278
(defun products (&optional (n 5))
276279
(loop for i from 0 below n
277-
collect (list i
278-
(format nil "Product nb ~r" i)
279-
9.99)))
280-
281-
(defun get-product (n)
282-
(list n (format nil "Product nb ~a" n) 9.99))
280+
collect (get-product i)))
283281
284282
(defun search-products (products query)
285283
(loop for product in products

content/tutorial/first-path-parameter.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
+++
2-
title = "Part 1: the first path parameter"
2+
title = "the first path parameter"
33
weight = 20
44
+++
55

@@ -137,7 +137,7 @@ Here are mine:
137137

138138
yeah I'm just printing the product, as a list, very simply.
139139

140-
I added a `get-product (n)` function helper.
140+
I added a `get-product (n)` helper function. You should edit `products` to use it too.
141141

142142
However I don't like the copy-pasting between `render-product` and `render-products` so I'll fix it. Can you too?
143143

content/tutorial/first-route.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
+++
2-
title = "Part 1: the first route"
2+
title = "the first route"
33
weight = -1
44
+++
55

@@ -32,7 +32,7 @@ It's time to start our web server:
3232

3333
```lisp
3434
(defun start-server (&key (port *port*))
35-
(format t "~&Starting the web server on port ~a" port)
35+
(format t "~&Starting the web server on port ~a~&" port)
3636
(force-output)
3737
(setf *server* (make-instance 'easy-routes:easy-routes-acceptor
3838
:port port))
@@ -50,7 +50,7 @@ the running Lisp image.
5050
Now call the function `(myproject::start-server)` or simply
5151
`(start-server)` if you did the `(in-package :myproject`) in the REPL.
5252

53-
Yourather instantly) should see:
53+
You should see:
5454

5555
```
5656
CL-USER> (myproject::start-server )
@@ -75,4 +75,11 @@ By the way, did you notice that while developping, you didn't have to stop the
7575
app, to stop the web server, nor to reload anything? Any changes
7676
compiled in the running image are immediately available.
7777

78+
Did you wait for something? You didn't, and I promise that as the
79+
applications grows, with this interactive development style, you will
80+
never have to wait for stuff re-compiling or re-loading. You'll
81+
compile your code with a shortcut and have instant feedback. SBCL
82+
warns us on bad syntax, undefined variables and other typos, some type
83+
mismatches, unreachable code (meaning we might have an issue), etc.
84+
7885
To stop the app, use `(hunchentoot:stop *server*)`. You can put this in a "stop-app" function.

content/tutorial/first-template.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
+++
2-
title = "Part 1: the first template"
2+
title = "the first template"
33
weight = -1
44
+++
55

@@ -88,7 +88,7 @@ simply use `:key` arguments, like this:
8888
(djula:render-template*
8989
(djula:compile-string *template-root*)
9090
nil
91-
:products (products))
91+
:products (products)) ;; <----- added
9292
```
9393

9494
you can compile the route again and refresh the page at [localhost:8899/](localhost:8899/).
@@ -114,16 +114,16 @@ We have a very cool route:
114114
(djula:render-template*
115115
(djula:compile-string *template-root*)
116116
nil
117-
:products (products))) ;; <----- added
117+
:products (products)))
118118
```
119119

120120
To test it and see its output, we had to re-compile it (OK), and
121-
refresh our browser. ARGH! We can do better. It may not look necessary
121+
refresh our browser. ARGH! We can do better. It may not look obvious
122122
now, but we are already writing business logic inside a web route. We
123123
should extract as much logic as possible from the route. It will make
124-
everything so much easier to test in the long run.
124+
everything so much easier to write and test in the long run.
125125

126-
My point here is that we have one business rule: rendering
126+
My point here is that we have one application function: rendering
127127
products. We can have a function for this:
128128

129129
```lisp
@@ -137,7 +137,7 @@ products. We can have a function for this:
137137
(render-products))
138138
```
139139

140-
The great benefit is that you can run `(render-products)` by itself (and
140+
The benefit is that you can run `(render-products)` by itself (and
141141
very quickly with `C-c C-y` `M-x slime-call-defun`) to test it in the
142142
REPL, and see the HTML output.
143143

0 commit comments

Comments
 (0)