File size: 8,631 Bytes
036b3a6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# BYO Blog


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

<div>

> **Caution**
>
> This document is a work in progress.

</div>

In this tutorial we’re going to write a blog by example. Blogs are a
good way to learn a web framework as they start simple yet can get
surprisingly sophistated. The [wikipedia definition of a
blog](https://en.wikipedia.org/wiki/Blog) is “an informational website
consisting of discrete, often informal diary-style text entries (posts)
informal diary-style text entries (posts)”, which means we need to
provide these basic features:

- A list of articles
- A means to create/edit/delete the articles
- An attractive but accessible layout

We’ll also add in these features, so the blog can become a working site:

- RSS feed
- Pages independent of the list of articles (about and contact come to
  mind)
- Import and Export of articles
- Tagging and categorization of data
- Deployment
- Ability to scale for large volumes of readers

## How to best use this tutorial

We could copy/paste every code example in sequence and have a finished
blog at the end. However, it’s debatable how much we will learn through
the copy/paste method. We’re not saying its impossible to learn through
copy/paste, we’re just saying it’s not that of an efficient way to
learn. It’s analogous to learning how to play a musical instrument or
sport or video game by watching other people do it - you can learn some
but its not the same as doing.

A better approach is to type out every line of code in this tutorial.
This forces us to run the code through our brains, giving us actual
practice in how to write FastHTML and Pythoncode and forcing us to debug
our own mistakes. In some cases we’ll repeat similar tasks - a key
component in achieving mastery in anything. Coming back to the
instrument/sport/video game analogy, it’s exactly like actually
practicing an instrument, sport, or video game. Through practice and
repetition we eventually achieve mastery.

## Installing FastHTML

FastHTML is *just Python*. Installation is often done with pip:

``` shellscript
pip install python-fasthtml
```

## A minimal FastHTML app

First, create the directory for our project using Python’s
[pathlib](https://docs.python.org/3/library/pathlib.html) module:

``` python
import pathlib
pathlib.Path('blog-system').mkdir()
```

Now that we have our directory, let’s create a minimal FastHTML site in
it.

<div class="code-with-filename">

**blog-system/minimal.py**

``` python
from fasthtml.common import * 

app, rt = fast_app()  

@rt("/") 
def get():
    return Titled("FastHTML", P("Let's do this!")) 

serve()
```

</div>

Run that with `python minimal.py` and you should get something like
this:

``` shellscript
python minimal.py 
Link: http://localhost:5001
INFO:     Will watch for changes in these directories: ['/Users/pydanny/projects/blog-system']
INFO:     Uvicorn running on http://0.0.0.0:5001 (Press CTRL+C to quit)
INFO:     Started reloader process [46572] using WatchFiles
INFO:     Started server process [46576]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
```

Confirm FastHTML is running by opening your web browser to
[127.0.0.1:5001](http://127.0.0.1:5001). You should see something like
the image below:

![](quickstart-web-dev/quickstart-fasthtml.png)

<div>

> **What about the `import *`?**
>
> For those worried about the use of `import *` rather than a PEP8-style
> declared namespace, understand that `__all__` is defined in FastHTML’s
> common module. That means that only the symbols (functions, classes,
> and other things) the framework wants us to have will be brought into
> our own code via `import *`. Read [importing from a
> package](https://docs.python.org/3/tutorial/modules.html#importing-from-a-package))
> for more information.
>
> Nevertheless, if we want to use a defined namespace we can do so.
> Here’s an example:
>
> ``` python
> from fasthtml import common as fh
>
>
> app, rt = fh.fast_app()  
>
> @rt("/") 
> def get():
>     return fh.Titled("FastHTML", fh.P("Let's do this!")) 
>
> fh.serve()
> ```

</div>

## Looking more closely at our app

Let’s look more closely at our application. Every line is packed with
powerful features of FastHTML:

<div class="code-with-filename">

**blog-system/minimal.py**

``` python
from fasthtml.common import *

app, rt = fast_app()

@rt("/")
def get():
    return Titled("FastHTML", P("Let's do this!"))

serve()
```

</div>

Line 1  
The top level namespace of Fast HTML (fasthtml.common) contains
everything we need from FastHTML to build applications. A
carefully-curated set of FastHTML functions and other Python objects is
brought into our global namespace for convenience.

Line 3  
We instantiate a FastHTML app with the `fast_app()` utility function.
This provides a number of really useful defaults that we’ll modify or
take advantage of later in the tutorial.

Line 5  
We use the `rt()` decorator to tell FastHTML what to return when a user
visits `/` in their browser.

Line 6  
We connect this route to HTTP GET requests by defining a view function
called `get()`.

Line 7  
A tree of Python function calls that return all the HTML required to
write a properly formed web page. You’ll soon see the power of this
approach.

Line 9  
The [`serve()`](https://www.fastht.ml/docs/api/core.html#serve) utility
configures and runs FastHTML using a library called `uvicorn`. Any
changes to this module will be reloaded into the browser.

## Adding dynamic content to our minimal app

Our page is great, but we’ll make it better. Let’s add a randomized list
of letters to the page. Every time the page reloads, a new list of
varying length will be generated.

<div class="code-with-filename">

**blog-system/random_letters.py**

``` python
from fasthtml.common import *
import string, random

app, rt = fast_app()

@rt("/")
def get():
    letters = random.choices(string.ascii_uppercase, k=random.randint(5, 20))
    items = [Li(c) for c in letters]
    return Titled("Random lists of letters",
        Ul(*items)
    ) 

serve()
```

</div>

Line 2  
The `string` and `random` libraries are part of Python’s standard
library

Line 8  
We use these libraries to generate a random length list of random
letters called `letters`

Line 9  
Using `letters` as the base we use list comprehension to generate a list
of `Li` ft display components, each with their own letter and save that
to the variable `items`

Line 11  
Inside a call to the `Ul()` ft component we use Python’s `*args` special
syntax on the `items` variable. Therefore `*list` is treated not as one
argument but rather a set of them.

When this is run, it will generate something like this with a different
random list of letters for each page load:

![](web-dev-tut/random-list-letters.png)

## Storing the articles

The most basic component of a blog is a series of articles sorted by
date authored. Rather than a database we’re going to use our computer’s
harddrive to store a set of markdown files in a directory within our
blog called `posts`. First, let’s create the directory and some test
files we can use to search for:

``` python
from fastcore.utils import *
```

``` python
# Create some dummy posts
posts = Path("posts")
posts.mkdir(exist_ok=True)
for i in range(10): (posts/f"article_{i}.md").write_text(f"This is article {i}")
```

Searching for these files can be done with pathlib.

``` python
import pathlib
posts.ls()
```

    (#10) [Path('posts/article_5.md'),Path('posts/article_1.md'),Path('posts/article_0.md'),Path('posts/article_4.md'),Path('posts/article_3.md'),Path('posts/article_7.md'),Path('posts/article_6.md'),Path('posts/article_2.md'),Path('posts/article_9.md'),Path('posts/article_8.md')]

<div>

> **Tip**
>
> Python’s [pathlib](https://docs.python.org/3/library/pathlib.html)
> library is quite useful and makes file search and manipulation much
> easier. There’s many uses for it and is compatible across operating
> systems.

</div>

## Creating the blog home page

We now have enough tools that we can create the home page. Let’s create
a new Python file and write out our simple view to list the articles in
our blog.

<div class="code-with-filename">

**blog-system/main.py**

``` python
from fasthtml.common import *
import pathlib

app, rt = fast_app()

@rt("/")
def get():
    fnames = pathlib.Path("posts").rglob("*.md")
    items = [Li(A(fname, href=fname)) for fname in fnames]    
    return Titled("My Blog",
        Ul(*items)
    ) 

serve()
```

</div>

``` python
for p in posts.ls(): p.unlink()
```