# Python FastAPI Tutorial (Part 3): Path Parameters - Validation and Error Handling

## Метаданные

- **Канал:** Corey Schafer
- **YouTube:** https://www.youtube.com/watch?v=WRjXIA5pMtk
- **Дата:** 12.01.2026
- **Длительность:** 36:56
- **Просмотры:** 15,563
- **Источник:** https://ekstraktznaniy.ru/video/11683

## Описание

In this Python FastAPI tutorial, we'll be learning how to use path parameters in FastAPI to create dynamic routes that can fetch specific resources from our data. We'll build both an API endpoint and a template page for viewing individual posts, add type validation with proper error handling using HTTPException, and create custom exception handlers that return JSON for API routes and styled HTML pages for browser routes. By the end, you'll have a solid understanding of how to work with path parameters, validate input automatically, and handle errors appropriately for different types of clients. Let's get started...

The code from this video can be found here:
https://github.com/CoreyMSchafer/FastAPI-03-Path-Parameters

Full FastAPI Course:
https://www.youtube.com/playlist?list=PL-osiE80TeTsak-c-QsVeg0YYG_0TeyXI

UV Tutorial - https://youtu.be/AMdG7IjgSPM

✅ Support My Channel Through Patreon:
https://www.patreon.com/coreyms

✅ Become a Channel Member:
https://www.youtube.com/channel/UC

## Транскрипт

### Segment 1 (00:00 - 05:00) []

Hey there. How's it going everybody? In this video, we're going to be learning 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 at once, 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. Uh we'll also make those posts on our homepage clickable and learn about type validation with proper error handling. And then in the next video we're going to start introducing pyantic schemas so that we can add real validation and structure to our data which is going to be the real foundation for eventually moving from this in-memory list into a real database. All right. So I'm starting from the ending code of the last tutorial. We have templates set up. We have static files mounted. We have our homepage rendering our list of post and we have a JSON a API endpoint at SLAPI/post that returns a full list of post. So let me go ahead and run this again. I'm using UV here. So I'm using uvun fast API dev main. Uh we'll start up our dev server. Uh make sure that is running. So we can see our homepage here. And if we go to the API route, you can see that we are getting uh our JSON API data there. So now let's talk about path parameters. Uh so path parameters are a variable uh that are parts of the URL path. So, for example, instead of a route uh that looks like this slapi slpost, something like this would go to our uh all of our post resources here. Uh but we could use a URL parameter here. So I could say uh post-1 or post-2 and fast API can capture that value from the URL and pass it into our function as an argument. And the really nice thing is that fast API will validate that value for us automatically based on the type hint that we put into the function signature and then we can use this value here uh to grab a specific resource. Uh so let's create our first uh single post endpoint. So I'll add this right under our uh get post route here. So I'll use what we currently have as a starting point here. And I'm just going to start out with a very uh basic version here. So instead of get post, I'm going to call this get post. And now after post here, we can use these uh curly braces here and we can pass in the post ID. So I have a setting that automatically makes that an F string. I want to get rid of that. So this post ID here, this is what we are going to be capturing as our path parameter. Now in our function signature here, we can pass this in as an argument and we can use typeing here. So I can say that this is going to be an integer. And now we can use this to look up a certain post. So if you remember our current dummy data, we just have a list of dictionaries. So let me just loop over these dictionaries and uh match whatever ID uh is equal to uh here. Whenever I find a matching ID, I'll just return that post. So I'll just say for post inpost and then we'll say if uh post. get and we'll get the id here. and we'll say if that is equal to this post ID from the URL path then let's just go ahead and return that post. Now my uh editor is going to yell at me here until I do a return value here. Uh now if we don't find a post right now let's just return an error and uh this is just going to be an error message. We're going to see uh why this isn't the right way to do this here in just a second, but I'm just going to return uh an error of post not found. Whoops. And this needs to be for post in post right here. Okay. So again, let me explain what's going on here. So the post ID in the curly braces here uh inside of our route is our path parameter that tells fast API that this is part of the URL and that is a variable and whatever value is there should be captured and passed into our function. Then in our function signature we have post id with a type hint of integer. That type hint is really important because fast API uses it to automatically validate the input. So if someone tries to access forward slap API slashposthello then fast API will return a validation error because hello is not an integer

