back to index

Getting started with FastHTML


Whisper Transcript | Transcript Only Page

00:00:00.800 | Hi, this is Jeremy Howard from Answer.ai, coming to you here from Queensland, Australia.
00:00:06.160 | And I want to give you a gentle introduction to FastHTML. This won't be a super zippy
00:00:15.120 | get-it-done-fast thing, but I want to just kind of go through things a little bit
00:00:18.720 | gently and slowly and carefully, and show you around to help you get started.
00:00:26.640 | It will probably help if you've done a little bit of web programming before,
00:00:31.120 | but I'll try to introduce everything as we go to make it as accessible as possible.
00:00:36.800 | Okay, let's get started. So this is the FastHTML homepage. I imagine you've seen this already,
00:00:45.600 | given you found this video. And on the homepage, you can see some quick examples of
00:00:52.080 | what FastHTML looks like. So here's some code on the left. This is pretty short. And on the right
00:00:58.240 | is what it renders. In this case, it's all being done by a Python component called Card3D.
00:01:05.120 | And so these kinds of components, you can wrap them up, they can contain styles, they can contain
00:01:14.400 | JavaScript behavior and so forth, or they can be combined out of other Python components.
00:01:20.160 | FastHTML websites are dynamic, they're running on a server, but they are interacting with the
00:01:26.560 | browser via JavaScript automatically on the whole. So these are the actual temperatures in America
00:01:32.560 | right now, as I talked to you. They're coming from an API function here. And so we actually
00:01:37.760 | have dynamically updated weather on our homepage. And as you can see, it doesn't take very much to
00:01:44.640 | do that in terms of the code for it, assuming you've got the weather API hooked up, of course.
00:01:50.160 | The kinds of components that you can create can be kind of server-side data-driven APIs like that,
00:01:57.120 | or they can be kind of GUI-type components like this. So this is like an example of an accordion.
00:02:02.640 | Again, it's written in Python, it's accessible from Python. And so not the most exciting looking
00:02:10.160 | thing, but this is actually, again, a dynamically generated in real time on the homepage. This is
00:02:14.480 | the actual code for it coming from a database. As you can see, it's as simple as saying db.create,
00:02:22.560 | and you can define it in basically one data class, or it's not even a data class, just one regular
00:02:28.720 | Python class to say what the data is. And then to convert a table into an unordered list, you just
00:02:36.320 | say unordered list, and here's the table. So as you can see, this kind of thing really is very
00:02:42.960 | easy to get started with. One good way to get started is to look at lots of examples of people
00:02:49.600 | writing stuff. So we've got four examples here on our homepage, so you can check these out as well.
00:02:54.400 | But today we're going to get started on our own. There is a lot to learn when it comes to like
00:03:01.280 | learning to do web programming. There's a lot less to learn with fastHTML than with
00:03:05.600 | most current approaches. But you can't expect to just jump in and start coding right away without
00:03:12.000 | knowing any basics. So to help you learn the basics, we have this learn more here, which takes
00:03:18.640 | you to this mini site, which talks about the kind of basic principles of fastHTML, all the different
00:03:29.440 | technologies that it's built on, you know, the component, the foundations, the technology,
00:03:37.280 | how components work. And so this is a good place if you want to understand more of the concepts of
00:03:42.640 | how this is all put together. It was interesting when I built this page is I kind of thought,
00:03:49.760 | like, hmm, how am I going to do this? It would be nice to build it with fastHTML. I wonder what
00:03:53.600 | that would look like. And it actually worked out really nicely. I had it basically written in
00:03:57.600 | a couple of hours. So here's the actual source code for that page. As you can see, I've just got
00:04:07.120 | markdown, and I just created this little markdown wrapper here. So this is basically the code other
00:04:14.240 | than the prose to generate that page. And so you might be thinking, okay, well, how does markdown
00:04:20.960 | work? Well, the nice thing is, because we're using Python here, the markdown function I wrote
00:04:25.920 | simply calls something that I found on PyPy, which is the Python markdown library, and wraps it up
00:04:34.000 | in a div. So, yeah, as you can see, it's kind of like, you can create all kinds of different types
00:04:41.360 | of applications and pages and sites in fastHTML without necessarily needing, like, special purpose
00:04:49.200 | blogging systems or whatever. Maybe I'll show you one more example of something I wrote the other
00:04:57.360 | day, which I quite like, is I find it really helpful often to have a markdown version of
00:05:05.040 | documentation, so I can kind of provide it to a language model, for example, if I'm using Claude
00:05:10.160 | or ChatGPT. So often I find I want to, well, let's look at an example. So if we go back to that
00:05:18.160 | about page, let's say we wanted to include this as context in a, for a ChatGPT or Claude
00:05:26.720 | conversation chat. You wanted a markdown, so if I wrote this little thing, so I can copy the URL,
00:05:34.240 | paste it here, say load, and you can see here it is loaded up. And it's kind of cool, because,
00:05:45.200 | like, I want to, for example, get rid of the navigation, and so you can see here I can, like,
00:05:54.080 | grab, okay, that's the whole div, there's the nav section, and that looks like this is the,
00:06:01.040 | here we are, this is the main content. So I can just close up the whole nav section, select it,
00:06:07.280 | delete it, and then we could probably get rid of the footer. Delete it. There we go. And so once
00:06:16.640 | I've done that, I can then immediately, you can see underneath, look, there's the markdown right
00:06:23.680 | here, and then I can copy it, and all done. And so you can see here it's an interesting kind of
00:06:29.520 | combination of, like, you know, a JavaScript component, and, you know, everything happens
00:06:37.600 | automatically. As soon as I click load, as soon as I edit something here, it automatically changes
00:06:42.160 | here. So if I remove the word using, say, and you can see, yep, it's gone. So, yeah, these are kind
00:06:51.120 | of applications that can be quite interactive and incorporate JavaScript and Python functionality
00:06:56.240 | together, and styling, and so forth. So you'll also see here the link to the documentation.
00:07:04.720 | Hopefully by the time you read this, there'll be even more, but we've got a kind of a bit of a
00:07:10.320 | quick getting started here, and maybe the best way to learn is through examples. So we've got
00:07:14.800 | lots of examples of, you know, end-to-end. This one's actually got one, two, three, four, four
00:07:21.600 | full examples. Image generation, chatbot, multiplayer game of life, so forth. All right,
00:07:31.200 | so let's get into our own example now. So I've just literally got a totally empty nothing here.
00:07:38.960 | We don't need to start with any kind of skeleton or anything like that. We'll just create a file,
00:07:43.040 | and we call our main file main.py. And we're ready to get started. So one way maybe to
00:07:56.480 | get started here is, okay, you need to pip install it. Obviously I've done that already.
00:08:03.600 | And here is our minimal app. So why don't we just copy that.
00:08:11.280 | And we will paste it, but I can even make it more minimal.
00:08:18.000 | And I'll explain the components in a moment. But for now, let's just run it. So python main.py.
00:08:31.680 | Okay, so as you can see, it now says application startup complete, and it tells me here
00:08:39.680 | the link that I have to go to to see my website. So all these applications are running in the web.
00:08:45.360 | And here is my website. Hello, world. Okay, so what actually happened here,
00:08:54.160 | so fastHTML, we try to make it as easy as possible to get all the symbols you'll need
00:09:00.560 | for most fastHTML apps. So we've got this special common namespace here.
00:09:06.480 | It's pretty common when using kind of widget frameworks and so forth to just say import star.
00:09:13.200 | That way you just get kind of all the divs and p's and everything else you need. So that's what
00:09:17.440 | I prefer to do. An alternative that works also pretty well is to say from fastHTML import common
00:09:28.000 | as fh. And so in that case, then it's going to be fh.fastApp, fh.div, fh.p, fh.serve. So one kind of
00:09:42.320 | nice thing about that is that you can immediately kind of see all the different options that
00:09:48.240 | you have, which could be helpful. And so if I save that, you can see as soon as I save it,
00:09:59.040 | it automatically reloads. So that's handy. It doesn't automatically, by default,
00:10:09.600 | reload the website itself. So to make it auto-reload, I add live equals true.
00:10:19.680 | Save that. You'll see it automatically restarts the application.
00:10:26.640 | I'll refresh this once in order for it to read. Notice that it's now live.
00:10:33.280 | So now that I've done that, I should be able to say
00:10:38.480 | I am alive. And I'll hit save. And you can see as soon as I do, it updated itself.
00:10:47.120 | So what's actually happening here, let's just go back to how it was. I find that a bit more
00:10:55.600 | convenient. So one good way to understand what's happening here is to take a look at
00:11:06.560 | what's actually happening in the application. So one good way to do that is to open up the
00:11:11.680 | developer tools, which you can see here. And as you can see, it's command option i on my computer.
00:11:21.920 | And here I've got the network tab. If I refresh this,
00:11:26.960 | you can see it's loading up a whole bunch of JavaScript. That is all
00:11:32.960 | happening automatically. But the most important thing is this actual very first thing that gets
00:11:40.800 | called, which is to see what's actually going on here. And so what's actually going on here
00:11:46.160 | is it's going to my web browser has asked for the page at localhost colon 5001. And so you can see
00:11:53.200 | this is the request, localhost 5001. And what's it asking for? It's asking to get the page. So
00:12:00.640 | this is called the HTTP method. So this HTTP method is get. There's a number of HTTP methods
00:12:06.560 | we'll use. And all we do is we name the function with the same name as the HTTP method we want.
00:12:13.520 | So when you just go to a website, you type it into a URL and you hit enter,
00:12:16.800 | your browser always calls get. And in this case, I didn't put any path up here. So that means that
00:12:26.000 | you've got the default path, which is just slash. So here's the whole URL that it got.
00:12:31.840 | And so what happened after our browser asked for that information? Well, it hit our web server,
00:12:40.960 | which is running here. We've written serve. So that causes our web server to start. And
00:12:45.920 | this here is our router. Our router is created when we ask for a fast HTML application by writing
00:12:54.960 | fast app. We get back two things. We get the application object and we get the router.
00:12:59.680 | And so we say, OK, for the router, when you get a request for slash, so that's the default
00:13:06.560 | root level request, and it's a get request, I want you to do this and send this back to the browser.
00:13:13.840 | And so we got that request. Our web server ran. And you can see here the response it gave.
00:13:22.880 | And it's more than just what we asked for. Here's the bit we asked for, which was a div
00:13:27.680 | and a paragraph with hello world. And so fast HTML for us has auto generated
00:13:33.680 | an appropriate header. And also this little bit here, which is added for the live reloading
00:13:40.720 | functionality. Let's start to make this look a little bit better. So there's a number of like
00:13:46.560 | little conveniences in fast HTML. It would be nice if the title of the page was something other than
00:13:53.600 | just fast HTML page. And it'd be nice if it looked a bit better. So there's a little convenience we
00:13:59.360 | have, which is just to say titled, which means return a page with some particular title.
00:14:06.800 | And this is our greeting page. And so the title requires two things.
00:14:16.320 | As you can see here, it requires the title and then a bunch of things for the page itself.
00:14:23.120 | And so in this case, we just got the original page. So if I save that,
00:14:30.960 | here it is. So we've now got the title. It also appears as an h1. And you can also see that now
00:14:37.200 | that we've done that, it's made it look a little bit better. The reason it's looking a bit better
00:14:41.280 | is that by default, we use something called Pico CSS, which is a nice little minimal style sheet
00:14:48.560 | type thing. And so here's some examples of what it looks like.
00:14:58.640 | And so in Pico, everything has to be wrapped in a container. And so that's part of what thing
00:15:08.320 | that happens automatically. And that's why it's starting to look a little bit better than it used
00:15:12.320 | to. To see how these things work, they're generally very little code. So I can just jump to it here.
00:15:22.320 | And so here's titled. And you can see that all it does is it's just returning a title, an h1.
00:15:31.600 | Whatever I asked it to display, it's setting the class to what Pico wants.
00:15:37.760 | And it's wrapping the whole thing in main.
00:15:41.440 | And so you can see here that main is actually itself a HTML element. So basically things that
00:15:52.240 | are here, which are not specifically defined, are generally going to be HTML elements,
00:15:56.480 | ditto with title. FastHTML automatically handles making sure that if you've got a
00:16:03.280 | HTML header like title, it puts it in the right place for you. And so you can see that all works
00:16:08.400 | nicely. Okay, so maybe let's start adding a bit of interactivity. So behind the scenes,
00:16:19.600 | a lot of what we do is going to rely on this really nifty little library called
00:16:24.160 | htmx. Htmx is something which basically removes some constraints from your web browser. And it
00:16:30.880 | allows the web browser to do things which, I mean, when you learn about them, you realize,
00:16:35.840 | I mean, they just make a lot of sense. They probably should have been how web browsers
00:16:38.480 | were built in the first place. And maybe in the future, web browsers, there's thoughts that they
00:16:42.400 | might actually change to support some of the stuff in htmx directly. But for now, we have to use htmx.
00:16:49.280 | So FastHTML adds that automatically. And htmx is something where we can basically
00:16:57.200 | respond to these HTTP requests on the server and do all kinds of things with them.
00:17:01.920 | Let's look at the most simple kind of HTTP request, which is to create a hyperlink.
00:17:17.040 | Okay. And so this hyperlink says /change on it. So if I save this, you'll see here now we've got
00:17:25.120 | this link. And if you look at the bottom of the screen, it's going to say change. And if I click
00:17:30.320 | on it, it says not found. And that's because I don't have a root for /change at the moment.
00:17:38.240 | So let's go and create one. So to create one, I just say create a root for /change.
00:17:47.040 | Change is good. I don't know. Whatever. Okay. Save that. And so now, when I go to
00:17:57.600 | localhost colon 5001 /change, it'll return this. Well, that should be get, of course.
00:18:05.840 | I'm using this thing called cursor, which sometimes tries to use AI to change things
00:18:12.000 | automatically, and it doesn't always know what to do. There we go. Change is good. So go back,
00:18:17.120 | click the link. Excellent. And so we could, for example, add an additional one here,
00:18:26.080 | which just goes back to /. So now we can link. Maybe put this all in titled.
00:18:36.480 | Actually, we could just put it just directly here. Titled.
00:18:46.960 | Save that. Link. There we go. So you can see, using this way, we can create a website,
00:18:57.440 | you know? And our website can do Pythony kinds of things. So, for example, numbers equals
00:19:11.520 | I don't know, maybe we'll do something like, well, let's make them as a list,
00:19:16.160 | right? So we'll create an unordered list of list items.
00:19:20.480 | Row and range 10. That sounds good. So we'll put that all into a list comprehension.
00:19:31.920 | And in FastHTML, when you pass lots of things like that, you want to use star to basically
00:19:40.480 | unpack them. So it's going to send them all across as separate arguments to the UL call.
00:19:48.000 | So UL is an unordered list. And so maybe now we'll change that.
00:19:54.240 | And there they are. So to explain what's going on here, you can see that the HTML that we have,
00:20:09.600 | so the HTML that we have has like a UL and all these LIs, P, A, so forth. And
00:20:19.040 | we're using Python functions with the same name.
00:20:24.240 | P, A. And so that's part of what FastHTML is providing to us. So this means that we don't need
00:20:35.600 | templates or like anything like JSX or anything like that. Because everything's just Python,
00:20:42.400 | we can just, you know, do this stuff quite easily. So for example, if we wanted a
00:20:48.080 | function to create lists of any size, you know, we could factor this out
00:20:53.680 | into like a numbered list component. We'd pass in, say, 10 to say that's what we want to get the
00:21:04.640 | number list going up to. So it goes up to i and make it range i. So let's change that now to 15,
00:21:17.600 | say. Save. And you can see there, it's now going up to 15.
00:21:24.320 | But not the most exciting component, but you can kind of hopefully get the idea here that
00:21:31.360 | we can add more and more interesting components. So for example,
00:21:37.040 | that about page that you saw earlier, these kind of components, you know, that do things like
00:21:48.320 | creating the hamburger menu automatically and
00:21:57.760 | making sure that our captions wrap well and adding this little auto updating
00:22:03.600 | thing on the table of contents. So that's all being provided by a framework called Bootstrap.
00:22:12.880 | And so the source code for the about stuff is very small, as we've seen before.
00:22:24.480 | And one of the key reasons for that is that the work is being done by
00:22:29.600 | a Bootstrap component library. So things like this navbar, for example, that, you know,
00:22:40.720 | make it bold, basically depending on where you go to and so forth, are all happening
00:22:50.160 | inside here. So let's see, we've got -- here's our navbar, for instance.
00:22:55.840 | And so it's still all written in Python, which is nice, but, you know, it takes a bit of work
00:23:01.600 | to figure out how Bootstrap works and figure out how best to, you know, present it. So it's really
00:23:08.320 | nice when somebody else does that for you, and then you can use Bootstrap directly from Python
00:23:13.040 | without thinking about it. So that's the kind of goal of this -- that's kind of the goal of
00:23:22.400 | this approach here of being able to create these Python components. Okay. So this is
00:23:29.840 | not a particularly exciting application so far. And it's not really taking advantage of it at
00:23:40.960 | all. Like, when I click this, it's giving us a whole new page, and that's not really how modern
00:23:46.080 | JavaScript -- modern web applications tend to work. Things tend to happen kind of in place.
00:23:51.600 | So to show you how we can change that, we create a div here. We'll give it an ID.
00:24:07.600 | And what we're going to do is we're going to make a change here to this hyperlink.
00:24:14.480 | We're going to change it from an href to hxget. Hx is what htmx uses. Normally it uses hyphens.
00:24:25.040 | We use underscores anywhere you'd normally see a hyphen, because Python is not happy using hyphens
00:24:33.200 | in parameters. Okay. What happens when we say hxget instead of href? Well, what actually happens
00:24:43.360 | is that htmx will call this in the usual way, and it's going to call get, because we said we
00:24:53.120 | want to get. But it doesn't replace the whole page. Instead, it replaces or inserts or changes
00:25:00.240 | the existing page DOM, the existing page in some way. So actually, probably an even better way to
00:25:08.160 | do this is I'm going to -- really simple. I'm going to put it on this div itself. And so this is one
00:25:18.000 | of the interesting things about htmx is that it lets you respond not just to clicks and hyperlinks,
00:25:22.400 | but to literally anything. In this case, it's going to be a click on a div. It could respond
00:25:26.480 | to a scroll or a movement of a slider or a mouseover or anything you like.
00:25:31.760 | Okay. So when we click on this div containing these numbers, it's going to call /change.
00:25:40.160 | And then by default, what it's going to do is it's going to replace the contents of the div.
00:25:44.080 | So we're going to change the contents of the div with, say, this paragraph. Change is good.
00:25:54.400 | Okay. So I'll say save. And then I'm going to click on this div.
00:25:59.680 | And as you can see, it's changed it to say change is good. So what actually happened there
00:26:06.560 | might be interesting to look at the actual what's happening over the network.
00:26:14.800 | So I'm going to -- so what I'm going to do is I'm going to say refresh. Okay. So that's
00:26:22.560 | the usual page. Looks exactly like it did before. And so it's the usual page.
00:26:29.840 | Just contains all this HTML. Okay. Now, what happens when I click on it? So I'm going to click.
00:26:37.920 | So what happened when we clicked on that is it's called localhost/change. It called it with a get.
00:26:48.400 | The response was exactly what we asked for. It's just the paragraph. But as you can see,
00:26:55.280 | it actually updated the page. And so the reason for that is because we used
00:27:00.000 | htmx. Now, behind the scenes, it doesn't really matter how it's actually doing that. But if you're
00:27:06.000 | interested, behind the scenes, it's because htmx adds various headers to tell it what to do,
00:27:13.760 | which is that's how it all happens automatically. So you can see here that we now have,
00:27:21.120 | you know, the start of an application which is doing kind of dynamic calculations on the
00:27:27.600 | server side and updating the kind of inside of the web page on the client side. And so we've
00:27:34.080 | kind of got the pieces that we need to create, you know, pretty much anything we can think of.
00:27:44.400 | So let's keep going with that. So the next step I think we'll do is we will add a database.
00:27:54.960 | So if you look at FastApp, you can see it's got a lot of different things that can be passed.
00:28:04.320 | And over time, we'll see what all of those do. Not in this video, but over time. But perhaps
00:28:11.440 | one of the most interesting ones is the database. This is where we can
00:28:14.480 | easily create a database. And we can pass to it some keyword arguments, which will be used to
00:28:24.240 | define what that database contains. If you want just one table, which is all we need for now.
00:28:30.000 | If you want more than one table, then you can actually pass in a dictionary of all of the
00:28:36.880 | tables that you want. And this is all just, like, convenience. You can also do this through
00:28:41.360 | any other mechanism you like. But it's super convenient just to do it through here. So
00:28:48.480 | we're going to create a database for a to-do list. So it's going to contain
00:28:53.200 | to-do title. And that's going to be a string.
00:28:58.800 | And there's going to be a, is it done or not? And that's going to be a bool.
00:29:03.360 | I mean, that's all we really need for now, I guess, right? So then we need to say what's the
00:29:12.080 | name of this database going to be. So we'll call it todos.db. So by default, it's going to use
00:29:19.360 | something called SQLite, which is an incredibly fast database engine that sits actually in a file
00:29:28.400 | directly on the same machine. So it's the lowest latency possible that you can have in using a
00:29:34.160 | database. And FastHTML by default kind of sets it up in a way that makes it, you know, pretty fast
00:29:41.360 | and pretty scalable. It should be quite a while before your application gets popular enough that
00:29:47.600 | you're going to need to expand it to something more, you know, bigger and more complex. In fact,
00:29:54.720 | a lot of big companies now are actually running their, you know, most of their stuff on top of
00:30:01.120 | SQLite. So you can take it a long way. You can use any database you like, though. But we're
00:30:06.800 | just going to use the one that's built in that's easiest. So this is now means we have a database.
00:30:13.360 | The other thing then that it's going to do is it's going to also then pass back
00:30:22.480 | a couple of additional things. The additional things that it passes back is an object containing
00:30:30.960 | the actual table and an object containing the type of the things in that table. We can call
00:30:36.240 | them whatever we like. No, I don't need this anymore. So we're creating a to-do list.
00:30:43.360 | And so to list our table, you just type the table name, followed by parentheses.
00:30:51.200 | Okay, we save that and you can see it updates it. And we don't see anything yet because there are
00:31:01.440 | no to-dos yet in this to-do list. So that makes sense. So maybe just for testing purposes,
00:31:10.240 | we should just add something here. That looks fine. So I'll save that.
00:31:18.080 | Ah yes, that reminds me. We should also give it a primary key. So let's say id equals int.
00:31:28.720 | And then it's useful then to tell it what the primary key is.
00:31:32.000 | Let's try again. Okay, so after we add the primary key and tell it what the primary key is,
00:31:42.560 | you can see that it's now displaying a to-do. It's not displaying it in the most helpful way.
00:31:53.280 | One simple thing we could do that makes it slightly nicer would be to wrap it in a list.
00:31:59.760 | Actually, maybe before we refresh, we'll just add one more to-do.
00:32:04.560 | So we'll go through the list and we'll create a list item with our to-do.
00:32:20.080 | And so that will now be an unordered list of items.
00:32:23.280 | There we go. Accidentally got it twice there, but that's fine. We'll fix that one up later.
00:32:28.960 | Okay, so we have a to-do list. Why do they look like this? Well, the FastHTML, FastApp,
00:32:39.520 | is basically creating a data class for us behind the scenes. And this is how data classes get
00:32:45.120 | displayed. So we'd like to display them in a nicer way. So to say how you want to
00:32:50.960 | render your objects, you define a function.
00:32:55.600 | Okay, like so. And so it's going to take a to-do and it's going to return something.
00:33:05.360 | So we'll just return a list item with the title. So now we have to tell it what our
00:33:12.080 | render function is. So we'll say render equals render. And save.
00:33:19.040 | There we go. Oh, and now, of course, we shouldn't be creating list items for our to-dos at all.
00:33:25.840 | We can now just say to-dos. Because it's going to auto-render them. There we go. That's better.
00:33:38.080 | So let's maybe add a check mark as well. Plus, let's see. That looks fine. Save that.
00:33:51.040 | Okay, that's more like it. So none of them are done yet. So that's fine.
00:33:57.680 | Okay, what should we do next? Maybe we'll make it so we can mark them as done.
00:34:06.400 | Or we could also delete them. That might be quite useful as well. So let's add something to
00:34:14.240 | toggle whether they're done or not. All right. So to do that, maybe we'll just put a little link
00:34:22.240 | before it. Let's call it toggle. It's fine. Toggle equals. Okay, let's see what it's done for us.
00:34:35.760 | So yes, we're going to have a thing called toggle. And it's going to get a new path,
00:34:42.640 | which is /toggle/todoid. That all sounds pretty good. So now,
00:34:48.240 | I think I might actually put it at the start. Okay, so we'll save that. There we go. So we've
00:34:56.880 | now got our links. Okay, and now we need to say what happens when you go to this path.
00:35:06.160 | So the way we say what happens when you go to a path is we create a handler for it.
00:35:11.840 | And so this path's a bit special. It's got a part for it which we're going to be passed in
00:35:22.240 | a todoid. And so you can put that in curly brackets. And what happens when we toggle that?
00:35:34.480 | Okay, so what we need to do is we're going to grab our todo. And a simple way to do that is just to
00:35:47.680 | use this square bracket here. Okay, that looks pretty good. todo.done equals not todo.done.
00:35:55.520 | And what are we going to return? Well, remember that by default, it's going to,
00:36:05.920 | well, actually, what we want to do is we want to change the
00:36:13.360 | todo that this button was clicked on, this link was clicked on.
00:36:17.840 | So there's a number of ways to do that. But one simple way to do it would be to grab the todoid.
00:36:27.040 | And then the list item, we can give it an id. And so basically, if you've,
00:36:38.320 | anything kind of keyword arguments you pass to these tags are going to just become attributes
00:36:44.960 | to the tag. We'll see that in a moment. So then when we click toggle, what's going to happen?
00:36:54.400 | Well, what we're going to do is we're going to, first of all, we want to update our todos.
00:37:03.760 | And we just pass in the updated todo. So that's fine. And then we're going to return the todo
00:37:09.440 | itself. So what's going to happen to it? Well, it's going to be rendered using the render function
00:37:17.520 | and placed somewhere into the browser's DOM. And where does it go? Well, by default,
00:37:23.200 | it replaces the thing that clicked it. So by default, it replaced the link that says toggle,
00:37:27.600 | which is not what we want. So to tell it instead what element it should change, we can use the
00:37:34.640 | target id to say this is the id. And we just say tod. And actually, no, we should probably do what
00:37:42.320 | it just said there, which is to change this to f, like so. That's better. Great. So now it's
00:37:50.480 | actually got a string, todo dash something will be the id. So I'll save that. And we'll take a look
00:37:56.240 | at the -- and so you can see here now each list item has an id. And it has a tag, which is going
00:38:05.920 | to get toggle something in particular. And it's going to then target itself with the result. And
00:38:16.320 | so the result will actually change that DOM element. And actually, that doesn't work because
00:38:22.880 | we did make one critical error, which is that when you add -- and I also forgot to talk about this.
00:38:29.440 | When you add something like this in curly brackets, it says take whatever's here and place
00:38:35.200 | it into the parameter list of this function. So it's going to be passed the todo id. And as you
00:38:42.560 | can see, we added it to our path. So we're going to pull it out of our path. But we also have to
00:38:46.560 | tell it what type it is. And we've got to tell it what type it is. And it's very important here
00:38:50.400 | because this is actually an integer. Because when we created the table, we said that's an integer.
00:38:56.480 | So we need to make it an integer. And so now if we say toggle -- and so you can see it's
00:39:04.480 | fast. You know, as soon as I click, click, click, click.
00:39:08.880 | But actually, what's happening behind the scenes, if we click, you can see here it is
00:39:19.600 | sending a request to /toggle/3. The request is a get request. And then our server does the stuff
00:39:27.920 | that we told it to do. And sends back the updated response. Which now has the tick in it. One thing
00:39:43.920 | that can make things a bit, you know, more concise is actually all of these things like update and
00:39:50.480 | so forth actually return the object. So we can just actually return that. Let's save that. Make
00:39:58.320 | sure it still works. It does. Great. All right. So the next thing, let's make it so we can delete
00:40:09.280 | this extra one we didn't want. So that should be easy enough. We kind of know what to do now. So
00:40:13.840 | let's go ahead and copy this line. We'll call this delete. Okay. It's going to call. And so
00:40:21.040 | interestingly, it doesn't matter. We can certainly use delete here. Or funnily enough, you know,
00:40:25.440 | we can actually just use slash. And instead of HX get, we can say HX delete. It's not really
00:40:35.520 | that one's better than the other. You know, whatever you like. And so this time our route
00:40:40.800 | is still on slash. But now it's going to call the delete method. We're going to be passing in
00:40:46.320 | an ID. So we've got the ID here. So we'll go todos.delete. And then what happens? Well,
00:40:58.560 | what we actually want to happen is this whole list item to be deleted. So we've got the target ID
00:41:03.840 | equals TID. And if we return nothing at all, then by default, Python actually returns the special
00:41:12.560 | none value. And so it's going to replace the target, which is the list item we click on,
00:41:20.960 | with none. And that'll actually cause it to be deleted. Because it's being replaced with nothing.
00:41:27.280 | So this is actually all one liner, which is kind of neat. So if we save,
00:41:34.720 | oh, we also need to add our delete here. There we go. So now save. So there's our delete. Let's
00:41:44.240 | try getting rid of one of our deletes. And actually, while we do, let's have a look.
00:41:50.720 | All right. So delete. Okay. It was close. Oh, I know why that didn't work. And that's because
00:42:04.320 | also by default, it actually changes the contents of the thing that we're returning.
00:42:13.040 | And we don't want to change the contents of the list item. We actually want to
00:42:17.520 | change the whole list item. So
00:42:20.880 | these are -- there's not very many core attributes in HTMX. But one of the core
00:42:35.280 | attributes is when you basically tell it what you want to happen with the result.
00:42:40.560 | And the HX swap is the attribute that we use. And one of the values for it is -- so
00:42:47.280 | it says the default is innerHTML. So we're going to change it to outerHTML,
00:42:51.360 | which tells it to replace the entire target with nothing at all. So HX_swap equals outerHTML.
00:43:02.480 | Save that. And then we'll refresh. Okay. So let's now try clicking delete again.
00:43:16.320 | There we go. That worked nicely. So we've now got our toggle working and our delete working.
00:43:22.400 | So we've got one key thing that we now need to add for it to be somewhat usable,
00:43:28.080 | which is the ability to create additional todos. So we've got our get.
00:43:34.720 | We've got our toggle. We've got our delete. So now we need a create.
00:43:44.000 | Okay. So let's put this here. So what I tend to like to do is to look at the -- there's a couple
00:43:54.720 | of places you can look. One is to look at the Pico CSS documentation, if you're using the default
00:44:00.240 | Pico. We've got other style approaches you can use. But there's a component section. And you
00:44:06.160 | can kind of see like, okay, how do I want things to look? Here's a form section. Input. Or the
00:44:14.960 | other thing you can do is on our docs, if I go to component extensions, we can kind of have a look.
00:44:24.800 | You can see that there's a number of examples here, along with like what they actually look
00:44:32.080 | like when they're rendered, which is quite convenient. So I'm going to use this group
00:44:36.960 | approach. And it's useful to look at the source of these things. Because when you do,
00:44:42.480 | for example, group, you can see it's like literally a single line of code.
00:44:48.080 | Because that's what -- when you look at the Pico CSS docs, it says, oh, you have to create a field
00:44:54.000 | set and give it the role group. So in some ways, even creating this group function is
00:44:59.040 | hardly necessary. But I think it's kind of nice to have these things directly available.
00:45:03.440 | Okay. So we're going to have a group with an input. And with a button. That sounds perfect.
00:45:09.680 | In fact, we might as well just copy that. All right. So let's create a form. And that's going
00:45:19.200 | to contain our group. And so form generally doesn't call get, it calls post. Post generally
00:45:32.000 | means I'm creating something. And now, what do we want to do with that? So again, in this case, the
00:45:44.240 | target is going to be the to-do list itself. So let's give that an ID. Jeremy, I said to give it
00:45:54.320 | an ID. ID. There you go. Right. We'll put our form at the top. And maybe we'll put that in a --
00:46:07.160 | actually, we'll put the whole thing in a card. That can be quite nice. So we'll create a card
00:46:11.040 | where the header is the form. And so you can see what a card looks like.
00:46:19.920 | So we'll have our little header. We'll have our body. Okay. Actually, the header,
00:46:28.720 | because it's a position -- so the header has to come after -- okay. We've got a card with an
00:46:37.520 | unordered list as the body. The header is going to be the form.
00:46:41.520 | And when I save that -- okay. We've now got that at the top. So that's nice.
00:46:48.800 | We'll change this button to say add. And maybe this one, we're at a placeholder.
00:46:57.760 | That's looking good. Okay. So when we post this form, we're going to need, obviously, a
00:47:07.280 | post. And so the way that forms work is that they actually pass their data directly, basically,
00:47:16.960 | as headers. And so we're going to be passed all the information we need for it to do.
00:47:24.000 | Now, how do we make sure that that happens? The trick is to make sure that the names of each of
00:47:30.960 | our elements are what we need. So in this case, we only really have one element, which is the input.
00:47:35.760 | So if we give this the name title, then that means that it's going to know how to create a
00:47:45.200 | to-do with the correct title. Okay. So what we're going to do, then, is we've got this new to-do.
00:47:53.600 | We've got to add it to the database. And in doing so, it's going to return the added to-do.
00:47:58.000 | So we go to dos.insert. That will return the newly added to-do. And where do we want to put it?
00:48:07.280 | We want to put it at the end of the list. So the list has the name to-do list. So we're going to
00:48:15.440 | say that the target ID is to-do list. But we don't want to replace the to-do list with our single to-do.
00:48:24.080 | Instead, we want to insert it at the end. So if you look at the hxswap documentation,
00:48:32.640 | not hwswap, hxswap, there is a before end. Insert the response after the last child. That's exactly
00:48:41.360 | what we want. Okay. So we're going to change our swap to before end. Okay. Save. Let's try that out.
00:49:00.320 | Hello. Add. Wow. Amazing. Done. Delete. That's so cool. Wow. Okay. So I think there's just one
00:49:16.320 | more thing I want to do before we wrap up, which is that, well, actually, while we're here, why
00:49:22.400 | don't we just have a quick look again at the dev tools. We go to the network tab. Let's just see
00:49:28.880 | what this looks like. What does this look like? Add. And the answer is a request to slash. The
00:49:43.200 | method is post. And you'll see that it now has a payload, which is a form data that's added to the
00:49:58.560 | request. And the response, as per usual, is just the actual item that we want added. And
00:50:06.320 | you can see behind the scenes, it's setting things like
00:50:11.520 | hx-target. So it knows what to do with it. Okay. So after I click add, I would like to delete
00:50:23.680 | the contents of this. So that's a little bit different. It's the first time we're saying
00:50:30.640 | that when we click this button, we want two things to happen. We want the to-do to be
00:50:33.760 | inserted at the bottom. And we want the text box to be cleared out. So there's only one target.
00:50:41.360 | And the one target is the to-do list. And it's swapping to before the end. So we want to add
00:50:48.800 | kind of like a second target. You can't do that here. Instead, what we have to do is an out of
00:50:54.320 | band swap. And that's where the thing we return, this post, has to return an additional piece of
00:51:01.760 | information, which is to say, change something else. So the thing we want to change is the text
00:51:07.120 | box. We want to change the input box to basically have what it originally had, which is this.
00:51:15.120 | So a simple way to do that is we can factor this out into a function.
00:51:18.160 | So we'll call this make input. Okay. And we're not going to change it. We're just
00:51:28.640 | going to leave it like that. I kind of prefer to put these things above where they're used.
00:51:32.720 | So that's how my brain works. Okay. So just check this still works. Yep, still working. Cool.
00:51:44.000 | And so we're going to add another new parameter here, another new attribute here, which is hx,
00:51:53.280 | hx, I think it's called hx swap oob, isn't it? Yep, hx swap oob. So hx swap
00:52:10.160 | oob. And so when we do that, what it says is if you return this as a separate bit of HTML,
00:52:19.680 | then it says to find the thing with this ID and replace it. And actually, at the moment,
00:52:25.440 | we don't have an ID. So we should add one. And actually, if we just change this name to an ID,
00:52:30.160 | FastHTML automatically adds a name to it as well. So let's just try that out. Refresh.
00:52:37.040 | And so if we have a look at our input box, you can see here, look, it's got an ID title and it's
00:52:42.560 | got a name of title. So here's the trick. Rather than just returning the inserted to do, we also
00:52:49.920 | return our new input box. So we're returning two things. So if I refresh that now, we'll say,
00:53:01.360 | clear me, please. And we'll say add. And you can see there, look, it's added it and it cleared
00:53:11.040 | this out. So what does that look like in the network? Add. You'll see that the request is
00:53:22.080 | just the same as it was before. And again, the payload's just like it was before. But the response
00:53:27.760 | now contains two things. It contains the thing to add to the end of the to-do list. And it also
00:53:37.200 | includes our input with a placeholder and this special HX swap OOB. And so this is the thing
00:53:46.960 | that actually replaces the target ID. And then this thing replaces whatever has the same ID as it has.
00:53:56.880 | All right. And so now we have a to-do list. Not the most amazing one ever. But, you know,
00:54:08.240 | we -- it's all in Python. We haven't had to write any JavaScript. We haven't had to write any CSS.
00:54:13.280 | And it's not too much code. All right. I think the last thing that I want to do then is to
00:54:24.960 | deploy this so that everybody can use our amazing to-do list.
00:54:27.840 | So currently I think the easiest way to deploy fast HTML applications is to Railway.
00:54:37.840 | So this is Railway.app. To deploy it to Railway, you install the Railway CLI, which you can
00:54:49.440 | see their website for documentation on how to do that. And then here's our directory.
00:54:58.880 | Ah, yes. So there is one change you're going to have to make to have this work. Which is
00:55:04.560 | it uses the data subdirectory of your -- Railway uses the data subdirectory
00:55:11.200 | of your folder for -- it's kind of where it mounts the persistent storage. Because it's
00:55:17.680 | going to mount a volume for us. So -- and the other thing we should probably do for production
00:55:24.400 | is to change live to false. I'm not sure it matters too much. But we may as well.
00:55:31.760 | Don't need live reloading in a production app.
00:55:33.680 | And so we should change this now to data/to-dos. And let's just make sure that still works.
00:55:46.000 | Okay. Looks fine. That's good. So we can now deploy this. So once you've installed
00:55:56.320 | the Railway CLI, you type Railway login. And I've done that before. So I'm all logged in.
00:56:00.480 | So at this point, all I have to do is type FH for fast HTML. Railway deploy. And if you add a dash
00:56:07.680 | H, you'll get the help. So the first thing it needs is the name of your project. So I'm just
00:56:13.440 | going to call this demo hello. And then do you want to create a mount? We do. The default's true.
00:56:22.880 | So that should all be fine. So that's going to make sure that it stores our data for us.
00:56:27.920 | And Railway's quite neat. It doesn't, like, charge you anything other than the bytes you
00:56:31.920 | actually store. And it only charges you for the time that people are actually using your application.
00:56:38.000 | So it's only cost me a few cents a month, I find, to set all this up and test it lots and so forth.
00:56:43.680 | All right. So I'll say deploy.
00:56:46.480 | And this takes -- it varies a bit. Sometimes, like, 30 seconds. Sometimes a minute or two.
00:56:58.720 | Okay. So that one's finished. And so it gives me a domain that I can check out.
00:57:07.040 | So I'll copy that. Head back over here and paste it in. And there it is. And actually,
00:57:16.240 | it even copied our little demo database across as well. So that's why our data's already here.
00:57:24.720 | And there it is. It's all working. Excellent. Okay. So I think that's everything I wanted to
00:57:34.320 | show you for today. Obviously, it's a very kind of brief, quick start. Creating, you know, having
00:57:42.160 | the ability to create any web application that's possible through HTTP, HTML, and JavaScript,
00:57:48.320 | CSS. It's a big territory to cover. And we only covered a fraction of it today. But hopefully,
00:57:53.920 | it's enough to help you get started. And my suggestion is, as a next step, is to check out
00:57:59.120 | the many FastHTML examples. Because pick one that does something that you like, pull it apart,
00:58:06.960 | change it, and see all the secrets in there. And over the coming weeks and months,
00:58:13.360 | we'll be creating a lot more tutorials and documentation to help you learn from. And if you
00:58:19.280 | do create anything interesting or have any questions or create tutorials of your own or so
00:58:24.640 | forth, be sure to let us know on the FastHTML Discord. Or, of course, you can put an issue
00:58:31.680 | into our GitHub repo. Or if you have suggestions, you know, ideas in code for stuff that you think
00:58:37.760 | might be helpful, send in a PR is also most welcome. All right. Thanks, everybody,
00:58:43.120 | for joining me and signing off from Queensland, Australia. Bye-bye.