Articles

Build a FastAPI-Powered API with Python in Minutes

Published Apr 10, 2025Updated Apr 18, 2025
Build high-performance APIs with FastAPI and Python in minutes. Learn to create endpoints, validate data using Pydantic, handle errors, and compare FastAPI with Flask and Django.

What is FastAPI?

FastAPI is a high-performing web framework for building APIs with Python. Known for its exceptional speed and modern architecture, FastAPI has gained popularity in API development. Built on top of Starlette and Pydantic, it provides asynchronous support and robust data validation while keeping the codebase clean and efficient. Its design promotes best practices, enabling the development of scalable and maintainable web applications.

Read this article to understand how to use APIs in Python.

Key benefits of FastAPI

FastAPI stands out for several compelling features:

  • High performance: Supports asynchronous programming and handles large volumes of requests with minimal latency.
  • Automatic interactive documentation: Generates Swagger UI and ReDoc interfaces for real-time testing and exploration of endpoints.
  • Built-in validation: Utilizes Python type hints and Pydantic models for precise input validation and clear error messages.
  • Developer-friendly: Reduces bugs and increases development speed through editor support like autocomplete and type checking.

Now that FastAPI’s core strengths are clear, let’s focus on setting up the project environment and installing the necessary packages.

Related Course

Using OpenAI APIs: Accessing OpenAI APIs from Python

Excel in OpenAI APIs using Python. Discover API key authentication, access to completions APIs via endpoints, model configurations, and control of creativity and response length.Try it for free

Installing FastAPI

To start building APIs using FastAPI, the framework, and a server are required to run it. FastAPI works best with Uvicorn, a lightning-fast ASGI server that supports asynchronous programming and ensures high performance in production environments.

Before installation, it’s recommended to use a virtual environment to manage project dependencies efficiently and prevent conflicts across Python projects.

Step 1: Create a project directory

Begin by creating a dedicated folder for the FastAPI project:

mkdir fastapi_app # create the project directory
cd fastapi_app # navigate into the project directory

Here, fastapi_app is the folder name to be used.

Step 2: Set up a virtual environment

Create and activate a virtual environment in the project directory using the following command:

python -m venv venv
source venv/bin/activate # For Windows: venv\Scripts\activate

Step 3: Installing FastAPI and Uvicorn

With the virtual environment active, install FastAPI and Uvicorn using pip:

pip install fastapi uvicorn
  • fastapi: The main framework used to build APIs.
  • uvicorn: An ASGI server used to serve FastAPI applications.

With the environment set up and essential tools installed, the next step is to create the initial FastAPI application and define API endpoints.

Getting started with API creation

To start building the API, let’s create the main application file and define the initial routes. This section lays the groundwork for developing functional endpoints.

Creating the main file

The first step in API development is setting up the main application file. This file will serve as the entry point to the API. Inside the fastapi_app folder, create a new Python file to hold the FastAPI application:

touch main.py

The updated project folder should look like this:

fastapi_app/
├── venv/ # Virtual environment
├── main.py # FastAPI application file

Open main.py and add the following basic code to initialize a basic FastAPI app:

from fastapi import FastAPI
app = FastAPI() # Create an instance of the app.
@app.get("/")
def read_root():
return {"message": "Welcome to FastAPI!"}

This code performs the following:

  • Imports the FastAPI class.
  • Creates an instance of the app.
  • Defines a basic GET route for the root path / that returns a JSON message.

Use Uvicorn to run the application:

uvicorn main:app --reload

Here:

  • main refers to the filename main.py.
  • app is the FastAPI instance created inside the file.
  • --reload enables automatic reload on code changes.

After running this command, a local development server address will be displayed in the terminal, usually http://127.0.0.1:8000. Visit that address in the browser to see the default JSON response. The output will look like this right now:

Image showcasing the output of a basic get request from API

Let’s now define additional routes and explore how to handle different requests.

Creating endpoints in FastAPI

In FastAPI, API endpoints, also known as routes - are used to define how an application responds to different HTTP requests. These routes serve as the main entry points for client interactions with the API. Let’s create a basic grocery list endpoint that returns a welcome message:

from fastapi import FastAPI
app = FastAPI()
@app.get("/grocery")
def read_groceries():
return {"message": "This is your grocery list."}

Use the Uvicorn command mentioned earlier to test this endpoint and add /grocery at the end of the URL. This is how it should look in the output:

Image showcasing the output of a get request from API with endpoints

This output confirms the API is working. Next, we’ll enhance the endpoint using query and path parameters.

Using query and path parameters to enhance endpoints

FastAPI supports query and path parameters to make APIs flexible and powerful. Query parameters are optional values passed in the URL after a ?, while path parameters are part of the URL path itself.

FastAPI uses decorators like @app.get() to define API routes. This tells FastAPI which URL should trigger a specific function when an HTTP GET request is made.

Update the main.py file with the following code:

from fastapi import FastAPI
app = FastAPI()
grocery_list = []
# Query parameter: Add item to the grocery list
@app.get("/grocery")
def add_grocery_item(item: str = None):
if item:
grocery_list.append(item)
return {"grocery_list": grocery_list}
# Path parameter: Retrieve item by its index
@app.get("/grocery/{item_id}")
def get_grocery_item(item_id: int):
if 0 <= item_id < len(grocery_list):
return {"item": grocery_list[item_id]}
return {"error": "Item not found"}

For the first function, add_grocery_item():

  • @app.get("/grocery"): This defines a GET endpoint at /grocery.
  • The function handles two actions in one route:
    • If an item is passed using a query parameter like ?item=milk, it adds the item to the list.
    • If no item is passed, it returns the current grocery list.
  • The query parameter item: str = None makes the item optional. If provided, it’s added to the list.

For the second function, get_grocery_item():

  • @app.get("/grocery/{item_id}"): This defines a GET endpoint with a path parameter for the item index.
  • This function fetches a specific item from the list using its index.
  • The item_id (path parameter) is captured from the URL path. It’s automatically converted to an integer.
  • The function also checks if the index is within bounds before accessing the list.

To add items using the query parameter, use the following command:

curl "http://127.0.0.1:8000/grocery?item=milk"
curl "http://127.0.0.1:8000/grocery?item=bread"

To retrieve an item by index using the path parameter, use the following command:

curl http://127.0.0.1:8000/grocery/1

Note: These requests can also be tested by visiting http://127.0.0.1:8000/grocery or http://127.0.0.1:8000/grocery/0 in the browser.

Smooth requests are outstanding, but what happens when things go wrong? Let’s handle errors next.

Handling HTTP errors

In real-world APIs, it is essential to handle situations when something goes wrong - like when a user requests an item that doesn’t exist. FastAPI provides a way to raise and handle HTTP errors using the built-in HTTPException class.

The table highlights a few standard HTTP status codes often used in API development:

HTTP Status Code Description
400 Bad Request The request is malformed or missing the required data.
404 Not Found The requested resource doesn’t exist.
500 Internal Server Error Something went wrong with the server.

Let’s modify the /grocery/{item_id} endpoint to raise a proper HTTP error instead of returning a custom message:

from fastapi import FastAPI, HTTPException
app = FastAPI()
grocery_list = []
@app.get("/grocery/{item_id}")
def get_grocery_item(item_id: int):
if 0 <= item_id < len(grocery_list):
return {"item": grocery_list[item_id]}
raise HTTPException(status_code=404, detail="Grocery item with the given ID does not exist. ")

In this code:

  • HTTPException is imported from fastapi.
  • If the requested index is out of bounds, the function raises a 404 error.
  • status_code=404 tells the client the item wasn’t found.
  • detail="Grocery item with the given ID does not exist." gives a clear error message in the response.

If an item that does not exist is accessed, for example, using:

curl http://127.0.0.1:8000/grocery/5

The API will return the customized error message. Once errors are handled, it’s time to make our APIs smarter with structured JSON input.

Handling JSON requests with Pydantic

In FastAPI, data can be sent to the server using query parameters, path parameters, or JSON request bodies. Recall the following code, where a query parameter is used to add an item to a grocery list:

@app.get("/grocery")
def add_grocery_item(item: str = None):
if item:
grocery_list.append(item)
return {"grocery_list": grocery_list}

