by Philipp Acsany Publication date Nov 03, 2025 Reading time estimate 28m intermediate api web-dev
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Python REST APIs With FastAPI
This example project showcases important features of the FastAPI web framework, including automatic validation and documentation. FastAPI is an excellent choice for both beginners building their first API and experienced developers diving deep into API design.
In this tutorial, you’ll explore a…
by Philipp Acsany Publication date Nov 03, 2025 Reading time estimate 28m intermediate api web-dev
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Python REST APIs With FastAPI
This example project showcases important features of the FastAPI web framework, including automatic validation and documentation. FastAPI is an excellent choice for both beginners building their first API and experienced developers diving deep into API design.
In this tutorial, you’ll explore a FastAPI example application by building a randomizer API that can shuffle lists, pick random items, and generate random numbers.
By the end of this tutorial, you’ll understand that:
- Path parameters and type hints work together for automatic request validation.
 - Request bodies handle complex data in your API endpoints.
 - Asynchronous programming improves your API’s performance.
 - CORS configuration enables secure cross-origin requests.
 
To follow along with this tutorial, you should be comfortable defining Python functions, working with decorators, and have a basic understanding of CRUD and JSON.
** Take the Quiz:** Test your knowledge with our interactive “A Close Look at a FastAPI Example Application” quiz. You’ll receive a score upon completion to help you track your learning progress:
Set Up a FastAPI Example Project
Before diving into code, you’ll need to properly set up your development environment. This involves installing FastAPI and creating your first endpoint. The goal of these first lines of code is to verify that everything works correctly.
Install FastAPI
FastAPI requires two main components to run your application: the framework itself and an ASGI (Asynchronous Server Gateway Interface) server. The recommended way to install FastAPI includes these two components and all the standard dependencies you’ll need to get started with your FastAPI project.
First, select your operating system below and use your platform-specific command to set up a virtual environment:
With your virtual environment activated, install FastAPI with all its standard dependencies:
This command installs FastAPI along with Uvicorn as the ASGI server. The [standard] extra includes a predefined set of optional dependencies that enhance FastAPI’s functionality. For example, it provides the FastAPI CLI, which you’ll use later to run the development server.
Create Your First API Endpoint
With FastAPI installed, you can create a minimal API with one API endpoint to test your setup. This first endpoint will help you understand the basic structure of a FastAPI application. You’ll see how FastAPI uses Python decorators to define routes and how it automatically handles JSON serialization for your response data.
Start by creating a new file named main.py in your project’s directory:
You create your FastAPI application named app by instantiating the FastAPI class. The @app.get("/") decorator defines the home() function as a route handler. That way, FastAPI will call the home() function when someone sends a GET request to your API’s root URL.
To see what this looks like in action, hop over to the terminal and run your FastAPI application with the following command:
The fastapi dev command is part of the FastAPI CLI. It starts your application in development mode with automatic reloading. This means that any changes you make to your code will automatically restart the server.
Visit http://127.0.0.1:8000 in your browser, and you’ll see your welcome message:
FastAPI automatically converts your Python dictionary into JSON format. When you navigate to http://127.0.0.1:8000/docs or http://127.0.0.1:8000/redoc, you’ll find interactive API documentation generated automatically from your code:
The documentation that FastAPI automatically created for you is powered by Swagger UI and Redoc. Swagger UI provides an interactive interface that lets you test your endpoints directly from the browser, while ReDoc offers a well-organized, read-only view of your API. The documentation comes in especially handy as you grow your API and create more endpoints.
Enhance Your API Endpoints
Now that you have a working FastAPI application, it’s time to add some functionality to your API. You’ll add endpoints that demonstrate different FastAPI features while creating a randomizer API. Each endpoint you create will showcase a different aspect of FastAPI, from handling dynamic URLs to processing complex request data.
Declare Path and Query Parameters With Types
FastAPI’s path parameters allow your API to handle dynamic URLs. For your randomizer API, you’ll create an endpoint that generates random numbers within a specified range using Python’s random module.
When designing API endpoints, path parameters typically represent specific resources or resource identifiers. In REST API architecture, URLs should be intuitive and hierarchical, such as /products/electronics/laptops, which shows a category hierarchy.
Update your main.py file to include a random number endpoint:
The {max_value} in the path is a path parameter. FastAPI uses the type hint max_value: int to automatically validate that the parameter is an integer.
Test the get_random_number() endpoint with the integer 10 by visiting http://127.0.0.1:8000/random/10 in your browser. As a response, you see that the maximum number got set correctly and that you’ve received a random number:
When you visit the same URL repeatedly, you’ll notice that your randomizer API works as expected by responding with random numbers between one and ten, inclusive.
If someone tries to access http://127.0.0.1:8000/random/ten, then FastAPI will return a validation error with a clear message explaining that the path parameter must be an integer. This automatic validation provides consistent error responses across your API:
FastAPI’s automatic validation saves you from manually writing type-checking code. The framework handles the conversion and validation, letting you focus on your application logic. When validation fails, FastAPI returns a standardized error response with a 422 status code and detailed information about what went wrong, making it easier for API consumers to fix their requests.
Add Query Parameters
Beyond path parameters, FastAPI also supports query parameters. They come in handy when you want to specify or filter an endpoint. In a customer API, /users?status=active&role=guest usually indicates you’re filtering for users who are both active and have a guest role. FastAPI makes implementing these patterns straightforward while adding automatic validation.
For example, you can create a /random-between endpoint for your API that returns a random number between a minimum and a maximum value. To keep your API endpoint flexible, you define min_value and max_value as optional query parameters:
If you’re not familiar with function annotations, then it may be surprising how much is going on here. It may not even look like the Python code you’re familiar with. Before you go over the details, you might want to recap the requirements of get_random_number_between():
When you access /random-between, you receive a random number between 1 and 99 by default. You can optionally set a minimum value, a maximum value, or both to adjust the range of random numbers. If you set a value, then it must be between 1 and 1000.
To get an impression of the get_random_number_between() annotation’s impact, you can visit http://127.0.0.1:8000/docs:
Every requirement you’ve set is reflected in the Swagger UI, giving the user a convenient explanation of what they can and can’t do when interacting with your /random-between endpoint.
Now, take a line-by-line look at how you set these requirements using query parameters in the function definition of get_random_number_between():
- Line 2 imports 
Annotatedfrom typing to add additional meta information to your type hint in lines 10 and 15. - Line 4 imports 
HTTPExceptionandQuery, which you’ll use to handle errors and provide metadata and constraints to your endpoint. - Line 8 uses the 
@app.get()decorator to declareget_random_number_between()as a GET endpoint at the path/random-between. - Line 9 defines the function 
get_random_number_between()that will handle requests to this endpoint. - Line 10 defines the 
min_valueparameter usingAnnotatedto combine the type hintintwithQuerymetadata, setting constraints and a default value of 1 in line 15. - Line 11 sets the human-readable title for this parameter that appears in the auto-generated API documentation.
 - Line 12 provides a longer explanation of what this parameter does.
 - Line 13 sets 