### Segment 2 (05:00 - 10:00) [5:00]

and then we're just looping over those post uh comparing those to the ID of each dictionary. Uh if we find one we'll return that post. If not we'll return this error. But again this isn't really the right way to handle this and I'll show you why in just a second. But let me save this for now. Let me make sure we're still running our development server. And we are. So let's go back to the browser here. And now let's go to our API post here. And now instead of returning all of these, I'm going to do a forward slash one. And we can see that now we are just getting our post with an ID of one. So if I go to forward slash2, then we can see that now we're getting our post with an ID of two. Now, if I go to a post that doesn't exist, so for example, let's go to uh for slash99. If I go there, then we can see that we get our error message here. But here's the problem with this. So, if I go back to our development server, if I take a look at our output here, uh then we can see whenever I went to get that post of 99, we have a 200 response here. So we would expect a 200 for uh getting the post of one, two because those exist. If you don't know, 200 means that it is a success. But post 999 here shouldn't be a successful request uh because we couldn't find that post. So we should be returning a 404 status code to tell the client that the resource wasn't found. And this matters because client applications use these status codes to know how to handle uh the response. If they get a 200, then they're going to think that everything's okay. If they get a 404, then they know that resource doesn't exist, and then they can handle that appropriately, and that's just a restful API best practice. So, let's fix that by raising an HTTP exception with a proper 404 status code. So to do that, we'll need to import HTTP exception uh and status from fast API. So at the top here where we're currently importing fast API and request, I'm also going to add in HTTP exception there and also status. I'll import both of those. So HTTP exception is what we use to return proper HTTP error responses. And status gives us constants for HTTP status codes which makes our code more readable. So instead of just uh using the number 404, we can instead use status. http 404 not found. And whenever we do that, it's going to be more obvious in the code what we're doing. So we'll see that here in just a second. Now let me update the endpoint so that instead of returning this error dictionary here, it instead uh raises an HTTP exception. So here instead of returning this, I'm instead going to say that we want to raise an HTTP exception. And now we can pass in a few arguments here. So I can say that the status code is going to be equal to status. http http and this is going to be you can see that we have all of our status codes here. This is going to be a 404 not found. Now I like using these status constants for basically every status code. Um 404 most people know what that is but whenever you get into some of the more esoteric ones um I think it's better to have the status code here. So for example um you know not everybody knows what a 422 error code is. Uh so if I was just to return here uh that you know status code 422 uh in the code that's not going to be obvious what that is. But if we were to return uh status uh http 422 unprocessable content then that's you know more obvious what we're doing. But in this case this is a 404. So 404 not found and then we can pass in a detail here and the detail is basically just uh you know why are we getting this 404 and it's because uh the post was not found. So now instead of returning an a dictionary with an error uh we're now raising an HTTP exception. We pass in the status code using our status constant and we pass that detail message that will be included in the response. And when you raise an HTTP exception like this, Fast API automatically handles it and returns the appropriate response to the client. So now, let me save that. Let's make sure that we're still running our dev server. And we are. So now, let me go over here and refresh uh for our post 99 here. If I run that, then we can see that it

### Segment 3 (10:00 - 15:00) [10:00]