In this setup, the item is passed as a query parameter in the URL (e.g., ?item=Milk). However, passing it as a JSON body is more practical and scalable when dealing with more complex or structured data, such as an item with both a name and quantity.

FastAPI integrates with Pydantic to handle and validate JSON data sent in the body of a POST request. This makes it easy to ensure the input is complete and in the correct format.

Here’s how to update the grocery list API to accept JSON data:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel # BaseModel is used to define the structure and validation of request data
app = FastAPI()
grocery_list = []
# Define a Pydantic model for the expected JSON structure in POST requests
class GroceryItem(BaseModel):
name: str # 'name' should be a string
quantity: int # 'quantity' should be an integer
# This endpoint accepts POST requests with JSON data matching the GroceryItem model
@app.post("/grocery")
def add_grocery_item(item: GroceryItem):
grocery_list.append({"name": item.name, "quantity": item.quantity})
return {"grocery_list": grocery_list}
@app.get("/grocery")
def read_grocery_list():
return {"grocery_list": grocery_list}

In this code:

  • GroceryItem is a Pydantic model used to define the structure of the incoming JSON object.
  • The @app.post("/grocery") decorator sets up a POST endpoint.
  • FastAPI automatically:
    • Parses the incoming JSON request.
    • Validates the structure based on the GroceryItem model.
    • Returns a helpful error if the structure or types don’t match.
  • If validation passes, the item is added to grocery_list.

To test this, use the following curl command that sends a POST request with a JSON body:

curl -X POST -H "Content-Type: application/json" -d "{\"name\": \"Eggs\", \"quantity\": 12}" http://127.0.0.1:8000/grocery

In this command:

  • -H stands for header, and it sets the request header. Here, it tells the server that we’re sending JSON data.
  • -d stands for data, and it sends the JSON payload (the body of the POST request) to the API.

The response from the API will look like this:

{
"grocery_list": [
{
"name": "Eggs",
"quantity": 12
}
]
}

Using response models in FastAPI

When our API sends data back to the user—such as after adding an item or fetching a list—the structure of that data must be clear, consistent, and well-defined. This is where response models come in.

They allow us to define what our outgoing data should look like using Pydantic’s BaseModel. This helps FastAPI validate and format the response before it’s returned to the client.

Response models have the following benefits:

  • Standardized Output: This ensures that responses always follow a consistent structure, regardless of the logic inside your function.
  • Security: Prevents accidental exposure of sensitive or internal data by explicitly defining what should be included.
  • Structured Data: Makes your API predictable and improves the quality of your documentation.

We already use a model to receive data. Now, let’s define a model for the data we send back:

from pydantic import BaseModel
class GroceryItemResponse(BaseModel):
name: str
quantity: int

This tells FastAPI: “Any response involving a grocery item must include just a name and quantity, both with their proper types.”

We’ll modify our endpoints to apply this model using the response_model parameter. Here’s how the updated code looks:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List
app = FastAPI()
grocery_list = []
# Input model
class GroceryItem(BaseModel):
name: str
quantity: int
# Response model
class GroceryItemResponse(BaseModel):
name: str
quantity: int
# POST endpoint: Add item to the grocery list
@app.post("/grocery", response_model=List[GroceryItemResponse])
def add_grocery_item(item: GroceryItem):
grocery_list.append({"name": item.name, "quantity": item.quantity})
return grocery_list
# GET endpoint: Return all grocery items
@app.get("/grocery", response_model=List[GroceryItemResponse])
def read_grocery_list():
return grocery_list
# GET endpoint: Return one item by ID
@app.get("/grocery/{item_id}", response_model=GroceryItemResponse)
def get_grocery_item(item_id: int):
if 0 <= item_id < len(grocery_list):
return grocery_list[item_id]
raise HTTPException(status_code=404, detail="Grocery item with the given ID does not exist.")

In this code:

  • A new class GroceryItemResponse has been introduced. This acts as a filter and validator for outgoing responses.
  • Each route that sends grocery item data now uses response_model= to enforce this structure.
    • /grocery (POST and GET) returns a list of GroceryItemResponse.
    • /grocery/{item_id} returns a single GroceryItemResponse.
  • FastAPI uses this model to shape and validate what’s returned to the client automatically.
  • Even if the underlying data (grocery_list) changes internally, the external output remains clean and predictable.