ge=1, which means themin_valuemust be “greater than or equal” to 1. - Line 14 sets a “less than or equal to” constraint, creating a valid input range of 1 to 1000.
 - Line 15 makes 
min_valueoptional by providing a default value of 1 if none is provided. - Lines 16 to 21 repeat the same pattern for the second parameter of your endpoint, making 
max_valueoptional and with a default value of 99. - Lines 23 and 24 validate that 
min_valueisn’t greater thanmax_value, raising anHTTPExceptionwith a400status code if this condition is violated. 
Finally, you return a dictionary in lines 26 to 30 containing the minimum value, maximum value, and a randomly generated integer between those bounds using random.randint().
Go ahead and visit http://127.0.0.1:8000/docs to see how Swagger UI displays the title, description, and the constraints:
Your automatic documentation picked up on the provided information. If you want to test your API in the browser, then you can visit these URLs:
http://127.0.0.1:8000/random-betweenhttp://127.0.0.1:8000/random-between?min_value=90http://127.0.0.1:8000/random-between?max_value=10http://127.0.0.1:8000/random-between?min_value=18&max_value=65
The Annotated syntax you added to your app allows you to combine type hints with FastAPI’s validation metadata in a clean, type-safe way that modern Python type checkers understand.
Handle Different Request Types
So far, you’ve created endpoints that read data using GET requests. In many applications, you’ll need to handle different types of operations—creating, reading, updating, and deleting data. These operations, known as CRUD operations, form the foundation of most APIs.
For your randomizer API, you’ll create endpoints that manage a collection of items. This could represent a list of names for a random name picker, a list of tasks for random assignment, or any other collection you want to randomize.
First, you’ll need a place to store your items. For this example, you’ll use an in-memory list to keep things straightforward:
You define items_db as an empty list that will store your items throughout the application’s life cycle. In a production application, you’d typically use a database, but this in-memory approach works well for demonstration purposes.
Now you’ll add an endpoint to create new items. This endpoint will accept a POST request with a dictionary containing an item name to add to your collection:
The /items endpoint handles POST requests. This means you can add an item to your items_db by sending a request body to your API that looks like this:
The add_item() function stores the provided value in your items_db dictionary under the item_name key. But first, the function verifies that the string isn’t empty and doesn’t already exist in your collection. If validation passes, it adds the item to items_db and returns a confirmation message. The HTTPException provides clear error messages when validation fails.
Visit http://127.0.0.1:8000/docs to post some items. If you have a look at the /items endpoint, then you may notice that the example is rather ambiguous:
FastAPI understands that your API requires a JSON. But it’s not clear how the JSON object should be structured. You’ll improve the documentation later in the Leverage Pydantic to Model Your Data section.
For now, you must remember to add a JSON object with the key "name" and a string as the value:
If your request body is valid, then FastAPI will respond with a JSON containing a message that says “Item added successfully” and the item you added.
Next, you’ll enhance the /items endpoint by adding another handler that retrieves all items from the database in a randomized order:
This GET endpoint creates a shallow copy of your items_db list and shuffles it randomly. By returning both the original and randomized orders, users can see how the randomization changed the sequence.
To update items, create an endpoint for /items/{update_item_name} with a function named update_item(). This function allows the endpoint to accept PUT requests with the goal of replacing one item with another.
Following the REST principles you learned about earlier, you’ll identify the item to update in the URL path and send the new value in the request body:
The update endpoint identifies the item to update via the URL path parameter and replaces it with the new value from the request body. It validates that the item exists and blocks updates that would create duplicates.
By now, you have endpoints for GET, POST, and PUT requests. Finally, you’ll add a DELETE endpoint to remove items from your in-memory items_db list:
To test these endpoints, you can use the interactive documentation at http://127.0.0.1:8000/docs. Try adding a few strings like “Python”, “Rust”, and “Go”, then retrieve them in random order, update one, and delete another. You’ll see how these four endpoints work together to provide complete CRUD functionality for your randomizer API.
These CRUD operations demonstrate how FastAPI handles different HTTP methods to perform distinct actions on your data. While this example uses in-memory storage, the same patterns apply when working with databases.
Leverage Pydantic to Model Your Data
Many API operations require structured data that goes beyond simple path or query parameters. FastAPI uses Pydantic models to define the structure of request bodies.
Pydantic provides data validation using Python type hints, ensuring that the data your API receives matches your expectations before your endpoint function even runs. This will make your code cleaner and your automatic documentation even more descriptive.
Define Request Bodies With Pydantic BaseModel
When your API needs to accept structured data, Pydantic models provide a clear and type-safe way to define that structure. You’ll start by creating a model for handling the list of items in your randomizer API.
To improve the CRUD operations you created earlier, you’ll refactor them to use a Pydantic model that provides better validation, clearer documentation, and more maintainable code.
First, create a Pydantic model to represent the data structure for an item:
The Item model defines the structure and validation rules for your data. The Field adds constraints and documentation that will appear in your API’s interactive documentation.
Next, update your add_item() function, which handles POST requests, to use the Item model. Since you’ll work with an item object, ensure to update previous mentions of item_name with an underscore (_) to item.name with a dot (.):
You can remove the first if condition you had before. With Pydantic, the validation for empty strings happens automatically based on the min_length=1 constraint. FastAPI will reject any request where the name field is empty or missing, returning a detailed validation error.
If you visit http://127.0.0.1:8000/docs after updating add_item(), then you can see the advantage that Pydantic models bring to your documentation:
Thanks to your Pydantic Item model, the documentation now shows a clear example of what contents and structure the request body should have.
Next, update the PUT endpoint to use the Item model as well by replacing the body parameter and using item.name:
The Item model makes it clear what data the endpoint expects. Instead of accepting an ambiguous dictionary, you now have a structured request body that clearly defines the fields of an item. This means you can remove the need to manually extract dictionary values and check for their existence.
You don’t need to update the get_randomized_items() function because it doesn’t take any arguments. Also, you shouldn’t update delete_item() to work with Item because you can’t use a Pydantic BaseModel for path parameters, as they’re just basic values extracted from the URL.
That said, there’s still room for improvement. At the moment, you’re using Pydantic to model your incoming data. Next, you’ll also use Pydantic to model your API responses.
Validate Responses With Pydantic Models
Pydantic models aren’t just for request validation. They’re equally powerful for ensuring your API returns consistent, well-structured responses. By using response models, you guarantee that your endpoints always return data in the expected format.
Just like before, start by defining the models. To differentiate these models from your existing ones and indicate that they’re meant for responses, it’s a good idea to name them accordingly:
Now update your endpoints to use these response models by adding the response_model keyword argument to the endpoint decorators and returning the models instead of dictionaries:
By specifying response_model, you ensure that your endpoints always return the expected structure. If your endpoint function returns data that doesn’t match the model, FastAPI will raise an error during development, helping you catch bugs before they reach production.
Response models also provide benefits beyond validation. They automatically filter out any extra fields that aren’t defined in the model, ensuring your API doesn’t accidentally expose sensitive data. They also generate accurate response schemas in your API documentation, showing users exactly what to expect from each endpoint.
Level Up Your FastAPI Example App
Your randomizer API now has several useful endpoints, but it still needs a few features to be production-ready—namely asynchronous I/O and solid security configuration. These advanced features will improve your API’s performance and ensure it can safely handle requests from web browsers and other clients. There’s also room to improve the documentation so it scales as the API grows.
Handle Asynchronous Requests
FastAPI supports both synchronous and asynchronous request handlers. Using async functions can improve your API’s performance, especially when dealing with input/output (I/O) operations. By leveraging Python’s async and await keywords, your API can handle multiple requests concurrently without blocking, making it more efficient under load.
You only need to add async before the def keyword to convert your endpoints to asynchronous endpoints. Here’s how to do this for your first two endpoints. Feel free to update the other endpoints accordingly:
The async def syntax tells Python these are coroutine functions. While generating random numbers doesn’t require async operations, this pattern becomes valuable when your endpoints need to perform heavy I/O-bound tasks. Understanding when and how to use asynchronous functions is crucial for building high-performance APIs. Your endpoints benefit from async operations when they need to:
- Query databases
 - Call external APIs
 - Read from files
 
