Python FastAPI Tutorial (Part 8): Routers - Organizing Routes into Modules with APIRouter

Corey Schafer · Beginner ·🔧 Backend Engineering ·5mo ago

Key Takeaways

This video tutorial series covers the use of FastAPI and API routers to organize routes into modules, with a focus on improving internal organization and maintainability. Specific tools and frameworks demonstrated include FastAPI, APIRouter, and Python.

Full Transcript

Hey there. How's it going everybody? In this video, we're going to be learning how to organize our Fast API application using routers. Now, if you've been following along with this series, then you know that we've built up a lot of functionality. In tutorial 6, we added full CRUD operations for post and users. And in tutorial 7, we made everything faster with async and await. Now, before we add more features like forms and authentication, we're going to take a step back and organize what we have. So this is a common pattern in real world development. You build up some functionality, then you polish and organize before adding more complexity. So let's go ahead and get started here. So let me show you what I'm talking about. So we have our main.py file open here. And if I scroll through this, then you can see that this is already super long. So, we've got our imports, we've got our lifespan, we have our app setup, we have our template routes, we have our user routes, we have our API routes and exception handlers, all of that mixed together. Now, all of this works, but if we keep adding features, then this file would become a nightmare to maintain. So, in this video, we're going to reorganize using API routers. Now, if you've ever used Flask, this is similar to blueprints. So, we'll create a router directory and then move our user routes into one file, move our post routes into another, and then update main. py to include those. And when we're done, everything works exactly the same from the outside. So, the same URLs, same functionality. We're just improving the internal organization here. So, what is an API router? So, it's fast API's tool for organizing routes into modules. So instead of defining all of our routes on the main app object, we're going to define them on a router and then include that router in our app. So the basic pattern is pretty simple. In a separate file, we create a router with router equals API router. And then we define the routes using router.get and router.post instead of app.get and app.post like we have here. Then in our main file, we use app.incclude include router to connect it and that's going to give us a lot of flexibility and we can apply common prefixes to a group of routes, apply tags for documentation and keep related code together. Okay, so let's go ahead and get started and organize everything that we have here. So I'm going to create a new directory in my project directory here. And I'm going to call this routers. And within routers I am going to create this file that is double_anitouble_.py. And that file will make this a proper Python package. Now that's just going to remain empty. No code is needed there. It's not strictly required in modern Python, but it helps tools like liners and type checkers. So it's still a best practice. So now we're going to create two router files within this routers directory. So I will create a file here called users. py and then I will create a new file called post.py. And now let's start with our users route here. And I will get rid of the sidebar there. So first I'm going to add the imports. Now, another good thing about splitting these up is that we can add only the imports that we need for users instead of everything else that's currently in our main. py file. So, I've got these prepared in my snippets to save some time. Basically, I've just plucked out what's required for the users here. So, I'm going to grab all of our imports that are just specific to the users. Now one thing to point out here is that I also included this import from fast API called API router but we also have our database dependencies models and the schemas that we need. So now since this is a router I'm going to create our router instance. So I'll just say router is equal to and this will be API router and this is what we're going to decorate our routes with instead of our app. So now let's move the user endpoints from main.py py. Now, I think the best way to organize this uh what we currently have is to only move the API routes, which are the ones with the path starting with for/appi slash users. So, let me find those here. So, those start here. Now, I'm not going to move the template routes into our routers like the homepage, the post page, the users post page. I'm going to keep those in main. py. That way main.py can be for our front-end views and these API routers can be for our API routes. So let me just move over one API route for now and we'll see what changes. So I will grab uh this route here and paste this in. So I've currently moved over our create user route. Now when moving these two things are going to change. So first instead of app.post post here that becomes router.post post that router instance that we created there. And second, the path is going to change here. Instead of forward slash API slash users, I'm just going to use an empty string here. Now, using an empty string might look a bit odd, but here's why we're doing that. So, the path in the router is relative. So, when we include this router, we're going to specify a prefix of for/appi slash users. So the router's empty string actually becomes that prefix of for/ API/ users. So let me just type this out so that you understand what's going on here. So in main.py we're going to create this with a prefix of for/ API slusers. So now within users py we're going to have an empty string here. So, whenever these are put together, it's going to end up with a route uh that just looks like this. Since this is an empty string, now if I was to put a forward slash here and then we would have a forward slash here, it would add to that prefix and then we would have a trailing slash there on the end. Now, having that trailing slash is fine, but I just don't want it. Uh, it can sometimes cause issues with like 307 redirects and things like that, but I like the empty string anyway. I like uh having those non-trailing slashes there. So, clean paths and also no redirects. But that is all that it takes to move a route over to a router. All we changed was this from app.post to router.post. And we changed this from/appi/ users just to an empty string. Everything else stays the same. We have all the async await stuff. We have the database logic. We have the validation. None of that changes. Only the decorator and the path. So now let me move the rest of the API user endpoints. And I'll grab them all at once here. So I'm going back to our file here. Now I've already moved create user. So I'm going to remove that. And now I'm going to grab from get user all the way down to delete user. So let me find that. And that's there. So I'll cut that out. I will paste this in to our router here. And we've already done this one here. Now I'm just going to change the rest of these. So this will be router.get. And instead of for/appi slash users, this is just going to be forward slash user ID. So we'll remove that prefix that we're going to add. And I will do this for basically every route. So there's not too many of these here. So I'll keep making these changes. And we have one more here. And this will be router. And that will be for/ user ID there as well. So let me make sure that I did all of those correctly. That looks good. That looks good. And that looks good. So that's all of our user API endpoints that are moved o over to the users router. So now let's do the same for post. So I've already created this post. py uh within routers here. I'm also going to add the imports that we need from my snippets here. So let me copy this and within post.py I will add the imports that we need. Let me make sure that all those imports were used. They were okay. So this is a similar setup but with post related schemas and I also already have the API router instantiated here. Now I'll move all the post API endpoints from main.py. So it's the same pattern here. So from get post I am going to copy from here all the way down to delete post. So all the way to here we will cut those out and we will paste these in here. Go up to the top. And now I will go ahead and change all of these. This is going to be router.get. We are going to make this an empty string now because that is going to be our prefix. And we'll see what that prefix looks like here in just a second. Again, I will change that to router. Remove that. Change to router. Leave this forward slost ID there. That still needs to be tagged onto our URL. Router. Leave that forward/post ID. And router here. And leave that forward slashpost ID. And this is the last one here. Router. and leave that forward slashpost ID. Okay, so now we have all of our post API routes moved into this routers post. py. We have all of our user API routes moved into this users. py. Now let's update main.py to include these routers. So first I'll save that to do some auto formatting there. Now if we go to the imports, we can see that we're no longer using any of these schemas. So I'm just going to delete all of those. All of those schemas are now being used inside their individual router. So now let's add the routers import here. So I'll say from routers we want to import and let me spell that correctly. We want to import posts and we also want to import users. So just to show you in our sidebar how we've set this up. This is our package here. So we're saying from routers uh import post which is this post. py and import users which is this users py and now let me also make sure that we're still using everything else in our imports here and we are so we've removed everything uh that we can remove there a lot of those imports still stay because uh our template routes still use those now after the template setup here so after I have templates equal to this uh templates directory let's include the routers that we just created so to do this I can to say appinclude router and now we are going to say users router and now we're going to pass in that prefix and the prefix that we are going to use this is going to be for slapi slash users and we are also going to do something here that is called tags and a tags is going to be a list and we will pass in users here as a tag and now let me copy this and I'll do the same thing for post. So I'll say include post.outer. We want this prefix to be/appi/post. And for the tags we will do post as well. So let me explain what's happening here. So app.incclude router connects the router to our app. The prefix parameter adds that URL prefix to all routes in the router. So the router's empty string becomes basically the prefix and something like a forward slash user ID becomes forward slash API/ users slash user ID. So basically everything that we have here is getting appended to here in terms of the final URL. Now the tag parameter organizes the for/doccks page. It creates collapsible sections. So all of our user endpoints appear under a users header and all post endpoints are under a post header. Without tags, they'd just be in a flat list. So this helps nicely with organization as well. Now, one thing to watch out for is fast API uses function names as route names by default. So if you have a router function name with the same name as a template route, then it can conflict with URL 4. So for example, we have a function named home here. If I had a function named home in the router as well, then it might conflict with our template route named home. Here our solution that we have in place is just to use unique descriptive function names. And we already have that in place here. We're using nice descriptive function names for all of our routes. Uh but that's just something that you want to be on the lookout for. Don't name functions the same thing throughout different routers. So now let's verify our main.py structure here. So at this point it should contain imports for our routers. Here we have our lifespan function. We are creating our app. We have our static and media mounts here. We are setting up our templates. We're including our two routers. And then we have all of our template routes. These are basically our HTML view of our API. And we can see that this file is a lot less crowded now. And down here at the bottom, we still have our exception handlers as well. So all of those API routes are now gone from our main. py file. Now, one thing to point out here is that even though we changed a lot, all of the URLs are exactly the same and the responses are exactly the same. So, the functionality of our site is identical. We just moved where the routes are registered from the app object to the router object. It's purely internal organization. So now, let's verify that by running our server here. So, I'll run the server. And now, let's go to our homepage here and reload this. We can see that that still works. Let me try an API route here. The API routes still work. Now, if I go to the docs here and reload this. Now, this is one benefit that we got here because if we look at our documentation, our endpoints are now organized into groups. Here we have our users and these can be collapsed and we have our posts and these can be collapsed as well. That is what we get from the tags that we specified in those routes. So let's test that the functionality is all still working properly. So we have API users create user here. Let me go to try it out. I will create a test user and we will do test user atample.com. I will execute that and we can see that it created a user with a username or with a user ID of two here. So now let's collapse our users there and now let's create a post with that user. So I'll go to try it out and we will say testing routers router content and the user ID was two for that newly created user. I'll execute that and we can see that it created that uh post and we also get the author information in here. And let me make sure that that looks good on the front end as well. And it does. So comparing to what we had before, we can see that our main.py, we're down to less than 150 lines of code here. And with our individual routes, these are about 150 themselves as well. So that's a lot better than having all of that shoved into just one file by itself. And this is a pattern that you're going to see in pretty much any production fast API application. So the benefits we get a separation of concerns obviously. So post logic lives in post user logic lives in users and app configuration and our front end live in main. py. Now I could even split off the front end if we wanted if the app grew even larger but I'm going to keep it in main. py for this series since it's not that much code. And this makes it a lot easier to find code as well. So, for example, if I'm looking for, you know, where is the create post endpoint? Well, instead of scrolling all the way through main.py and trying to find that, I know that that's going to be in post. py. And this also gives us a scalable structure as well. So, let's say that we wanted to add admin endpoints for administration duties. Well, we just create an admin. py uh file here within routers. and then each router would follow the same patterns that we've set up here. It's also better for teams so different developers can work on different routers and there would be fewer merge conflicts there. And there's also some testing benefits as well. So you can test routers individually and mock dependencies per route. Now there's different ways to organize routers. We organized by resource here which is common. So we have users py and post py. You could organize by version. So we could do v1 users py v2 users py. We could access by level. So we could do a public py versus admin. py or we could do it by feature like a billing. py if we had billing routes. But however you do it, the pattern is going to remain the same and it should help you organize your code. And that is what we're going to be using for the series going forward. Now, in the next video, we're going to add front-end forms with JavaScript. Now that our code is organized, we can build forms to create, edit, and delete post using the fetch API to interact with our clean API structure that we have here. And then we'll tackle authentication. So, only users can edit their own post on the front end and in the API. And our organized code that we've set up here will make adding those features a lot easier. But I think that's going to do it for this video. Hopefully now you have a good idea how to organize fast API applications using routers. This is a fundamental pattern that you'll use on any real world project. In the next video, we'll build front-end forms with JavaScript that talk to our API. But if anyone has any questions about what we covered in this video, then feel free to ask in the comments 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.

