4 Tips for Building a Production-Ready FastAPI Backend

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
I've covered fast API several times in my previous videos today I'd like to dive in a bit deeper and talk about a few things that you typically won't find in most API tutorials online I'm going to cover four things that will help you especially if you want to create a back end that's used in a production setting this video is sponsored by palumi which is an infrastructure as code solution I'll talk more about them later in the video now let's dive into fast API the example that I'm going to cover today is a simple fast API back end that has both items and automations the items are just simple things that you can put in a database so there create read update and delete operations on them next to that you have automations that you can run whenever something happens to an item and I've added a feature here where if you update the item then some sort of automation is going to be run and the idea of dation is also pretty simple it just runs a piece of python code you can of course build out these automations more by adding things like triggers web hooks different paths depending on certain conditions Etc I'm not going to do that today I'm going to keep it really simple here's a quick overview of the code so we have a fast API app it includes some routers I'm going to talk about that in a minute and one part of the API is that there is a simple get request that just Returns the response that the server is running so I can start the server with uvicorn which is a server running tool it's very useful and now it's running on local host on Port 8,000 when I open the root URL in the browser then this is what you get now like I said this has both items and automations that's actually pretty easy to read items as well in the browser so I can simply type for example items one and then it's going to return adjacent structure representing a particular item in this case a coffee machine I'll cover the automation aspect of this API later on in the video now the first thing that I want to show you is that if you have multiple end points like items automations users whatever then it's going to be helpful to split these things up in different routes and fast API has a router mechanism for that and what this is going to do for you is that this will avoid you having to put all the routes into a single file and unfortunately most examples online explaining how to get started with fast API don't use routers they simply put everything in the main file if you want to build a more complex API application this is not going to work because you main file is going to be huge so routers are a really good way of organizing this so in the main file you can see that I have here two routers that I've imported there's the items router and there's the automations router so these represent each a group of endpoints and then in order to use them I simply include them in the app using the include router method and then it knows these endpoints and then what I did in the API is that I have here a routers folder where I have my various routers so there is for example the items router which looks like this and there's some other things in here that I'm going to cover in a minute as well so the first thing you see is that I create a router an API router this is an feature of fast API and it give it a prefix so now any end points that are part of this router fall under this prefix that means for example if I have a get method R.G and then the item id it means you can get this by navigating to rud your URL SL items SL the ID so/ item is put in front of every endpoint and then each route simply contains the code that needs to be run when you call that endpoint so there's creating an item there's reading an item there's uh reading item automations so this reads the automations that are associated with an item we have an update item function and we have a delete item end point so that's how the items router is set up and if you take a look at the auto mations router is that actually pretty similar except we have a different router with a different prefix so here it's not/ items but it's SL automations and also here we have a create automation read automation I was scrolling too far update Automation and delete Automation and actually here's an error this should be automation actually fast API doesn't really care what the names of these functions are but of course we should call this correctly and it's set up in a very similar way to how the items router is set up that's why this function was called delete item because I just copy pasted everything so in the items route for example create item needs an item create object that contains all the information about the item I'll show that in a minute then getting an item simply needs an item id which is in this case an integer and reading item automations also only needs an item ID and then it's going to return a list of automations updating an item needs an item item update object which contains information about the item that'll also show in a minute and then finally you have deleting the item that simply needs an item ID and for automations the the way this set up is exactly the same so if you create an API that has multiple groups of endpoints like items automations used I really recommend that you use routers for this so that's the first thing that's going to help you organize your code better the second thing that you're not going to see in most examples online is that directly write the code that should happen if you call the endpoints inside the endpoint function and if you're creating a more complex API application that's not really a good way to do things uh for one the endpoint will then contain everything which makes it harder to test the second thing is that you're actually if you're doing that you're mixing up two responsibilities you're mixing up routing which is organizing where which endpoint is and how you should call it together with the actual behavior of the endpoint code and next to making things harder to test that's also going to make things harder to read because then again your files that contain all the routes for a particular router are going to get very long and finally you not a problem with writing all that code in your endpoint function that also means that if let's say you ever want to create a command line application for example that does the same things as your backand then you have to copy paste all of that code or you have to do a lot of refactoring work so if you want to create a more complex API with lots of Route group groups containing lots of different routes then you should also separate the routing part of your application from the actual operations and this is actually what I've done here so if you look at let's say let's take uh creating an item which is here so creating an item the the endpoint function is basically almost empty right it calls a create database item which is the actual operation it passes the item create object show you what that is in a minute and it also passes a link to the session the database session and then once that's done then we have a database item object that create database item gives us and then we transform that into something that fast API can return as a Json structure so the operation which is all in this function is completely separate from the actual endpoint and I'm using another thing here that's also really important part of fast API which is dependency injection so I provide a database session to the endpoint and that means that I can write tests for this endpoint more easily so how does this actually work so in fast API if I go to the database section of the code what you can do is that I have here a basic database setup so as you can see from the Imports this part is actually independent from the fast API tool right it's it has some typing Imports and it uses SQL alchy which is an omm to interact with the datab datas so it's completely separate from the routing aspect of the system which is really important so here I just use a simple sqlite database so it contains a test database you can change this to a SQL connection string if you want to but then you you'd have to make sure to not include the credentials in the code that should be in in environment variables so then using SQL Alchemy I specify the models that are part of this database setup so we have a declarative base which is the core of SQL Alchemy and then we have two things we have a database item so that corresponds to an items table in the database and an item simply has an ID an name and a description and that's it and ID is a primary key and it's also an index so that's database item very basic and then we have an automation it's also very simple we have an ID which is again an integer so it maps to a table automations forgot to mention that so ID is also a primary key and then we have the item id which is a reference to the item that the automation belongs to and I simply specified it this is a foreign key it corresponds to the ID in the items table and finally we have code so this is the code that should be run when we want to run the automation so that's all there is to it that's the setup of the database and then we have the part where we create the connection with the database so that we can use that connection use the session in fast API so first we need to create database engine then we need a session maker something that can create sessions for us and then we create The Binding so that the engine knows that these models are actually going to be used in order to interact with the database by SQL Alchemy and then finally we have very simple dependency uh so this is a function get database that actually gives a database session and I'm using a generator for this so that we can close the database automatically atically when we're done and that's what this try finally block does here so we yield the database session that we create here and then finally whether you get an exception or not doesn't matter we're going to close the database and that's good practice to always close database connections when you don't need them because these connections start to pile up and it's going to slow down your database if you are not careful so then how do we set up this dependency in fast API well if you go for example to the items router you see that we import the get database function from here and then we provide it as a dependency to the function and then fast API will make sure that this function gets called whenever the endpoint is called and that way we have always a database session you also see that I passed the database session to the create database item operation so this function is what's doing the actual work in the database and of course that makes sense because you can't work in database if you don't have a database session so if you look at the actual implement mation of these operations that's actually What's Happening Here in items. pii under the DB folder so we have here a couple of identic models that specify the information we need in order to communicate with this part of the application so there is an item which has an ID name and description and by the way these classes are also used by fast API to return Json structures or read Json data so item id name and description very similar to how the database has been set up and then we also have item create and item update so in order to create an item we need to provide a name and description description is optional it's not necessary and if we update an item we can provide either a name or description so we can update either one of them and then we have the actual operation so create database item for example well it creates a DB item object so that's an actual omm model and then it uses the session to create this database item and it also returns this as a result so you can read it and do something with it updating a database item also has its own code for performing that operation so I'm first reading the database item and I'm actually reusing another operation for that so that's uh the one that's here so this queries the database and then filters it on the item id Returns the first one if it's not found it raises a not found error then updating the database item use that to get the database item it sets the attributes and then commits that in a session and finally what this update operation also does is that it runs the automations which is another part of the system that I mentioned in the beginning so just include this as simple example you may want to run automations in different places as well but this allows you for example to execute some custom code whenever an item in a database is updated and that can be helpful for example if you build a web shop then if an item in the database is updated then maybe you want to run automatically some code that also updates the items description on your website just a minor design remark and that's more about where you place the boundaries so what I've done here is that the input of these functions is a pantic base model it's an item create or an item update or simply an integer right item id but it returns a database item in a way that doesn't make a lot of sense because you would expect actually that this thing would then also return an item right it should return something of this base model because then if you call the function you don't deal with the database at all it's completely hidden so I haven't done that in this particular example because I'm reusing read database item and using that in other functions as well but probably it would be even better if each of these functions returns actually an item and not a database item and then I would simply create an internal function to read a database item that I can reuse in these end points that would probably be better because then in the routers I also wouldn't need to do this conversion step here and knowing about how D database items are being implemented so in the current implementation this is not ideal but this is actually something that's really easy to fix for example let's take this function and make a copy of that and then let's just keep this for internal use and now this one we can change so that it returns an item and what we then can simply do is take part of the read item code like this and then let's change this so that it returns this and then here I'm simply going to use the read database item that passes the item ID in the session so now I have this new function that already converts this to an item and then in my router I don't need to do this anymore I can simply do this like so and now this is even simpler and then of course you can change the other end points as well so that it works like this so for example creating a database item I can also simply return the item like so and then in the router I can simply do this and you can do the same thing for the other operations as well so you can imagine that sometimes it can be hard to determine what the exact boundaries are of what an operation should get as input and what it should return as a result and how it interacts with the database and what routers should or shouldn't know because there might also be a disadvantage to actually returning items and not database items for example if you combine different operations in your router endpoints then you may want to pass a database item between the operations for efficiency purposes if you return item you can't do that you then might need to do multiple database requests to get the information that you actually need a third thing that you won't see in many examples online is that they don't really talk about deployment all that much so whenever you start working on a new application you may not even think about that yourself and then think you know I've first build the thing and then I'll start thinking about how to actually deploy it actually what I like to do is the other the way around I like to set up the let's say the scaffolding first before actually doing too much in the code so typically what I would do if I let's say I want to create a new backend API that I want to host in the cloud so what I will do is that I won't build out the complete API locally but I'll first start with let's say only the health check end points just an empty get request and then set up the whole scaffolding including the whole deployment um cicd pipeline uh settings that you need in the cloud everything to actually run this API and deploy it to the cloud whenever you push a new version to the main branch and the reason why I do it that way why I don't build out the complete API and then think about deployment is that I want to make sure that all the pieces fit together and in my experience this part setting up the scaffolding and deployment and everything is often the most tricky to get right because you will have to create things like Docker files with the various Imports and dependencies and there's many things that can go wrong there's also many things that can go wrong with setting Secrets up correctly so that you have access to the right credentials enabling apis in your cloud provider so that you can actually host your API in the way that you want to in the cloud so there's lots of things that can go wrong lots of things that you need to set up and it can be very frustrating if you wait with that until the last minute because it's possible that due to some dependency that you're using that actually blocks being able to deploy it properly to the cloud so I find it important to set this up as soon as possible and basically deal with the potential problems as soon as possible so that afterwards once it's set up then it's pretty easy to build out the API and add more features to it for deployment you can use different tools one that I'd like to use is palumi they're also the sponsor of this video palumi is what's called an infrastructure as code tool and that means you can Define what your Cloud infrastructure looks like and handle provisioning those Resources by actually writing code using your favorite programming language so you can use Python for example to Define what your infrastructure looks like and I find that's really helpful in this particular API what I've done is I've created a Docker file so that's what I see here so it's built on python 3.1 and actually the docker file is pretty basic so I simply install the dependencies I copy the scripts to the folder and then I simply start the server just like I would do on my local machine but then I have a main file which is a python file that actually specifies how to create the resources in the cloud and being able to do this in Python I find this very very helpful because it simplifies the workflow a lot in my opinion so there's a couple of helper functions like creating a registry where we're going to host the docker image that we're going to build then we have a function for building an image so this all relies on pumi libraries this uses blumi Docker for example so it creates an image and I'm also passing some build arguments because I'm running this on a Mac which has Apple silicon so I need to make sure that it builds for Linux amd64 processors otherwise it won't work and then I'm hosting this application in the cloud using Google's cloudrum service so here I'm using the bloy package for Google Cloud then I have a function that sets the right access policy so that it's actually publicly available and finally I have a main file where I call these functions and deploy the actual application so I create a registry then I'm going to get the information about the registry build an image create the cloud run service create the access policy and then export the important information like what is the URL of the cloud run surface for example or what was the image name that was generated so I can find it back easily and then what's really nice with pumi is that it's really easy to then deploy your application to the cloud so you simply write pumi up and then it will set up all these things in the cloud for you what's nice about an infrastructure as code solution is that you can do things with it that aren't possible if you define your infrastructure statically and this actually how I built the automations part of this example so here you see what the code looks like when I want to run an actual automation run code in the cloud and uh what I'm doing is that on the fly whenever I want to run a piece of code I create a Sandbox environment a serverless function in the cloud that actually runs that code and then right after that I immediately destroy the function again so this allows me to run code in a isolated environment and then destroy that environment immediately so it doesn't affect other parts of my backand if you were to run automation code directly in your back end might be dangerous because if that code breaks your backend server then well you have a problem so if you do it in an isolated environment that's much better and that's actually what I'm doing here so the first step is again some scaffolding work preparing a virtual environment so as you can see I'm directly calling python here to create a VM and then I install the requirements and then I have the function that runs all the automations so I get the code from the Automation and then I'm using palumi automation framework to actually on the Fly create a cloud resource so that's what happening here so I prepare a virtual environment by calling that function then I create a new stack so pumi works with different Stacks like a development stack or production stack in this case I create a development stack and then I'm updating the stack so this is going to create the resources in the cloud for me and then what I get as a result from doing that is a function URL so I create a cloud function and this gives me a URL and then next step is that I actually invoke the function passing the code of the Automation and then as a final step I destroy the stack again and how I've set this up is that next to the API I have a simple code Runner setup which is basically another service that has a very simple main file that basically has a run code function that gets the actual code to run from Json it captures whatever we write to the standard out or standard error and then it returns that as a result and this is simply another infrastructure as code setup that deploys a cloud function using pumi and then when you actually update an item and run the automations you see that it actually runs the code part of the automations in the cloud function and returns that as a result so infrastructure code allows to do these kind of things dynamically and it opens up a lot of possibilities if you want to try out palumi it's free you can simply go to pmi.com the link is also in description of this video a final important thing to think about is that when you deploy your API you also need to make sure that people can't abuse it that's there's several of things you can do one is that you can create an authentication flow using oo and bar tokens and API keys so that you know who it is you can restrict access using Scopes that's all part of the oo standard and that's one way to make sure that your users don't have access to things that they shouldn't have access to so whenever you're creating a more complicated production level API should definitely also add some sort of authentication flow and authentication flows are a whole other topic if you'd like me to do a video about that let me know in the comments so adding authentication is important but that's not the only thing you should do because a that doesn't help if you developing a public API or if your API has public endpoints and B it also doesn't preclude users from sending out way too many request in a row and if you want to deal with that the best thing to do is to add a rate limiter to your backend API so that you can restrict the number of times users or anybody can call your endpoints so how does a rate limit are typically work well it's going to look at things like the ID of your machine the IP address things like that to make sure you're from not from a single machine sending thousands of requests per second so fast API doesn't have direct buil-in support for rate limiting but there are packages that integrate really well with fast API and one of them is slow API and that's actually the rate limiter that I'm using here so setting up slow API is really simple you just need to add an exception Handler for rate limit exceeded that's an eror type from slow API and then you need to pass it the Handler that handles that particular error and then what you can do in your routers or in your simple endpoints is that you can add limiters so here is how that's set up so slow API has a limiter class and we can indicate how we want to determine how the limiter is going to detect that a request comes from a different user and I'm using the get remote address function for that that looks at the remote address of the the machine that's doing the API call so that creates the limiter and then in my router I import this limiter and then I'm using it as a decorate to Define how often we can request that and here we put a limit of 1 per second and since the limiter needs to access the actual request in order to extract the information you need to also make sure to pass it as an argument to your endpoint function otherwise this doesn't work so this way we've now defined a limiter for this particular endpoint and you can do this for every endpoint and you can specify different types of limits so for example if you want creating an item to be one request per second but reading an item you may want to do that uh more so you want to put the limits maybe a bit higher at 10 per seconds it's really easy to change this in the limiter specification there's one minor cier to take into account it's that you need to specify these decorators in the right order so limiter needs to be below the router. get decorator otherwise this won't work correctly so these are just some of the things you need to think about when you create a production API so first split your endpoints into separate routers so that the code becomes more manageable also move operations out of the endpoint functions so that you can more easily test them and more easily reuse them in other types of applications such as a command line interface the third point is that you need to think about deployment of your application set up the scaffolding first because that's going to lead to most of the issues going to take up most of the time so do that as a first step and the fourth one is that you need some way to control access so whether that's an authentication flow or using a limiter or both that's really important to think about in order to make your apis scalable and safe now fifth thing that I didn't talk about at all in this video is that you need to actually write software test when you write your test for your fast API backend there are a couple of things you can do to make your life a lot easier if you want to learn about that watch this video next where I dive into the dets thanks for watching and see you soon
Info
Channel: ArjanCodes
Views: 48,403
Rating: undefined out of 5
Keywords: fastapi backend, backend fastapi, fastapi production backend, fastapi tutorial, fastapi, fast api, actual production backed, production backed, fastapi backend tutorial, python fastapi backend, python backend fastapi, fast api development, fastapi tutorial python, fastapi project, fastapi python, fastapi tutorial advanced, fastapi tutorial project, pulumi python, pulumi tutorial, pulumi, fastapi roadmap, fastapi tutorial playlist, react frontend fastapi backend
Id: XlnmN4BfCxw
Channel Id: undefined
Length: 27min 1sec (1621 seconds)
Published: Fri Jan 12 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.