Now that our API returns structured responses, let’s explore how to view and test it easily using FastAPI’s interactive documentation.

Interactive documentation feature in FastAPI

One of FastAPI’s standout features is its automatic generation of interactive documentation. With no extra setup, it provides a built-in UI to view and test your API, making development and debugging incredibly convenient. The table highlights the two types of docs that FastAPI offers:

Feature Swagger UI ReDoc
Interface A dynamic, interactive interface where you can explore endpoints, fill in inputs, and click “Try it out” to test your API directly. A clean, read-only interface that organizes your API schema in a well-structured format.
Ideal For Developers during the testing phase Sharing API references with other teams or external users
URL Available at /docs Available at /redoc

To try it, start your FastAPI server:

uvicorn main:app --reload

Then open these in your browser:

Image showcasing the Swagger UI doc for this API

Image showcasing the ReDoc for this API

You’ll see your grocery store API in action:

  • Submit items using POST /grocery
  • View all items via GET /grocery
  • Retrieve a specific item with GET /grocery/{item_id}

Both interfaces automatically update as your API evolves.

With our basic API up and running, it’s time to step back and see where FastAPI stands in the broader landscape of Python web frameworks.

FastAPI vs. Flask vs. Django

FastAPI, Flask, and Django are widely used Python web frameworks, each suited for different use cases. One may fit better depending on our project’s size, performance needs, and development goals.

The table below highlights how they compare across key features:

Feature FastAPI Flask Django
Speed Very fast Moderate Moderate
Asynchronous Support Built-in async support Requires extra setup (e.g. Quart) Limited (available in newer versions)
Validation Built-in via Pydantic Manual or with extensions Form-based validation
Automatic Docs Yes (Swagger UI & ReDoc out of the box) No (requires extensions) No (needs third-party tools)
Use case APIs and microservices Lightweight APIs & web apps Full-scale web applications
ORM Support External (SQLModel, Tortoise ORM) External (SQLAlchemy, etc.) Built-in ORM (powerful and mature)
Community & Resources Growing rapidly Mature and large Very large and well-established

Each framework has its strengths—choosing the right one depends on your project’s complexity, performance needs, and development style.

Conclusion

FastAPI offers a fast and modern way to build APIs with minimal effort. Throughout this article, we explored how to create APIs using FastAPI - creating routes, handling requests, validating inputs, and structuring responses. Interactive docs like Swagger UI and Redoc simplified testing. Finally, we compared FastAPI with Flask and Django to understand their strengths.

To dive deeper into building robust APIs and expand your backend development skills, check out Codecademy’s Create REST APIs with Spring and Java skill path.

Frequently asked questions

1. Why is FastAPI faster than Flask?

FastAPI is built on Starlette and Pydantic, using Python’s asyncio for asynchronous execution. This makes it highly performant, especially for handling multiple requests concurrently, unlike Flask’s synchronous nature.

2. Is FastAPI better than REST API?

FastAPI is a framework for building REST APIs. It's not a replacement but a modern tool to build APIs quickly, with automatic validation, async support, and interactive docs.

3. Can I use FastAPI with databases like PostgreSQL or MongoDB?

Yes, FastAPI integrates smoothly with ORMs like SQLAlchemy for PostgreSQL and tools like Motor or ODMantic for MongoDB.

4. Does FastAPI support authentication?

Absolutely! FastAPI supports OAuth2, JWT, and API key-based authentication out of the box, with built-in tools to manage security and dependencies.

5. Can FastAPI be used for full-stack applications?

Yes. You can pair FastAPI with frontend frameworks like React or Vue, or use templating engines like Jinja2. However, Django might offer more built-in features for more traditional full-stack needs.

Codecademy Team

'The Codecademy Team, composed of experienced educators and tech experts, is dedicated to making tech skills accessible to all. We empower learners worldwide with expert-reviewed content that develops and enhances the technical skills needed to advance and succeed in their careers.'

Meet the full team