looks pretty similar. It still just says uh you know, detail post not found. So it looks like we're just getting kind of a similar error message. But if I go back here uh to our dev server then we can see that now whenever we go to get that route we are getting that 404 status code. So if you want to have a reliable API uh then you want to return the correct status codes. That way clients can detect that and correctly you know handle it however they want to uh their way. So now let's test validation uh because this is one of the big benefits of path parameters with type hints. So we can see that we type hinted this as an integer here. So if I go back to our page so whenever we go to uh post 99 it doesn't exist. That's still an integer. But if I go to post uh slashhello and go here then we can see that we get this long uh detailed error message here. So this is actually a 422 response with a message explaining exactly what went wrong. Uh it says that the input should be a valid integer unable to parse string as an integer. So this is automatic validation from our typins. We didn't have to write any code to handle this. And if I go back to the dev server here, we can see that we did get a 422 response there. Uh so it's nice that is just built in for us just by us telling fast API that we're expecting an integer. And another nice thing is that this shows up automatically in the docs as well. So if I go back to our docs here and reload this, then we can see that we get our uh new route here in our documentation. And if I expand this uh then we can see that it's telling us that post ID is an integer. So if I go to try it out then I can do all the same things here. I can try to get post ID of one. Execute that we can see the response body there. If I go to post 99 execute that we can see that we get post not found and that we get a 404 status code. And let's do post hello. execute that in our docs and we can see that uh it's telling us well actually it looks like it's not even uh letting us because it says it must be an integer here. So that's a nice benefit of using path parameters and typins. Uh the docs are generated automatically and you can test it right here in the browser. All right. So now we have our single post API endpoint. Uh now we also want a single post for the browser as well. Uh right now our homepage lists all of the post but there's no individual post page like you know post one. So let's go ahead and build that next. So first we need a template for a single post page. So if I go back to the code here uh I actually already have a finished version already here in my project and I've called this postfinish. html. So, I'm going to copy that uh and we will create a new template here. And we will call this template post. html. And I will copy this postfinish. html into this uh post. html within our templates. And again, I'm going to have all of these files available for download, and you can find those in the description section below. But let me go ahead and quickly scroll through this so that you can see what's in here. So it extends our layout. html just like the homepage. Uh it displays the author's image here. Uh we have some post metadata. We have the title. We have the content. And you'll notice if I scroll down here a little bit that there are placeholders for edit buttons and delete buttons. Uh now those are there for whenever we add forms uh and authentication later in the series, but we're not wiring those up just yet. So now let's add the route for that template. So I want to keep my web routes grouped together here uh and my API routes grouped together. So I'm going to add this right under the home route and above these API endpoints here. Now, I probably will use the API endpoint just as a starting point here. So, let me copy that and paste it in here. And now, let me change a couple of things about this. So, instead of for/ API, we only want this to be for slashpost with that post ID. We also want this include in schema equal to false since we don't want this showing up in the documentation since this is returning HTML. Uh now remember we also need to take in the request when we're using uh these ginger 2 templates. So

### Segment 4 (15:00 - 20:00) [15:00]