Original Description

In this video, we'll be learning how to organize our FastAPI application using APIRouter. As our app has grown throughout this series, our main.py file has become long and difficult to maintain. We'll fix that by creating a routers directory and splitting our API routes into separate modules—one for users and one for posts. This is a common pattern in real-world FastAPI development and is similar to Blueprints if you're coming from Flask. By the end of this video, you'll know how to structure your FastAPI projects for better maintainability and scalability. Let's get started... The code from this video can be found here: https://github.com/CoreyMSchafer/FastAPI-08-Routers 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
Watch on YouTube ↗ (saves to browser)
Sign in to unlock AI tutor explanation · ⚡30

Playlist

Uploads from Corey Schafer · Corey Schafer · 0 of 60

← Previous Next →
1 Web fonts using CSS Font Face
Web fonts using CSS Font Face
Corey Schafer
2 Using Font Awesome in Desktop Applications (OS X)
Using Font Awesome in Desktop Applications (OS X)
Corey Schafer
3 Sublime Text 2: Setup, Package Control, and Settings
Sublime Text 2: Setup, Package Control, and Settings
Corey Schafer
4 ArcGIS API for JavaScript Part 1: Our First Web Map
ArcGIS API for JavaScript Part 1: Our First Web Map
Corey Schafer
5 Mac Tip: Windows' Snapping Feature on Mac with HyperDock
Mac Tip: Windows' Snapping Feature on Mac with HyperDock
Corey Schafer
6 Linux/Mac Terminal Tutorial: Creating Aliases for Commands
Linux/Mac Terminal Tutorial: Creating Aliases for Commands
Corey Schafer
7 ArcGIS API for JavaScript Part 2: Starting Templates
ArcGIS API for JavaScript Part 2: Starting Templates
Corey Schafer
8 Paver Patio Time Lapse
Paver Patio Time Lapse
Corey Schafer
9 Mac Tip: Ways to perform Screen Capturing and Screenshots
Mac Tip: Ways to perform Screen Capturing and Screenshots
Corey Schafer
10 WordPress Plugins: Imsanity
WordPress Plugins: Imsanity
Corey Schafer
11 WordPress Tips: Test your theme with Theme Unit Test and Monster Widget
WordPress Tips: Test your theme with Theme Unit Test and Monster Widget
Corey Schafer
12 Sublime Text 3: Setup, Package Control, and Settings
Sublime Text 3: Setup, Package Control, and Settings
Corey Schafer
13 Understanding Binary, Hexadecimal, Decimal (Base-10), and more
Understanding Binary, Hexadecimal, Decimal (Base-10), and more
Corey Schafer
14 Mac Tip: Adding Folder Stacks to the Dock
Mac Tip: Adding Folder Stacks to the Dock
Corey Schafer
15 CSS Tips and Tricks: Add External URLs to Print Stylesheets
CSS Tips and Tricks: Add External URLs to Print Stylesheets
Corey Schafer
16 JavaScript Arrays: Properties, Methods, and Manipulation (Part 7 of 7)
JavaScript Arrays: Properties, Methods, and Manipulation (Part 7 of 7)
Corey Schafer
17 JavaScript Arrays: Properties, Methods, and Manipulation (Part 1 of 7)
JavaScript Arrays: Properties, Methods, and Manipulation (Part 1 of 7)
Corey Schafer
18 JavaScript Arrays: Properties, Methods, and Manipulation (Part 5 of 7)
JavaScript Arrays: Properties, Methods, and Manipulation (Part 5 of 7)
Corey Schafer
19 JavaScript Arrays: Properties, Methods, and Manipulation (Part 4 of 7)
JavaScript Arrays: Properties, Methods, and Manipulation (Part 4 of 7)
Corey Schafer
20 JavaScript Arrays: Properties, Methods, and Manipulation (Part 3 of 7)
JavaScript Arrays: Properties, Methods, and Manipulation (Part 3 of 7)
Corey Schafer
21 JavaScript Arrays: Properties, Methods, and Manipulation (Part 2 of 7)
JavaScript Arrays: Properties, Methods, and Manipulation (Part 2 of 7)
Corey Schafer
22 JavaScript Arrays: Properties, Methods, and Manipulation (Part 6 of 7)
JavaScript Arrays: Properties, Methods, and Manipulation (Part 6 of 7)
Corey Schafer
23 Python Tutorial: if __name__ == '__main__'
Python Tutorial: if __name__ == '__main__'
Corey Schafer
24 Sublime Text Quick Tip: "Go To Definition" Click Shortcut
Sublime Text Quick Tip: "Go To Definition" Click Shortcut
Corey Schafer
25 How to quickly create favicons for the desktop, Apple/Android devices, tablets, and more
How to quickly create favicons for the desktop, Apple/Android devices, tablets, and more
Corey Schafer
26 Easily Resize Multiple Images Using Picasa
Easily Resize Multiple Images Using Picasa
Corey Schafer
27 Easily Resize Multiple Images Using the Mac Terminal
Easily Resize Multiple Images Using the Mac Terminal
Corey Schafer
28 Python Tutorial: virtualenv and why you should use virtual environments
Python Tutorial: virtualenv and why you should use virtual environments
Corey Schafer
29 Python Tutorial: pip - An in-depth look at the package management system
Python Tutorial: pip - An in-depth look at the package management system
Corey Schafer
30 Git Tutorial: Using the Stash Command
Git Tutorial: Using the Stash Command
Corey Schafer
31 How Software Engineers, Developers, and Designers can volunteer their skills
How Software Engineers, Developers, and Designers can volunteer their skills
Corey Schafer
32 Git Tutorial: Diff and Merge Tools
Git Tutorial: Diff and Merge Tools
Corey Schafer
33 Git Tutorial: Change DiffMerge Font-Size on Mac OSX
Git Tutorial: Change DiffMerge Font-Size on Mac OSX
Corey Schafer
34 Sublime Text Quick Tip: Launch Sublime Text from the Terminal
Sublime Text Quick Tip: Launch Sublime Text from the Terminal
Corey Schafer
35 Python Tutorial: str() vs repr()
Python Tutorial: str() vs repr()
Corey Schafer
36 Programming Terms: DRY (Don't Repeat Yourself)
Programming Terms: DRY (Don't Repeat Yourself)
Corey Schafer
37 Programming Terms: String Interpolation
Programming Terms: String Interpolation
Corey Schafer
38 Programming Terms: Idempotence
Programming Terms: Idempotence
Corey Schafer
39 Python Tutorial: Namedtuple - When and why should you use namedtuples?
Python Tutorial: Namedtuple - When and why should you use namedtuples?
Corey Schafer
40 Programming Terms: Mutable vs Immutable
Programming Terms: Mutable vs Immutable
Corey Schafer
41 Python Tutorial: Else Clauses on Loops
Python Tutorial: Else Clauses on Loops
Corey Schafer
42 Overview of Online Learning Resources
Overview of Online Learning Resources
Corey Schafer
43 Mac OS X Terminal Tutorial: Time-Saving Keyboard Shortcuts
Mac OS X Terminal Tutorial: Time-Saving Keyboard Shortcuts
Corey Schafer
44 Git Tutorial for Beginners: Command-Line Fundamentals
Git Tutorial for Beginners: Command-Line Fundamentals
Corey Schafer
45 Quickest and Easiest Way to Run a Local Web-Server
Quickest and Easiest Way to Run a Local Web-Server
Corey Schafer
46 Python Tutorial: Generators - How to use them and the benefits you receive
Python Tutorial: Generators - How to use them and the benefits you receive
Corey Schafer
47 Python Tutorial: Comprehensions - How they work and why you should be using them
Python Tutorial: Comprehensions - How they work and why you should be using them
Corey Schafer
48 Chrome Quick Tip: Quickly Bookmark Open Tabs for Later Viewing
Chrome Quick Tip: Quickly Bookmark Open Tabs for Later Viewing
Corey Schafer
49 Programming Terms: Combinations and Permutations
Programming Terms: Combinations and Permutations
Corey Schafer
50 Git Tutorial: Difference between "add -A", "add -u", "add .", and "add *"
Git Tutorial: Difference between "add -A", "add -u", "add .", and "add *"
Corey Schafer
51 Preparing for a Python Interview: 10 Things You Should Know
Preparing for a Python Interview: 10 Things You Should Know
Corey Schafer
52 SQL Tutorial for Beginners 1: Installing PostgreSQL and Creating Your First Database
SQL Tutorial for Beginners 1: Installing PostgreSQL and Creating Your First Database
Corey Schafer
53 SQL Tutorial for Beginners 2: Creating Your First Table
SQL Tutorial for Beginners 2: Creating Your First Table
Corey Schafer
54 SQL Tutorial for Beginners 3: INSERT - Adding Records to Your Database
SQL Tutorial for Beginners 3: INSERT - Adding Records to Your Database
Corey Schafer
55 Linux/Mac Terminal Tutorial: Navigating your Filesystem
Linux/Mac Terminal Tutorial: Navigating your Filesystem
Corey Schafer
56 Python: Ex Machina Easter Egg - Hidden Message within the Code
Python: Ex Machina Easter Egg - Hidden Message within the Code
Corey Schafer
57 Mac Tip: New Split Screen Feature in El Capitan
Mac Tip: New Split Screen Feature in El Capitan
Corey Schafer
58 Setting up a Python Development Environment in Eclipse
Setting up a Python Development Environment in Eclipse
Corey Schafer
59 Git Tutorial: Fixing Common Mistakes and Undoing Bad Commits
Git Tutorial: Fixing Common Mistakes and Undoing Bad Commits
Corey Schafer
60 SQL Tutorial for Beginners 4: SELECT - Retrieving Records from Your Database
SQL Tutorial for Beginners 4: SELECT - Retrieving Records from Your Database
Corey Schafer

This video tutorial series teaches how to use FastAPI and API routers to organize routes into modules, improving internal organization and maintainability. By following the steps outlined in the video, viewers can learn how to create separate files for each router, use APIRouter to organize routes, and improve scalability and maintainability.

Key Takeaways
  1. Create a new directory for routers
  2. Create a new file for each router (e.g. users.py, post.py)
  3. Move API routes from main.py to routers
  4. Use APIRouter to organize routes into modules
  5. Specify tags for routes to group them in documentation
  6. Use unique descriptive function names for routes to avoid conflicts
  7. Split off front-end code into separate file
  8. Create separate file for each router
  9. Organize routers by resource
  10. Test routers individually and mock dependencies
💡 Using APIRouter to organize routes into modules can significantly improve internal organization and maintainability of FastAPI applications.

Related Reads

Up next
I Built a Thumbnail Generator SaaS in 20 Minutes (ChatGPT Images 2.0)
Alex Leischow
Watch →