The performance benefits of asyncio become apparent under heavy load. A synchronous API might handle hundreds of requests per second, while an async API can handle thousands, depending on the I/O operations involved.
Manage Security With CORS
When building APIs, you’ll often need to handle requests from web browsers running on different domains. Cross-Origin Resource Sharing (CORS) controls which domains can access your API.
Without proper CORS configuration, browsers will block cross-origin requests from JavaScript applications by default, preventing access to your API from different domains. This is a common scenario when your front end and API are hosted separately.
To add CORS configuration to your FastAPI-powered API, you must add CORS configuration to your add the CORSMiddleware to your project:
You use the .add_middleware() method to add the CORSMiddleware configuration to app. In this example, the CORS configuration allows requests from localhost:3000 and example.com. Here’s what each setting means:
allow_origins: Specifies which domains can access your APIallow_credentials: Determines whether credentials are supported in CORS requestsallow_methods: Identifies which HTTP methods are allowedallow_headers: Lists which headers the client can include
Allowing all headers ("*") can expose your API to security risks. In production, you should explicitly list the headers that are allowed to access your API, use HTTPS for secure communication, and carefully consider which HTTP methods and headers your API should accept from cross-origin requests.
Tidy Up Your FastAPI Documentation
Currently, all your endpoints are shown in one big list named “default” when you access the automatic documentation at http://127.0.0.1:8000/docs or http://127.0.0.1:8000/redoc. Also, your API is titled generically as “FastAPI”:
While this isn’t wrong, you can make your documentation a bit more descriptive with a few tweaks. Start by defining tag groups for the different logical parts of your API in a list named tags_metadata:
The tags_metadata list contains dictionaries in which you define groups with names and descriptions. In a moment, you’ll assign these groups to your endpoints to separate the items/ endpoints from the others.
Next, edit the class instantiation of FastAPI() to give your API a title, a description, and a version. Also, add an openapi_tags keyword argument with tags_metadata as its value:
With all the metadata in place, you need to assign your endpoints to the tag groups by adding the tags parameter to the endpoint decorator like this:
If you want to enhance the decorators of the other functions of your API, then you can copy these tag suggestions from this table:
| Function | Tags | 
|---|---|
get_random_number() | ["Random Playground"] | 
get_random_number_between() | ["Random Playground"] | 
add_item() | ["Random Items Management"] | 
get_randomized_items() | ["Random Items Management"] | 
update_item() | ["Random Items Management"] | 
delete_item() | ["Random Items Management"] | 
When you assign tags to an endpoint, you need to make sure to provide them as a list, even if you only want to assign one tag. Also, the strings in the list must match the tags you provide as the openapi_tags argument. Otherwise, you’ll inadvertently create a new tag without a description.
With the changes in place, have a look at your brushed up API documentation:
If you want to enhance your API documentation even more, it’s worth checking out the OpenAPI Specifications or the Swagger Docs.
The automatic API documentation that FastAPI offers is a great feature. However, there may be situations when you don’t want to expose your API endpoints. Once again, FastAPI has you covered—you can easily disable the automatic documentation.
Conclusion
You’ve built a complete FastAPI example application that showcases the framework’s most important features. Starting with a simple endpoint, you progressively added functionality that demonstrates real-world API development patterns.
In this tutorial, you’ve learned how to:
- Set up a FastAPI project with proper dependencies and structure
 - Use path parameters with automatic type validation
 - Handle JSON request bodies using Pydantic models
 - Implement asynchronous handlers for better performance
 - Configure CORS for secure cross-origin access
 