right in front of our post ID there uh we'll accept the request and the post id. And now here within the API, we were just returning that post and that gets returned as JSON. But here we're going to return a template response. So let me just copy the template response from our homepage here and paste this in. But instead of home. html, we're going to return post. html. And if you remember in our home. html template, we were looping over these posts. Uh but in the post template, we only have one post. So we are just referencing that directly. So instead of posts in our context here, we're going to want to pass in post as that context, the post that we find with that ID. And also we have a title here of home. Uh we don't want that title to be home. Let's go ahead and create a new title here. So, I'll just say that the title is equal to let's have that be the post title since these have uh title keys up here for these dictionaries. And sometimes these titles can be a bit long. Uh so I'm just going to return the first 50 characters of that title. So now the title for our homepage here or for our post page instead of home, we'll just pass in that title directly. So just a quick recap here. Um so this looks very similar to our API endpoint. We are capturing this post ID as an integer and that gives us validation just like our API endpoint and inside the function we are looping through the post to find a match. Now a couple of things to point out here. Actually before I point that out let's go ahead and test this really quick uh just to make sure that what we have so far is working. So, I'm going to save that. We're still running our development server. So, let me go back to our web page here. And now, let's go to I'm going to open up a new tab here. Let me go to forward slashpost slash one. So, we can see that we get only that first post. If I go to forge slash2, we only get that second post. So, both of those are good. Now, I really don't want to have to navigate to these manually here. Uh, it would be nice to have these clickable from the homepage, but right now, these are just dead links. So, let's make these clickable from the homepage and add these links in. Uh, so that is in our home. html template. So, I'm going to pull up our home. html HTML template here and I'm going to go down to where uh we are looping over our post and we can see here that on our post. title we currently have an href that is just a dummy link. So instead let's replace this with a URL for and this will be a URL for let's see we called this function uh post page I believe. Oh, actually I just saved myself an error here. I'm glad that uh I just saw this where we copied this from the uh API route here. We can see that my editor is underlining this telling me that uh we have multiples of these. I meant to rename this function name here. So we need to rename this to something. Let's just call this post page. So now I will copy that and we want the URL for that post page. And then with URL 4, we can pass in any path parameters as keyword arguments. So if I wanted to pass in post ID, then I can set that equal to uh the current post that we were that we are on and grab the ID of that post. So in our homepage here, let me quickly show this here. So when we are looping over our post here uh for post and post we come down here and we're saying okay so for the post title I want this to be a link and that link is going to be the URL for the post page and then we want to also pass in post do ID as this post ID here. So what that's going to do is it's going to generate links like for/post one/post2 for each of those post. Now again using URL 4, it's really powerful because if we ever decide to change our URL structure, then we only have to change it in one place within main. py and all of our links will automatically be updated. Okay, so let me go ahead and save this here. And we still have our development server running, so that's good. And let's go back to our homepage

### Segment 5 (20:00 - 25:00) [20:00]

here and run this. Now, if I click on a link, then it goes to that single post page. Again, these are just dead edit links and delete links right now. Uh they don't do anything yet. We'll hook those up later. Uh if I click on the second post, then we can see it takes us to that second post. So, that's good. Okay. So, now this is what I was about to mention earlier. Uh, now let's test some error handling because this is where things currently aren't that great. So if I go to for/post/99, then we can see that we get that detail post not found. And if I was to check the status code of that, we are getting a 404 response, which is correct. But right now it's returning JSON in the browser. Uh so we get that same JSON response that we get for from the API. So JSON that's fine for the API but for uh a browser or for a user browsing a regular website seeing raw JSON error messages like this can be a little confusing. So we would rather show them a nice HTML error page instead. But we also don't want to break our API. So if an API client uh requests a missing post, then they should get JSON. Uh but if someone on the front-end HTML uh requests a missing post, then they should get a nice HTML error response. So we need a way to handle errors differently depending on whether the request is for the API or for the website. Uh but the idea is pretty simple. So, we'll just set this up to where if the request path starts with for slappi, then we'll return JSON. Otherwise, we'll return an HTML error page. So, first let's create an error template for our HTML. So, within templates here, let me scroll back to our project. So, within templates here, I will create an error. html. And I also have a finished version of this ready as well. It looks like I forgot to put it in my project here. So, let me do that really quick. Okay, so through the magic of editing, it looks like we always had this here in our project. Okay, so now I'm going to open this up. We can see that this is an extremely uh simple template. So, I'm going to copy this finished version here and I will paste this into the error. html HTML template that we just created. Now, I know that I'm using a lot of copying and pasting for these templates, but I figured you all are more interested in the fast API stuff and would rather get around to that. So, having these HTML templates pre-built and styled and having those available for download allows me to drop in the HTML and CSS stuff without much of an explanation. Uh, so that we can focus mainly on fast API. But like I was saying, uh this is a fairly simple template here. We are just extending our layout. html and it just displays our status code and a message here. So now let's set this up uh to work with our routes. So I'm going to need to uh add some exception handlers here. And this requires a few more imports here. So up here at the top, the first thing I'm going to import here, I'm going to say from fast API. exceptions and I'm going to import a request validation error. I'm also JSON response and we'll hook all of this up here in just a second. So I'm going to say fast API responses and I'm going to import JSON response. Okay. And the last thing that I'm going to import here, I'm going to say from starlet and this is starlet. exceptions, I'm going to import http exception. Now, we're already importing HTTP exception from fast API. Uh, so I'm going to give this an alias here. So, I'll say that we are importing this. Let's just call it starlet HTTP exception. So let me explain what we imported here. We have JSON response so that we can manually return JSON responses from our exception handler. We have request validation error and that is for handling validation errors like when someone passes hello instead of an integer when we're expecting an integer. And then we're importing HTTP exception from starlet and aliasing that to starlet http exception. Now you might be wondering why we need this when we already have HTTP exception for fast API. Now the reason for this is that fast API is built on top of starlet and

