Hi, this is Jeremy Howard from Answer.ai, coming to you here from Queensland, Australia. And I want to give you a gentle introduction to FastHTML. This won't be a super zippy get-it-done-fast thing, but I want to just kind of go through things a little bit gently and slowly and carefully, and show you around to help you get started.
It will probably help if you've done a little bit of web programming before, but I'll try to introduce everything as we go to make it as accessible as possible. Okay, let's get started. So this is the FastHTML homepage. I imagine you've seen this already, given you found this video.
And on the homepage, you can see some quick examples of what FastHTML looks like. So here's some code on the left. This is pretty short. And on the right is what it renders. In this case, it's all being done by a Python component called Card3D. And so these kinds of components, you can wrap them up, they can contain styles, they can contain JavaScript behavior and so forth, or they can be combined out of other Python components.
FastHTML websites are dynamic, they're running on a server, but they are interacting with the browser via JavaScript automatically on the whole. So these are the actual temperatures in America right now, as I talked to you. They're coming from an API function here. And so we actually have dynamically updated weather on our homepage.
And as you can see, it doesn't take very much to do that in terms of the code for it, assuming you've got the weather API hooked up, of course. The kinds of components that you can create can be kind of server-side data-driven APIs like that, or they can be kind of GUI-type components like this.
So this is like an example of an accordion. Again, it's written in Python, it's accessible from Python. And so not the most exciting looking thing, but this is actually, again, a dynamically generated in real time on the homepage. This is the actual code for it coming from a database.
As you can see, it's as simple as saying db.create, and you can define it in basically one data class, or it's not even a data class, just one regular Python class to say what the data is. And then to convert a table into an unordered list, you just say unordered list, and here's the table.
So as you can see, this kind of thing really is very easy to get started with. One good way to get started is to look at lots of examples of people writing stuff. So we've got four examples here on our homepage, so you can check these out as well.
But today we're going to get started on our own. There is a lot to learn when it comes to like learning to do web programming. There's a lot less to learn with fastHTML than with most current approaches. But you can't expect to just jump in and start coding right away without knowing any basics.
So to help you learn the basics, we have this learn more here, which takes you to this mini site, which talks about the kind of basic principles of fastHTML, all the different technologies that it's built on, you know, the component, the foundations, the technology, how components work. And so this is a good place if you want to understand more of the concepts of how this is all put together.
It was interesting when I built this page is I kind of thought, like, hmm, how am I going to do this? It would be nice to build it with fastHTML. I wonder what that would look like. And it actually worked out really nicely. I had it basically written in a couple of hours.
So here's the actual source code for that page. As you can see, I've just got markdown, and I just created this little markdown wrapper here. So this is basically the code other than the prose to generate that page. And so you might be thinking, okay, well, how does markdown work?
Well, the nice thing is, because we're using Python here, the markdown function I wrote simply calls something that I found on PyPy, which is the Python markdown library, and wraps it up in a div. So, yeah, as you can see, it's kind of like, you can create all kinds of different types of applications and pages and sites in fastHTML without necessarily needing, like, special purpose blogging systems or whatever.
Maybe I'll show you one more example of something I wrote the other day, which I quite like, is I find it really helpful often to have a markdown version of documentation, so I can kind of provide it to a language model, for example, if I'm using Claude or ChatGPT.
So often I find I want to, well, let's look at an example. So if we go back to that about page, let's say we wanted to include this as context in a, for a ChatGPT or Claude conversation chat. You wanted a markdown, so if I wrote this little thing, so I can copy the URL, paste it here, say load, and you can see here it is loaded up.
And it's kind of cool, because, like, I want to, for example, get rid of the navigation, and so you can see here I can, like, grab, okay, that's the whole div, there's the nav section, and that looks like this is the, here we are, this is the main content.
So I can just close up the whole nav section, select it, delete it, and then we could probably get rid of the footer. Delete it. There we go. And so once I've done that, I can then immediately, you can see underneath, look, there's the markdown right here, and then I can copy it, and all done.
And so you can see here it's an interesting kind of combination of, like, you know, a JavaScript component, and, you know, everything happens automatically. As soon as I click load, as soon as I edit something here, it automatically changes here. So if I remove the word using, say, and you can see, yep, it's gone.
So, yeah, these are kind of applications that can be quite interactive and incorporate JavaScript and Python functionality together, and styling, and so forth. So you'll also see here the link to the documentation. Hopefully by the time you read this, there'll be even more, but we've got a kind of a bit of a quick getting started here, and maybe the best way to learn is through examples.
So we've got lots of examples of, you know, end-to-end. This one's actually got one, two, three, four, four full examples. Image generation, chatbot, multiplayer game of life, so forth. All right, so let's get into our own example now. So I've just literally got a totally empty nothing here. We don't need to start with any kind of skeleton or anything like that.
We'll just create a file, and we call our main file main.py. And we're ready to get started. So one way maybe to get started here is, okay, you need to pip install it. Obviously I've done that already. And here is our minimal app. So why don't we just copy that.
And we will paste it, but I can even make it more minimal. And I'll explain the components in a moment. But for now, let's just run it. So python main.py. Okay, so as you can see, it now says application startup complete, and it tells me here the link that I have to go to to see my website.
So all these applications are running in the web. And here is my website. Hello, world. Okay, so what actually happened here, so fastHTML, we try to make it as easy as possible to get all the symbols you'll need for most fastHTML apps. So we've got this special common namespace here.
It's pretty common when using kind of widget frameworks and so forth to just say import star. That way you just get kind of all the divs and p's and everything else you need. So that's what I prefer to do. An alternative that works also pretty well is to say from fastHTML import common 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 nice thing about that is that you can immediately kind of see all the different options that you have, which could be helpful. And so if I save that, you can see as soon as I save it, it automatically reloads.
So that's handy. It doesn't automatically, by default, reload the website itself. So to make it auto-reload, I add live equals true. Save that. You'll see it automatically restarts the application. I'll refresh this once in order for it to read. Notice that it's now live. So now that I've done that, I should be able to say I am alive.
And I'll hit save. And you can see as soon as I do, it updated itself. So what's actually happening here, let's just go back to how it was. I find that a bit more convenient. So one good way to understand what's happening here is to take a look at what's actually happening in the application.
So one good way to do that is to open up the developer tools, which you can see here. And as you can see, it's command option i on my computer. And here I've got the network tab. If I refresh this, you can see it's loading up a whole bunch of JavaScript.
That is all happening automatically. But the most important thing is this actual very first thing that gets called, which is to see what's actually going on here. And so what's actually going on here is it's going to my web browser has asked for the page at localhost colon 5001.
And so you can see this is the request, localhost 5001. And what's it asking for? It's asking to get the page. So this is called the HTTP method. So this HTTP method is get. There's a number of HTTP methods we'll use. And all we do is we name the function with the same name as the HTTP method we want.
So when you just go to a website, you type it into a URL and you hit enter, your browser always calls get. And in this case, I didn't put any path up here. So that means that you've got the default path, which is just slash. So here's the whole URL that it got.
And so what happened after our browser asked for that information? Well, it hit our web server, which is running here. We've written serve. So that causes our web server to start. And this here is our router. Our router is created when we ask for a fast HTML application by writing fast app.
We get back two things. We get the application object and we get the router. And so we say, OK, for the router, when you get a request for slash, so that's the default root level request, and it's a get request, I want you to do this and send this back to the browser.
And so we got that request. Our web server ran. And you can see here the response it gave. And it's more than just what we asked for. Here's the bit we asked for, which was a div and a paragraph with hello world. And so fast HTML for us has auto generated an appropriate header.
And also this little bit here, which is added for the live reloading functionality. Let's start to make this look a little bit better. So there's a number of like little conveniences in fast HTML. It would be nice if the title of the page was something other than just fast HTML page.
And it'd be nice if it looked a bit better. So there's a little convenience we have, which is just to say titled, which means return a page with some particular title. And this is our greeting page. And so the title requires two things. As you can see here, it requires the title and then a bunch of things for the page itself.
And so in this case, we just got the original page. So if I save that, here it is. So we've now got the title. It also appears as an h1. And you can also see that now that we've done that, it's made it look a little bit better. The reason it's looking a bit better is that by default, we use something called Pico CSS, which is a nice little minimal style sheet type thing.
And so here's some examples of what it looks like. And so in Pico, everything has to be wrapped in a container. And so that's part of what thing that happens automatically. And that's why it's starting to look a little bit better than it used to. To see how these things work, they're generally very little code.
So I can just jump to it here. And so here's titled. And you can see that all it does is it's just returning a title, an h1. Whatever I asked it to display, it's setting the class to what Pico wants. And it's wrapping the whole thing in main. And so you can see here that main is actually itself a HTML element.
So basically things that are here, which are not specifically defined, are generally going to be HTML elements, ditto with title. FastHTML automatically handles making sure that if you've got a HTML header like title, it puts it in the right place for you. And so you can see that all works nicely.
Okay, so maybe let's start adding a bit of interactivity. So behind the scenes, a lot of what we do is going to rely on this really nifty little library called htmx. Htmx is something which basically removes some constraints from your web browser. And it allows the web browser to do things which, I mean, when you learn about them, you realize, I mean, they just make a lot of sense.
They probably should have been how web browsers were built in the first place. And maybe in the future, web browsers, there's thoughts that they might actually change to support some of the stuff in htmx directly. But for now, we have to use htmx. So FastHTML adds that automatically. And htmx is something where we can basically respond to these HTTP requests on the server and do all kinds of things with them.
Let's look at the most simple kind of HTTP request, which is to create a hyperlink. Okay. And so this hyperlink says /change on it. So if I save this, you'll see here now we've got this link. And if you look at the bottom of the screen, it's going to say change.
And if I click on it, it says not found. And that's because I don't have a root for /change at the moment. So let's go and create one. So to create one, I just say create a root for /change. Change is good. I don't know. Whatever. Okay. Save that.
And so now, when I go to localhost colon 5001 /change, it'll return this. Well, that should be get, of course. I'm using this thing called cursor, which sometimes tries to use AI to change things automatically, and it doesn't always know what to do. There we go. Change is good.
So go back, click the link. Excellent. And so we could, for example, add an additional one here, which just goes back to /. So now we can link. Maybe put this all in titled. Actually, we could just put it just directly here. Titled. Save that. Link. There we go.
So you can see, using this way, we can create a website, you know? And our website can do Pythony kinds of things. So, for example, numbers equals I don't know, maybe we'll do something like, well, let's make them as a list, right? So we'll create an unordered list of list items.
Row and range 10. That sounds good. So we'll put that all into a list comprehension. And in FastHTML, when you pass lots of things like that, you want to use star to basically unpack them. So it's going to send them all across as separate arguments to the UL call.
So UL is an unordered list. And so maybe now we'll change that. And there they are. So to explain what's going on here, you can see that the HTML that we have, so the HTML that we have has like a UL and all these LIs, P, A, so forth.
And we're using Python functions with the same name. P, A. And so that's part of what FastHTML is providing to us. So this means that we don't need templates or like anything like JSX or anything like that. Because everything's just Python, we can just, you know, do this stuff quite easily.
So for example, if we wanted a function to create lists of any size, you know, we could factor this out into like a numbered list component. We'd pass in, say, 10 to say that's what we want to get the number list going up to. So it goes up to i and make it range i.
So let's change that now to 15, say. Save. And you can see there, it's now going up to 15. But not the most exciting component, but you can kind of hopefully get the idea here that we can add more and more interesting components. So for example, that about page that you saw earlier, these kind of components, you know, that do things like creating the hamburger menu automatically and making sure that our captions wrap well and adding this little auto updating thing on the table of contents.
So that's all being provided by a framework called Bootstrap. And so the source code for the about stuff is very small, as we've seen before. And one of the key reasons for that is that the work is being done by a Bootstrap component library. So things like this navbar, for example, that, you know, make it bold, basically depending on where you go to and so forth, are all happening inside here.
So let's see, we've got -- here's our navbar, for instance. And so it's still all written in Python, which is nice, but, you know, it takes a bit of work to figure out how Bootstrap works and figure out how best to, you know, present it. So it's really nice when somebody else does that for you, and then you can use Bootstrap directly from Python without thinking about it.
So that's the kind of goal of this -- that's kind of the goal of this approach here of being able to create these Python components. Okay. So this is not a particularly exciting application so far. And it's not really taking advantage of it at all. Like, when I click this, it's giving us a whole new page, and that's not really how modern JavaScript -- modern web applications tend to work.
Things tend to happen kind of in place. So to show you how we can change that, we create a div here. We'll give it an ID. And what we're going to do is we're going to make a change here to this hyperlink. We're going to change it from an href to hxget.
Hx is what htmx uses. Normally it uses hyphens. We use underscores anywhere you'd normally see a hyphen, because Python is not happy using hyphens in parameters. Okay. What happens when we say hxget instead of href? Well, what actually happens is that htmx will call this in the usual way, and it's going to call get, because we said we want to get.
But it doesn't replace the whole page. Instead, it replaces or inserts or changes the existing page DOM, the existing page in some way. So actually, probably an even better way to do this is I'm going to -- really simple. I'm going to put it on this div itself. And so this is one of the interesting things about htmx is that it lets you respond not just to clicks and hyperlinks, but to literally anything.
In this case, it's going to be a click on a div. It could respond to a scroll or a movement of a slider or a mouseover or anything you like. Okay. So when we click on this div containing these numbers, it's going to call /change. And then by default, what it's going to do is it's going to replace the contents of the div.
So we're going to change the contents of the div with, say, this paragraph. Change is good. Okay. So I'll say save. And then I'm going to click on this div. And as you can see, it's changed it to say change is good. So what actually happened there might be interesting to look at the actual what's happening over the network.
So I'm going to -- so what I'm going to do is I'm going to say refresh. Okay. So that's the usual page. Looks exactly like it did before. And so it's the usual page. Just contains all this HTML. Okay. Now, what happens when I click on it? So I'm going to click.
So what happened when we clicked on that is it's called localhost/change. It called it with a get. The response was exactly what we asked for. It's just the paragraph. But as you can see, it actually updated the page. And so the reason for that is because we used htmx.
Now, behind the scenes, it doesn't really matter how it's actually doing that. But if you're interested, behind the scenes, it's because htmx adds various headers to tell it what to do, which is that's how it all happens automatically. So you can see here that we now have, you know, the start of an application which is doing kind of dynamic calculations on the server side and updating the kind of inside of the web page on the client side.
And so we've kind of got the pieces that we need to create, you know, pretty much anything we can think of. So let's keep going with that. So the next step I think we'll do is we will add a database. So if you look at FastApp, you can see it's got a lot of different things that can be passed.
And over time, we'll see what all of those do. Not in this video, but over time. But perhaps one of the most interesting ones is the database. This is where we can easily create a database. And we can pass to it some keyword arguments, which will be used to define what that database contains.
If you want just one table, which is all we need for now. If you want more than one table, then you can actually pass in a dictionary of all of the tables that you want. And this is all just, like, convenience. You can also do this through any other mechanism you like.
But it's super convenient just to do it through here. So we're going to create a database for a to-do list. So it's going to contain to-do title. And that's going to be a string. And there's going to be a, is it done or not? And that's going to be a bool.
I mean, that's all we really need for now, I guess, right? So then we need to say what's the name of this database going to be. So we'll call it todos.db. So by default, it's going to use something called SQLite, which is an incredibly fast database engine that sits actually in a file directly on the same machine.
So it's the lowest latency possible that you can have in using a database. And FastHTML by default kind of sets it up in a way that makes it, you know, pretty fast and pretty scalable. It should be quite a while before your application gets popular enough that you're going to need to expand it to something more, you know, bigger and more complex.
In fact, a lot of big companies now are actually running their, you know, most of their stuff on top of SQLite. So you can take it a long way. You can use any database you like, though. But we're just going to use the one that's built in that's easiest.
So this is now means we have a database. The other thing then that it's going to do is it's going to also then pass back a couple of additional things. The additional things that it passes back is an object containing the actual table and an object containing the type of the things in that table.
We can call them whatever we like. No, I don't need this anymore. So we're creating a to-do list. And so to list our table, you just type the table name, followed by parentheses. Okay, we save that and you can see it updates it. And we don't see anything yet because there are no to-dos yet in this to-do list.
So that makes sense. So maybe just for testing purposes, we should just add something here. That looks fine. So I'll save that. Ah yes, that reminds me. We should also give it a primary key. So let's say id equals int. And then it's useful then to tell it what the primary key is.
Let's try again. Okay, so after we add the primary key and tell it what the primary key is, you can see that it's now displaying a to-do. It's not displaying it in the most helpful way. One simple thing we could do that makes it slightly nicer would be to wrap it in a list.
Actually, maybe before we refresh, we'll just add one more to-do. So we'll go through the list and we'll create a list item with our to-do. And so that will now be an unordered list of items. There we go. Accidentally got it twice there, but that's fine. We'll fix that one up later.
Okay, so we have a to-do list. Why do they look like this? Well, the FastHTML, FastApp, is basically creating a data class for us behind the scenes. And this is how data classes get displayed. So we'd like to display them in a nicer way. So to say how you want to render your objects, you define a function.
Okay, like so. And so it's going to take a to-do and it's going to return something. So we'll just return a list item with the title. So now we have to tell it what our render function is. So we'll say render equals render. And save. There we go. Oh, and now, of course, we shouldn't be creating list items for our to-dos at all.
We can now just say to-dos. Because it's going to auto-render them. There we go. That's better. So let's maybe add a check mark as well. Plus, let's see. That looks fine. Save that. Okay, that's more like it. So none of them are done yet. So that's fine. Okay, what should we do next?
Maybe we'll make it so we can mark them as done. Or we could also delete them. That might be quite useful as well. So let's add something to toggle whether they're done or not. All right. So to do that, maybe we'll just put a little link before it. Let's call it toggle.
It's fine. Toggle equals. Okay, let's see what it's done for us. So yes, we're going to have a thing called toggle. And it's going to get a new path, which is /toggle/todoid. That all sounds pretty good. So now, I think I might actually put it at the start. Okay, so we'll save that.
There we go. So we've now got our links. Okay, and now we need to say what happens when you go to this path. So the way we say what happens when you go to a path is we create a handler for it. And so this path's a bit special.
It's got a part for it which we're going to be passed in a todoid. And so you can put that in curly brackets. And what happens when we toggle that? 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 use this square bracket here.
Okay, that looks pretty good. todo.done equals not todo.done. And what are we going to return? Well, remember that by default, it's going to, well, actually, what we want to do is we want to change the todo that this button was clicked on, this link was clicked on. So there's a number of ways to do that.
But one simple way to do it would be to grab the todoid. And then the list item, we can give it an id. And so basically, if you've, anything kind of keyword arguments you pass to these tags are going to just become attributes to the tag. We'll see that in a moment.
So then when we click toggle, what's going to happen? Well, what we're going to do is we're going to, first of all, we want to update our todos. And we just pass in the updated todo. So that's fine. And then we're going to return the todo itself. So what's going to happen to it?
Well, it's going to be rendered using the render function and placed somewhere into the browser's DOM. And where does it go? Well, by default, it replaces the thing that clicked it. So by default, it replaced the link that says toggle, which is not what we want. So to tell it instead what element it should change, we can use the target id to say this is the id.
And we just say tod. And actually, no, we should probably do what it just said there, which is to change this to f, like so. That's better. Great. So now it's actually got a string, todo dash something will be the id. So I'll save that. And we'll take a look at the -- and so you can see here now each list item has an id.
And it has a tag, which is going to get toggle something in particular. And it's going to then target itself with the result. And so the result will actually change that DOM element. And actually, that doesn't work because we did make one critical error, which is that when you add -- and I also forgot to talk about this.
When you add something like this in curly brackets, it says take whatever's here and place it into the parameter list of this function. So it's going to be passed the todo id. And as you can see, we added it to our path. So we're going to pull it out of our path.
But we also have to tell it what type it is. And we've got to tell it what type it is. And it's very important here because this is actually an integer. Because when we created the table, we said that's an integer. So we need to make it an integer.
And so now if we say toggle -- and so you can see it's fast. You know, as soon as I click, click, click, click. But actually, what's happening behind the scenes, if we click, you can see here it is sending a request to /toggle/3. The request is a get request.
And then our server does the stuff that we told it to do. And sends back the updated response. Which now has the tick in it. One thing that can make things a bit, you know, more concise is actually all of these things like update and so forth actually return the object.
So we can just actually return that. Let's save that. Make sure it still works. It does. Great. All right. So the next thing, let's make it so we can delete this extra one we didn't want. So that should be easy enough. We kind of know what to do now.
So let's go ahead and copy this line. We'll call this delete. Okay. It's going to call. And so interestingly, it doesn't matter. We can certainly use delete here. Or funnily enough, you know, we can actually just use slash. And instead of HX get, we can say HX delete. It's not really that one's better than the other.
You know, whatever you like. And so this time our route is still on slash. But now it's going to call the delete method. We're going to be passing in an ID. So we've got the ID here. So we'll go todos.delete. And then what happens? Well, what we actually want to happen is this whole list item to be deleted.
So we've got the target ID equals TID. And if we return nothing at all, then by default, Python actually returns the special none value. And so it's going to replace the target, which is the list item we click on, with none. And that'll actually cause it to be deleted.
Because it's being replaced with nothing. So this is actually all one liner, which is kind of neat. So if we save, oh, we also need to add our delete here. There we go. So now save. So there's our delete. Let's try getting rid of one of our deletes. And actually, while we do, let's have a look.
All right. So delete. Okay. It was close. Oh, I know why that didn't work. And that's because also by default, it actually changes the contents of the thing that we're returning. And we don't want to change the contents of the list item. We actually want to change the whole list item.
So these are -- there's not very many core attributes in HTMX. But one of the core attributes is when you basically tell it what you want to happen with the result. And the HX swap is the attribute that we use. And one of the values for it is -- so it says the default is innerHTML.
So we're going to change it to outerHTML, which tells it to replace the entire target with nothing at all. So HX_swap equals outerHTML. Save that. And then we'll refresh. Okay. So let's now try clicking delete again. There we go. That worked nicely. So we've now got our toggle working and our delete working.
So we've got one key thing that we now need to add for it to be somewhat usable, which is the ability to create additional todos. So we've got our get. We've got our toggle. We've got our delete. So now we need a create. Okay. So let's put this here.
So what I tend to like to do is to look at the -- there's a couple of places you can look. One is to look at the Pico CSS documentation, if you're using the default Pico. We've got other style approaches you can use. But there's a component section. And you can kind of see like, okay, how do I want things to look?
Here's a form section. Input. Or the other thing you can do is on our docs, if I go to component extensions, we can kind of have a look. You can see that there's a number of examples here, along with like what they actually look like when they're rendered, which is quite convenient.
So I'm going to use this group approach. And it's useful to look at the source of these things. Because when you do, for example, group, you can see it's like literally a single line of code. Because that's what -- when you look at the Pico CSS docs, it says, oh, you have to create a field set and give it the role group.
So in some ways, even creating this group function is hardly necessary. But I think it's kind of nice to have these things directly available. Okay. So we're going to have a group with an input. And with a button. That sounds perfect. In fact, we might as well just copy that.
All right. So let's create a form. And that's going to contain our group. And so form generally doesn't call get, it calls post. Post generally means I'm creating something. And now, what do we want to do with that? So again, in this case, the target is going to be the to-do list itself.
So let's give that an ID. Jeremy, I said to give it an ID. ID. There you go. Right. We'll put our form at the top. And maybe we'll put that in a -- actually, we'll put the whole thing in a card. That can be quite nice. So we'll create a card where the header is the form.
And so you can see what a card looks like. So we'll have our little header. We'll have our body. Okay. Actually, the header, because it's a position -- so the header has to come after -- okay. We've got a card with an unordered list as the body. The header is going to be the form.
And when I save that -- okay. We've now got that at the top. So that's nice. We'll change this button to say add. And maybe this one, we're at a placeholder. That's looking good. Okay. So when we post this form, we're going to need, obviously, a post. And so the way that forms work is that they actually pass their data directly, basically, as headers.
And so we're going to be passed all the information we need for it to do. Now, how do we make sure that that happens? The trick is to make sure that the names of each of our elements are what we need. So in this case, we only really have one element, which is the input.
So if we give this the name title, then that means that it's going to know how to create a to-do with the correct title. Okay. So what we're going to do, then, is we've got this new to-do. We've got to add it to the database. And in doing so, it's going to return the added to-do.
So we go to dos.insert. That will return the newly added to-do. And where do we want to put it? 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 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. Instead, we want to insert it at the end. So if you look at the hxswap documentation, not hwswap, hxswap, there is a before end. Insert the response after the last child. That's exactly what we want. Okay.
So we're going to change our swap to before end. Okay. Save. Let's try that out. Hello. Add. Wow. Amazing. Done. Delete. That's so cool. Wow. Okay. So I think there's just one more thing I want to do before we wrap up, which is that, well, actually, while we're here, why don't we just have a quick look again at the dev tools.
We go to the network tab. Let's just see what this looks like. What does this look like? Add. And the answer is a request to slash. The method is post. And you'll see that it now has a payload, which is a form data that's added to the request. And the response, as per usual, is just the actual item that we want added.
And you can see behind the scenes, it's setting things like hx-target. So it knows what to do with it. Okay. So after I click add, I would like to delete the contents of this. So that's a little bit different. It's the first time we're saying that when we click this button, we want two things to happen.
We want the to-do to be inserted at the bottom. And we want the text box to be cleared out. So there's only one target. And the one target is the to-do list. And it's swapping to before the end. So we want to add kind of like a second target.
You can't do that here. Instead, what we have to do is an out of band swap. And that's where the thing we return, this post, has to return an additional piece of information, which is to say, change something else. So the thing we want to change is the text box.
We want to change the input box to basically have what it originally had, which is this. So a simple way to do that is we can factor this out into a function. So we'll call this make input. Okay. And we're not going to change it. We're just going to leave it like that.
I kind of prefer to put these things above where they're used. So that's how my brain works. Okay. So just check this still works. Yep, still working. Cool. And so we're going to add another new parameter here, another new attribute here, which is hx, hx, I think it's called hx swap oob, isn't it?
Yep, hx swap oob. So hx swap oob. And so when we do that, what it says is if you return this as a separate bit of HTML, then it says to find the thing with this ID and replace it. And actually, at the moment, we don't have an ID.
So we should add one. And actually, if we just change this name to an ID, FastHTML automatically adds a name to it as well. So let's just try that out. Refresh. 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 got a name of title.
So here's the trick. Rather than just returning the inserted to do, we also return our new input box. So we're returning two things. So if I refresh that now, we'll say, clear me, please. And we'll say add. And you can see there, look, it's added it and it cleared this out.
So what does that look like in the network? Add. You'll see that the request is just the same as it was before. And again, the payload's just like it was before. But the response now contains two things. It contains the thing to add to the end of the to-do list.
And it also includes our input with a placeholder and this special HX swap OOB. And so this is the thing that actually replaces the target ID. And then this thing replaces whatever has the same ID as it has. All right. And so now we have a to-do list. Not the most amazing one ever.
But, you know, we -- it's all in Python. We haven't had to write any JavaScript. We haven't had to write any CSS. And it's not too much code. All right. I think the last thing that I want to do then is to deploy this so that everybody can use our amazing to-do list.
So currently I think the easiest way to deploy fast HTML applications is to Railway. So this is Railway.app. To deploy it to Railway, you install the Railway CLI, which you can see their website for documentation on how to do that. And then here's our directory. Ah, yes. So there is one change you're going to have to make to have this work.
Which is it uses the data subdirectory of your -- Railway uses the data subdirectory of your folder for -- it's kind of where it mounts the persistent storage. Because it's going to mount a volume for us. So -- and the other thing we should probably do for production is to change live to false.
I'm not sure it matters too much. But we may as well. Don't need live reloading in a production app. And so we should change this now to data/to-dos. And let's just make sure that still works. Okay. Looks fine. That's good. So we can now deploy this. So once you've installed the Railway CLI, you type Railway login.
And I've done that before. So I'm all logged in. So at this point, all I have to do is type FH for fast HTML. Railway deploy. And if you add a dash H, you'll get the help. So the first thing it needs is the name of your project. So I'm just going to call this demo hello.
And then do you want to create a mount? We do. The default's true. So that should all be fine. So that's going to make sure that it stores our data for us. And Railway's quite neat. It doesn't, like, charge you anything other than the bytes you actually store. And it only charges you for the time that people are actually using your application.
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. All right. So I'll say deploy. And this takes -- it varies a bit. Sometimes, like, 30 seconds. Sometimes a minute or two. Okay. So that one's finished.
And so it gives me a domain that I can check out. So I'll copy that. Head back over here and paste it in. And there it is. And actually, it even copied our little demo database across as well. So that's why our data's already here. And there it is.
It's all working. Excellent. Okay. So I think that's everything I wanted to show you for today. Obviously, it's a very kind of brief, quick start. Creating, you know, having the ability to create any web application that's possible through HTTP, HTML, and JavaScript, CSS. It's a big territory to cover.
And we only covered a fraction of it today. But hopefully, it's enough to help you get started. And my suggestion is, as a next step, is to check out the many FastHTML examples. Because pick one that does something that you like, pull it apart, change it, and see all the secrets in there.
And over the coming weeks and months, we'll be creating a lot more tutorials and documentation to help you learn from. And if you do create anything interesting or have any questions or create tutorials of your own or so forth, be sure to let us know on the FastHTML Discord.
Or, of course, you can put an issue into our GitHub repo. Or if you have suggestions, you know, ideas in code for stuff that you think might be helpful, send in a PR is also most welcome. All right. Thanks, everybody, for joining me and signing off from Queensland, Australia.
Bye-bye.