In this Python FastAPI tutorial, we'll be learning how to use Jinja2 templates to create an HTML frontend for our API. Templates allow us to serve proper HTML pages to users while keeping our JSON endpoints intact for the backend API. We'll cover setting up Jinja2Templates, passing data to templates, using Jinja2 syntax for loops and conditionals, implementing template inheritance with a layout file, adding Bootstrap for styling, and configuring static files for CSS and images. By the end of this video, we'll have a nicely styled blog homepage that displays our posts. Let's get started...
The code from this video can be found here:
https://github.com/CoreyMSchafer/FastAPI-02-Templates
Full FastAPI Course:
https://www.youtube.com/playlist?list=PL-osiE80TeTsak-c-QsVeg0YYG_0TeyXI
✅ Support My Channel Through Patreon:
https://www.patreon.com/coreyms
✅ Become a Channel Member:
https://www.youtube.com/channel/UCCezIgC97PvUuR4_gbFUs5g/join
✅ One-Time Contribution Through PayPal:
https://goo.gl/649HFY
✅ Cryptocurrency Donations:
Bitcoin Wallet - 3MPH8oY2EAgbLVy7RBMinwcBntggi7qeG3
Ethereum Wallet - 0x151649418616068fB46C3598083817101d3bCD33
Litecoin Wallet - MPvEBY5fxGkmPQgocfJbxP6EmTo5UUXMot
✅ Corey's Public Amazon Wishlist
http://a.co/inIyro1
✅ Equipment I Use and Books I Recommend:
https://www.amazon.com/shop/coreyschafer
▶️ You Can Find Me On:
My Website - http://coreyms.com/
My Second Channel - https://www.youtube.com/c/coreymschafer
Facebook - https://www.facebook.com/CoreyMSchafer
Twitter - https://twitter.com/CoreyMSchafer
Instagram - https://www.instagram.com/coreymschafer/
#Python #FastAPI
Оглавление (8 сегментов)
Segment 1 (00:00 - 05:00)
Hey there. How's it going everybody? In this video, we're going to be learning how to use templates and fast API. Templates allow us to serve up HTML pages to our users while still maintaining our JSON endpoints for the backend API. So, we'll set up Ginger 2 templates, pass data to templates, use Gingu syntax for loops and conditionals, implement template inheritance with a layout. HTML. We'll add some Bootstrap for styling and set up static files for our CSS and images. So, by the end of this video, we're going to have a good-looking blog homepage that uh displays our posts in a nicely styled layout. All right. So, let's talk about why templates are important. So, in the last video, we uh returned some raw HTML strings uh with our home route here. And that works fine for something super simple. But once you need full HTML pages with headers, navigation, footers, and styling, then managing all of that in a Python string would be a nightmare. So that's where templates come in. So they let us write proper HTML files and just pass in our dynamic data. Now, on the other hand, our API posts uh return JSON, which is perfect for API access, but it's not something that most humans want to read directly in the browser. So, we need a better solution here. And that solution is going to be using templates. So, we're going to keep our API routes returning JSON, and we'll add page routes that return proper HTML using templates. So, both can use the same data, but they serve different audiences. So, let's do a quick reminder of where we're starting from here. So, we're currently within our main. py file, and this is what we created in the last video. And just to make sure that we're all on the same page, let me go ahead and run this app currently so that we can see what we're starting with. Uh, so I'm going to open up my terminal here. I'm going to kill the development server that I'm already running. I'm going to rerun that. I'm using uvun fast api dev main. py. Uh, that's because I'm using uv throughout this series. If you're not using UV and you're just using something like pip, you can just say fast api dev main. py. So I'm going to run that server. And now let's go back to our browser and reload our page here. And we can see that for our home route, we have that simple h1 tag with the first post title. And if we go to for slashi uh slpost, I'll make this a little larger here. Uh we can see that we get a JSON list of all of our current posts. and we just have those two dummy posts for now. All right, so now let's get started with templates. So the first thing to know is that if you installed fast API with fast API standard like we did in the last tutorial, then Ginger 2 is already included. You don't need to install anything extra. So Ginga 2 is the templating engine that Fast API uses and it's the same one that Flask uses also. So if you've ever worked with Flask templates, then this is going to look very familiar. Now if for some reason you didn't install fast API with fast API standard uh then you would need to install ginger 2 separately uh you can do that with a pip install ginga 2 uh or you can do that with a uv ginga 2. Okay so let's go back to our application here. So first we need to import uh what we need from fast API. So I'm going to add request to our fast API import because Ginger 2 templates uh require the request object and I'll also import ging 2 templates uh from fast api. templating. So from fast API here I'm going to add in request and also I'm going to uh say from fast api. mplating templating. I'm going to import ginger 2 templates. And we can actually remove the HTML response import uh since we won't be using that anymore now that we're using templates. So I'm going to go ahead and remove that. And now we need to create a templates directory in our project. So this is the convention in fast API and in Flask projects. So the folder should be called templates. So over here in my project directory, I'm going to create this new directory and I'm going to call this templates. Okay. So now that we have that templates directory, I'm going to close the sidebar for now. And now we need to tell fast API where to find our templates. So right after we create our app right here, I'm going to say templates is equal to and we're going to use that Ginga 2 templates that we uh imported. And I'm going to say that the directory is equal to templates. And that's the name of the directory that we just now created. So this creates a templates
Segment 2 (05:00 - 10:00)
object that knows to look in our templates directory for template files. Okay. So now let's create our first template. So inside of the templates folder, I'm going to create a new file here. And I'm going to call this home. html. And now I just want a basic HTML structure here. Uh I have a snippets file so that uh you don't have to see me type all of this out. Uh this is just a very simple uh HTML structure here where we have our title, an H1 tag that says homepage, and then just a paragraph tag that says that we are using a template. So it's nothing special yet, but we'll see how we can add to this later. So now let's update our home route uh to use this template instead of uh returning that raw HTML string like we were doing before. So now let's change this to use a template response. So first let's remove this HTML response uh that we have here since we are no longer using that import. So I will get rid of those. Now, one thing that we need to add to our actual route function here is a request parameter uh as an argument here because Ginga 2 requires that. So, within here, I'm going to uh accept this request and I'm going to pass that in as request. Okay. And now, instead of returning this HTML string here, we are going to return templates. late response. And then pass in that request and then the name of our template. For now, that is just home. html. So, real quick, let me just explain again what's going on here. We removed that HTML response uh response class since we don't need that anymore. Uh we added our request as a parameter here, which ginger 2 uh needs to work properly. And then we return uh templates. template template response with our request and the name of our template file. And we still have uh include in schema equal to false here on both of those routes which we learned in the last tutorial. This keeps uh these pages these page routes out of our API documentation uh at that for/doccks location. Okay. So now let me save this and test it in the browser. Uh let me make sure that our server is still running and it is. So now if I reload the browser here, now we can see that it's returning our HTML template. But right now our template is pretty static. It doesn't actually show our post. So let's fix that by passing data to our template. So in the template response, we can pass a uh context dictionary as the third argument here. So we have our request as the first one, template name as the second, and then the third we can pass in a context dictionary. And this dictionary is going to contain all of the variables that we want to be able to use in our template. So for this, it's going to be a dictionary. And one variable that I want to use is going to be called posts. And we'll just pass in all of our posts as the uh value for that variable. So now I'm passing in our post list to this template. And the template can access anything that's in this context dictionary. So now let's update our template to actually display the post. This is where Ging 2's templating syntax comes in. So to loop through items, uh we use this curly brace percent syntax. And to display uh variables, uh we do a double curly brace syntax. Let me show you this real quick. So right here, I'm going to replace our paragraph. And I'm going to do this curly brace uh percent sign. And this we can use a for loop. So I'll say for post in post. And now we need to end this for loop. So I'll do the same thing uh curly brace percent sign. And then I can say end for on that. Okay. And now within this we can use a double curly brace uh to access some attributes. So I'll just create an H2 tag here and I'll use these double curly braces here and we have that post variable since we are looping over those post. So now I can just say posttitle and then for the body sorry my HTML is auto formatting here whenever I save. So I'll try not to save until I'm done here. So now for the body I'll use just a paragraph tag and again we are going to uh use this double curly brace and
Segment 3 (10:00 - 15:00)
we'll just say post. content within those paragraph tags. So this curly brace percent syntax here for loop loops over our post. We pass these posts in as the context right here. And then within this for loop we are using these double curly braces and we can access uh those values from that dictionary. Now something that might look a little strange if you're new to templates is this dot notation here. Uh we're using uh post. title and post. content even though post is actually a dictionary. Uh, Ginga 2 lets us access uh, dictionary keys using dot notation, which is just a more clean way to do it within our templates. So, let me go ahead and save that. And now, let's go back to our browser and refresh this. And now we can see that both of our posts are displayed on the page. So, each post shows its title and then we have a paragraph tag here with the content. So, now we're dynamically rendering our data now. And now let me show you conditional statements in Ginga 2. So these are useful for things like displaying different content based on whether a value exists. So a common use case uh is setting the page title for example. So let's update our page title here in the template to show a specific title if one is passed in or a default one if one is not passed in. So here where we have title, I'm just going to uh cut out that fast API blog. And now I'm going to use this syntax again here. So curly brace percent sign and then this is going to be an if conditional. So I'll say if title and then we will end this if here. So here I will say end if and now within this conditional we can paste in that if we have a title then we want this to be fast API blog then we'll just do a dash double curly brace and then display that title after that. Now I actually uh want an else statement here as well because I also want a default value. So if we say else right here, now we can pass in a default value. And I'll just have fast API blog be that default value there. So now if we pass in a title into the context of the template, then it will show fast API dash with the title. If we don't, then it'll just default to fast API blog. So now let me go update our uh main. py py file here and let's pass in title here to our context. So I'll just put in a comma there and we will say title and pass in something like home since this is the home route and let me go ahead and put a comma here so that it uh displays a little bit better. Okay, so now if I save that our server should still be running and updating. So now if I reload the page here, this might be a little difficult for you to see, but up here in my browser tab, it says fast API blog-home. Uh so that is working correctly. So that's pretty much the basics of Ginger 2 templating. We have for loops for iterating, uh double curly braces for displaying values, and if statements for conditionals. So these are the building blocks that you're going to use constantly in your templates. So now let's talk about template inheritance. So this is a really powerful feature that will save you a lot of code duplication. So right now if we wanted to create more pages like um an about page, a single post page, then we would have to copy this entire HTML structure to each template. So we'd have to copy this head section, um any of our navigation, our footers, all of that would be duplicated. If we did that and then wanted to change a navigation link, then we would have to update that on every single template and that just wouldn't be maintainable. So template inheritance solves this by letting us create a parent template with the common structure and then the child templates just fill in the parts that are different. So let me create a layout. html template here in my templates folder. So I'll call this layout. html. And now I want to use this as our parent template. So I'll start by taking the structure from our home. html since it has uh pretty much everything in there. And I'll modify it here to be a parent template within the layout. So if we look at this template here, um all of this stuff here is going to be the same on every page. We're just going to have
Segment 4 (15:00 - 20:00)
our head with the title, things like that. Now the body, this is what's going to be different per page. So I am going to uh get rid of our body here. And now we can create something called a block. So I'm going to create a block here. And again it's just this curly brace percent sign uh syntax. So I'll say block. I'll call this content. And now underneath this we are going to use the same syntax here again. and I will say end block content. So what this does is it defines a section called content that the child templates can override. Uh you can have multiple blocks with different names if you need to. So you could have like a sidebar block or a scripts block. Uh but for now we just have our one content block. So now let me update home. html HTML to extend this layout instead of having its own full HTML structure. So within home. html, we're going to use this layout. html and it's going to reuse all of this. And then all we are going to do is fill in this content block. So within here, all we want to do is keep what's here in the body. And actually, I'll get rid of this H1 tag as well. And we'll just keep our post. So, I'm going to get rid of that H1 tag. And now all this other stuff, too, because all of that is in our layout. So, I'll get rid of all of that. And now, let's extend that layout. html. So, I'll use the same syntax that we've been using. And I can just say that this extends layout. html. And now we have to tell that layout template uh what section we are overwriting. So we are going to set the content block. So I'll say block content. Now I also want to show where this uh block ends. So down here I will set another one and I will say end block content. And now we just have that for loop inside of that content block. So now HTML uh home. html is much simpler. Now it just extends that layout. html and just define defines what goes in that content block. Uh the full HTML structure, the head section with the conditional title, all of that comes from the layout. So if I save this, if I go back to our browser, if I run this, uh we can see that still works. I got rid of the uh H1 tag. So that's why it looked like something changed there. But if I actually go to view the page source here, uh then we can see that we have the full HTML structure. So that's working well. So now if we wanted to create another page, then we would just extend that layout again and define our content block. And that's all we'd need to do. And if we wanted to add navigation or change something in the overall structure, then all we need to do is update this layout. h HTML uh file once and it affects all of the pages that inherit from it. All right, so we've basically learned the basics uh but our page still looks very plain. So let me show you how to add some styling. So I'm going to use Bootstrap since I think that is very simple for most people to use and understand, but you could use Tailwind or any other CSS framework that you're comfortable with. I'm also going to use some custom CSS that I've written. I've prepared some template files that we'll use for the rest of this series. Uh they use the same concepts that we've already learned, conditionals and for loops, but just with more of a complete HTML structure and styling. So now I'm going to replace our simple uh layout. html here with a more complete version. And I have a file that I've moved over into my project here called layoutfinish. html. So, let me open this and copy its content. And I will copy and paste this into layout. html. And I'll go over all of this. Don't worry. And again, all of this is going to be available to download. And I'll have links in the description section below if you want to follow along with this code exactly. Okay. So, this is a lot more code, but let me scroll through here and point out the key parts. So up here in the head section, we have our conditional title like we already had. And now we have some open graph meta tags. These control how your site appears when shared on social media. I include these in every site since I just use these as a starting point. If you wanted to use these too, then you could fill in those uh content attributes later when your site is deployed. So we are also loading in some custom fonts
Segment 5 (20:00 - 25:00)
from Google Fonts. This just gives us a nicer looking font than the browser defaults. And here we are loading in the uh Bootstrap CSS from a CDN. Like I said, I think Bootstrap is uh something that's easy for beginners and most people are already familiar with it, but you could use Tailwind or any other framework that you prefer. So down here we are loading in a custom uh CSS stylesheet. Now, this is loaded in from for/static CSS. Uh, now we haven't set up our static directory yet. Uh, so this isn't going to work right now, but we'll fix that in just a second. So, then if I keep scrolling down, we can see more static files. Uh, we just have some favicon links, uh, which are icons that appear in the browser tab. And here in the body, we have a navigation bar using uh, Bootstrap classes. So, we can see that we have a navbar class here. Uh, navbar expand for um responsive layouts uh that comes with uh Bootstrap built in. And a lot of these are just Bootstrap classes that style everything. It looks like a lot, but it's just for styling purposes. So, if I scroll down here, where did it go? Oops, I haven't got there yet. If I keep scrolling down uh down to our body, then we can see that somewhere we have our content block. And okay, I'm blind. Where is it? The content block. It's going to be in our main section. Oh, it's right here. I was scrolling right past it the entire time. So, this is our content block here where our child templates are going to fill in their content. And then we have a sidebar here that just has some placeholder content. You can do with that whatever you want. Um, at the bottom here, we have a footer that's just setting a copyright. We're using JavaScript to fill in that year automatically. And then we also load in any uh JavaScript for Bootstrap here. And then we have a simple uh dark mode toggle between uh light and dark themes because it is 2026 and we got to have dark mode these days. So that's for our layout. Now for our home. html, let me also update that with a complete version. So I also have this homefinish. html. Let me update that. You can see that this is a lot less here than we had for the layout. So here I'm going to paste this in. Now let's look at this. So this is much simpler and it's the same concepts that we were looking at before. We're just extending the layout. We are creating this content block here. We're then looping over our post and then we just have some more HTML elements to be more syntactically correct uh and adding some styles to that. And then you can see here that we uh have the post author and the date that it was posted, the title, the content, things like that. Okay. So, if I save this now, if I reload this in the browser right now. Okay. Actually, I had a point to make and that shouldn't have worked. I think it's because from where I showed the There we go. Okay. So, I had to do a hard refresh there from where I showed you the complete site in the first video. It was caching that CSS. So, I did a hard reset and now it can no longer find our CSS files because we haven't mount mounted our static directory yet. And we should be able to notice this. If I reload the page here again, then I should be able to go back to our code here. And if I look at the latest request uh down here in our development server, we can see all these 404s. And that is because it's looking in the static directory here and saying, "Hey, I can't find this CSS file. icon. I can't find this picture. " Um, all of those are returning 404 not found. So, let's go ahead and set up uh those static files. So, static files are things like our CSS, JavaScript, uh images, icons, uh things that don't change dynamically. They're just served as is. So first let me show you the static files that I've prepared here. So I have a static finished directory here. Let me actually open this in finder and uh show you what this looks like. I'll make this a little bit larger here. So here we are in our project directory. I have this static finished folder here that I've dropped in. And within here we have CSS. It just has one file that is main. css. Uh we have some JavaScript. This is just an empty utils. js right now. Uh nothing's in there. We have some icons that we're
Segment 6 (25:00 - 30:00)
using for the site. You can put your own icons in there. Um and for profile picks, this is just a default picture here that we're using uh for all users so far until we get a database set up. And then I also have a site uh manifest file there. You don't have to worry about that. That's for uh basically progressive web app support. That might be overkill for a tutorial that I've added those in, but it just gives you a complete starting point. Uh, if you want to use this for your own project, these are basically the files that I use anytime I'm creating a new site. So, I figured I'd just add them all in here uh for you all to use. So, you can look up what a siteweb manifest is on your own time if you want to update that. But now, let's use this as the static folder for our website here. So, I'm going to rename this to static instead of static finished. And now, let's go back to our page here. And now, let me go ahead and close down the sidebar there. Uh, now we need to tell fast API to serve that static directory. So, in main. py, I need to import static files and mount the static directory. So up here at the top I'm going to say from fast API static files and I'm going to import uh static files. Okay. And now down here below our app I'm going to go ahead and mount this static directory. So I'll say app. mount and I'll just say forward slashstatic. I'll explain what all these are here in just a second. uh but for the uh second argument here it's going to be static files. I'm going to pass in the directory here and that is going to be directory of static and then lastly we'll have a name here and I'll set the name to static as well. So this mount method takes three arguments. So the first one here is the URL path where these static files will be accessible. Uh the second one is a static files instance here that's pointing to our static uh folder, our static directory. And the third one here is a name that we can use to reference in our templates. So now any file in our static folder is accessible at for/static in the browser. So if I have main. css CSS that's within a CSS directory that's within the static directory then that'll be accessible at for/static/css/main. css. So let's go ahead and take a look at that. So let me go ahead and do a hard refresh here again. Now this is working and it should be working this time. It's not just a cached version. So now we can see that this is looking a lot better. Uh we have a navigation bar here at the top. Our posts are nicely styled. Uh we have our filler sidebar here and the dark mode toggle here that we can use to switch between uh light and dark or just set it to auto. So this looks nice and the files are loading correctly. But let me show you one more thing about static files and navigation. So right now our navigation links are using um hash symbols for navigation. So to show you what I mean, let me go to the layout here. Uh so if I scroll up to the top, then for our navigation here, uh we are just using these hash symbols here for the href. And that just makes it a dead link. It's just a placeholder. So we're going to update these to use URL 4, uh which is the proper way to generate URLs in templates. So there's two different use cases for using URL 4 in templates. So first for route links like navigation and second for static files like CSS, JavaScript and images. But the benefit of using URL 4 is that if you ever change your routes or change the mount path from for/static to something else, then all the links will update automatically. Uh it's more flexible and follows best practices. So, right here in our navigation of our layout instead of just that hash placeholder, I'm going to change this and I'm going to use those uh double curly uh braces and I'll say URL 4 and we will just say that this is the URL for home. And now let me copy that. And I'm also going to set that as the home link as well. So when we're using URL 4 on a specific page, uh you can see that we're passing in the function name there. If I go back to main. py, uh this home here is what we are passing in as that URL for. Now I also want to update the static file references to use URL for as well. So if I go up and uh
Segment 7 (30:00 - 35:00)
look at my CSS here, we can see that currently this is hardcoded in with static CSS main. css. So this is going to be a little bit different here. So I'll paste in what we had before. I'll still leave that hard path right there. So I'll say URL for static. Okay. And now inside of the URL function there, I'm going to say that the path within static is equal to and we already have static there. So now we just need this second part here, which is the CSS directory and then main. css. And now I can get rid of that uh hard-coded path there. So now I'm going to go through and I'm going to upgrade or update the rest of these uh static hard-coded URLs here. I don't want to waste your time. So I'm going to do that really quick and then fast forward here. Okay, so I updated all of those five icons there uh to use this URL for static. Um now in the home. html HTML. We have a static file as well. It is the uh profile picture for the user. And right now we're just using the default. So let me replace that as well. So that goes to profile pictures default. jpeg. And then I'll get rid of that. Okay. And also in our layout. html, HTML. Uh we do have a login and a register route here as well. Those are right here, but I'm going to you uh leave these as placeholders for now because if you use URL 4 on a route that doesn't exist, then it's going to throw an error. So, we'll add those routes later in the tutorial. So, now if we save everything and then uh test this. So, I will save that. go back to the browser here and load this up. What is the issue that we are having? Oops, some of you may have seen this, but it looks like I forgot to put the uh path on some of these. So, uh actually on a lot of these. So, what I need here is path equals. So, sorry about that. So, path equals as that second argument. And I probably did that here too as well, didn't I? So, let's try to rerun this again. Now, it looks like it's working. Okay, so it was able to find all those uh static files and everything uh with our URL uh for function in those templates. Now, one thing I want you to notice here, whenever I click home, uh I don't know if you can see here, but it's actually going to forward slashpost. So if I reload this, uh, click on either of these, it's going to for/post. Okay, now both routes work. If you remember, the post route and the root are basically the same thing. That's not our API post route. Uh, but why is it doing that? Why does URL for home uh go to post? And what if I wanted that to go to the root instead? So this is happening because if you remember in our main py here uh our home route has two decorators pointing to the same function. By default fast API uses the function name as the route name. But since both routes are attached to the same function and that name isn't unique. Uh so URL 4 just ends up going with this second route here uh at post. But we can actually fix this by giving each route an explicit name. So with uh this right after include in schema is equal to false I can say name is equal to home for this root route here. And then I'll set name is equal to post for this for/post route there. So now this for/root route is explicitly named home and the post route has its own name. Uh so now uh when we use URL 4 it will use our explicit names here. So now if I save that and rerun this here in the browser and if I click on home we can see that goes to the root of our website. Okay. So now let me do one final check to make sure that everything is still working here. So let me uh go back. Let's reload that. That looks good. Let's check out our uh post API import uh endpoint. Sorry. Um, so that's still looking good. And in our docs, if I reload this, then we can see that we're still just getting that one API route and that the home routes are not showing up there because we have that include in schema equal to false. So we still have a clean separation there. So let's recap what we did here. So we
Segment 8 (35:00 - 37:00)
created this templates directory and we configured fast API to use that and then we passed data to our templates. uh through this context dictionary here and we use the Ginga 2 syntax with for loops for iterating, double curly braces for displaying variables and if statements for conditionals. We implemented template inheritance with layout. html as the parent template so that we don't have to repeat our HTML structure everywhere. We added in our own custom styles and bootstraps for responsive styling. And we also set up static files in our static directory and that's what serves up our custom CSS uh JavaScript which we don't have yet but we'll be adding later in the series images and icons and things like that. Uh we used URL 4 for generating URLs to uh both our routes and the static files. And now we have a good-looking front end with uh an HTML display for our data. and that's split off from the JSON backend that we're going to use for our API. Now, in the next video, uh we're going to learn about URL parameters and how we can use those to grab specific resources from our data. So, for example, instead of returning all of our posts, so here we can see that we're returning all of our posts at once, uh instead we can use path parameters to grab a single post instead. So, we'll create both an API endpoint and a template page for viewing individual posts and make the post on our homepage clickable. And we'll learn about type validation with proper error handling as well. But if anyone has any questions about what we covered in this video, then feel free to ask in the comment section below and I'll do my best to answer those. And if you enjoy these tutorials and would like to support them, then there are several ways you can do that. The easiest way is to simply like the video and give it a thumbs up. Also, it's a huge help to share these videos with anyone who you think would find them useful. And if you have the means, you can contribute through Patreon or YouTube. And there are links to those pages in the description section below. Be sure to subscribe for future videos. And thank you all for watching.