### Segment 6 (25:00 - 30:00) [25:00]

when a user goes to a route that doesn't exist like for example if they just go to our website for slashdn or something uh it's actually starlet that raises that 404 error not fast API. If we only caught fast API's HTTP exception, then we would handle our manual errors through fast API but missed those other page not found errors from Starlet. So by catching the starlet HTTP exception as well, we're just going to cover both cases there. So now let's add the exception handler. So I'm going to put this at the end of the file after all of our routes here. And this is a bit long. So, I actually have this in my snippets. Let me close down all of our other stuff here. And I have some snippets here that I can post in. Now, like I said, this is a bit long, but I'm going to paste this in. And then we'll explain exactly what is going on here. So, let me paste this at the end of our file and save that. So, now let me walk through exactly what this does. So, we use the app. exception exception handler decorator and tell it to catch uh this starlet HTTP exception. The handler function receives the request and the exception here. And the first thing we're doing here is we are setting up a message. So I'm saying that the message is equal to exception. detail if it has a detail. Um otherwise we are just going to set it to this default string of an error occurred. Please check your request and try again. So that'll just handle cases where starlet raises an exception without a detail. So then what we're doing here is we are checking if the request URL path starts with API and if it does then we're going to return a JSON response and we're just returning a JSON response with that status code from the exception and the message. Now if it's not an API route then what we're doing is we are returning this template response here and that's going to be our error. html template. So again these templates they take in the request as the first argument. We're saying that we want the template to be error. html and then we are passing in some context here. So the context is we have our status code which is the status code from the exception passing in our message and also I'm setting the title of the page to that status code as well. And notice we're also passing in the status code to the template response itself. Uh so this tells the browser that it makes sure that we get the correct HTTP status code. If we didn't pass this in then it would display everything like it's an error. but we would get a 200 response. Uh if we pass in that status code, then we're going to get the correct response code with that template. Now, one thing I should mention is that while this manual JSON response uh works perfectly for our current needs, it's a simplified version. Later, when we introduce asynchronous functions, we're going to switch to using Fast API's built-in exception handlers, which automatically handle some edge cases like custom headers that we're currently skipping. But for now, and what we're using now, this will work great. Okay, so now let me save this and test a couple of different scenarios here. Um, so we still have our uh development server running, so that's good. So first let's test a manual 404 uh for a page route for HTML. So this was for/post uh 99. This is not our API route. So this should return HTML. So we can see that now we get that error template. That looks good. So now in if a user comes to our front end instead of getting that confusing JSON now they'll see oops 404 error post not found uh with a message um and that's much better than what was there before. Okay. So now let me test a 404 that isn't actually coming from our own code uh but just because a page doesn't exist at all. So instead here I'm going to go to for slash we'll just say for slashdn for does not exist. Uh and we can see that works as well. Now this is what I was talking about before. This is actually starlet that is handling this. Um since fast API is built on starlet and this message here of not found um that is just the default message from starlet since we didn't provide a custom detail. So now let me make sure that our API routes are still