Your randomizer API serves as a foundation for more complex applications. You could extend it by adding database storage, user authentication, or WebSocket support for real-time features.
Frequently Asked Questions
Now that you have some experience with FastAPI in Python, you can use the questions and answers below to check your understanding and recap what you’ve learned.
These FAQs are related to the most important concepts you’ve covered in this tutorial. Click the Show/Hide toggle beside each question to reveal the answer.
FastAPI lets you build high-performance web APIs in Python. It uses type hints for automatic validation and generates interactive documentation at /docs, along with an alternative documentation page at /redoc. This means you can write less boilerplate code and focus on your API logic.
You write async def endpoints when your code waits for I/O-like database queries, HTTP calls, or file access. While it’s waiting, the event loop can serve other requests, which boosts throughput under load.
You add CORSMiddleware with .add_middleware() to your FastAPI() app and configure allow_origins, allow_methods, allow_headers, and allow_credentials. In production, you restrict these settings to only what your clients need.
You declare path parameters and add type hints like max_value: int, so FastAPI validates and converts input automatically. If validation fails, you get a clear 422 response with details, and you avoid manual checks.
** Take the Quiz:** Test your knowledge with our interactive “A Close Look at a FastAPI Example Application” quiz. You’ll receive a score upon completion to help you track your learning progress:
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Python REST APIs With FastAPI