### Segment 7 (30:00 - 35:00) [30:00]

returning JSON. So if I go back here, uh we can see this was for our data validation here. If I reload that, we can see that still works. If I go to post 99, then we can see that we're still getting JSON here of post not found. So that's good. Our API routes are returning JSON errors and our template routes are returning HTML errors. And that's exactly what we wanted. Now there's one more type of error that we should handle and that is validation errors. So again if I go back to our validation error here from our API then this is that 422 validation error. Now let's see what happens if we try this on a template route. So if I go to a specific post here we can see that we're at for/post slash one. Let me go to for slash99. Whoops. I'm sorry. We're testing validation errors, not 404. I wanted to go to for/hello. Sorry about that. So now we can see that we are uh getting JSON again, which isn't what we wanted. And that's because validation errors are a different exception type. They're uh request validation errors, not HTTP exceptions. So our handler didn't catch them. So now let me add a handler for validation errors. So again I'm going to grab this from my snippets here. Let me grab this and I will just paste this below our HTTP exceptions there. Let me split that up a bit. And this exception handler is very similar to our previous one, but we are catching a request validation error instead. And there's a couple of key differences though. So first up here you can see that we were creating our own message if we had this exception. detail uh or having a generic message. Here we are not doing that because validation errors don't have a simple detail string. They have a list of detailed error information. If I go back we can see that we have a list here of a lot of detailed information here. it has a message input uh what the problem was and everything. So if I go back to the code, so the way that we're going to handle this is for our API responses here. If our request uh path starts with API, what we're doing here is we're just passing in a status code of HTTP422 unprocessable content. And then for our detail, we're passing in this exception. here and that will get all of the validation details so that a developer or someone consuming our API can see exactly what went wrong. Uh and now down here actually let me see if that's the only thing I wanted to talk about with that. Yeah. Okay. Now if you'll notice up here we were setting the status code here with exception. status code uh for these um request validation errors. Those errors don't have a status code attribute. There are always 422 errors. So we can just use that status http422 everywhere instead of getting it from the exception. So for the HTML response we just show a message saying that the request was invalid here. I'm not uh pulling anything from the exception. We don't need to expose the technical validation details to end users in the HTML. We'll just do that through the API. But for everything else, uh the status code in the template, the title of the page, and the status code uh that we are returning with the template, those are all going to be 422. So let me save that and then go back here and we will test this both in the API and we can see that we get that JSON message there and let me also test this on our web page. So now we can see that we're getting a 422 error on our web page as well. So now we have proper error handling for both HTTP exceptions uh and validation errors for uh both the API and returning JSON and uh returning HTML uh with our templates. So we can see that we're building a nice clear separation of concerns here. So our API endpoints are all under for/ API and they return JSON. Like I said, those are meant for programmatic access like front-end JavaScript apps connecting to our API, mobile apps connecting to them, anything that wants to consume our API. Our template routes are for HTML pages uh like for/post and they're meant as a

### Segment 8 (35:00 - 36:00) [35:00]

frontend for us uh just for humans using the browser. That's a frontend that we're specifically building and not someone consuming our API. And the nice thing about that is that both of those things can use the same data source. Right now it's just this inmemory uh post list up here at the top, but later this is going to be a database. So now we've set ourselves up here to be ready for the next steps. So right now we're using dummy data but in the upcoming tutorials we're going to add paidantic schemas for validation then connect to a real database and add create update and delete operations. But we have a very good foundation of what we've built so far. But with that said I think that's going to do it for this video. Uh hopefully now you have a good idea of how to use these path parameters in fast API and uh use those parameters to access specific resources and how to handle errors properly for both API routes and browser routes. In the next video, we're going to learn about paidantic models and schemas and start adding some real validation to our data. 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 to 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, 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.
