An Introduction to ⚡FastAPI
Tags: javascript, webdev, python, opensource
Published: 2023-10-17 10:50:46

<div>
<a href="https://s.refine.dev/fast-api">
   <img src="https://refine.ams3.cdn.digitaloceanspaces.com/blog-banners/oss-insight.png" alt="refine repo" /> 
</div>

**Author: <a  target="_blank" href="https://refine.dev/blog/author/obisike_treause/">Obisike Treause</a>**


## Introduction

  
Since its introduction to backend development, Python has grown in popularity, competing with pre-existing heavyweights such as [PHP](https://www.php.net/) and [.Net](https://dotnet.microsoft.com/en-us/languages). It has made the developer experience more efficient and streamlined by introducing simplicity and power. Despite being known to be slower than its counterpart, Python has thrived greatly in this ecosystem.

Several frameworks for developing web APIs have been developed, such as [Django](https://www.djangoproject.com/) and [Flask](https://flask.palletsprojects.com/en/2.3.x/), but the underlying speed problem has always been present. As a result, another Python framework, FastAPI, has been developed to combat this issue.

Steps we'll cover:
- [What is FastAPI](#what-is-fastapi)
- [Benefits of using FastAPI](#benefits-of-using-fastapi)
- [Comparing FastAPI with other Python frameworks](#comparing-fastapi-with-other-python-frameworks)
- [Understanding FastAPI by building a REST API for an inventory application](#understanding-fastapi-by-building-a-rest-api-for-an-inventory-application)
- [Advanced Concepts in FastAPI](#advanced-concepts-in-fastapi)


## What is FastAPI

FastAPI is a modern Python microframework that simplifies the creation of web APIs using Python programming. It allows developers to swiftly and easily build APIs, ensuring optimal performance and easy management without compromising code quality and efficiency.

It provides numerous advantages, including exceptional speed, outperforming several other Python backend frameworks, and competing with popular frameworks like Express.js.

FastAPI offers the simplicity of Flask as it closely resembles Flask but still packs out-of-the-box configurations such as validation, documentation, and response encoding.

## Benefits of using FastAPI

As previously emphasized, FastAPI stands out due to its exceptional benefits and extensive advantages. Let's delve into some of the notable benefits it offers:

- **Performance**: FastAPI maximizes performance by utilizing the full potential of critical libraries and tools such as [Pydantic](https://docs.pydantic.dev/latest/) and the [ASGI](https://asgi.readthedocs.io/) ecosystem. Furthermore, because of its solid foundation on the Starlette framework, it seamlessly integrates the power of async/await functionality.
- **Scalability**: The modularity and simplicity of FastAPI allow for seamless integration with load balancers, facilitating scalability and ensuring efficient resource utilization.
- **Automatic Documentation:** By requiring the explicit definition of various FastAPI components, Pydantic's integration has allowed FastAPI to be able to generate its API documentation automatically. FastAPI provides Swagger API documentation.
- **Ease-of-use:** FastAPI is a Python framework, so the benefits of using Python are inherited. Not only that, FastAPI makes creating your server and building endpoints simple and quick.
- **Request Validation**: FastAPI provides request validation with a much more detailed error message readable by users. This is also attributed to its use of Pydantic for request data type specification.

## Comparing FastAPI with other Python frameworks

FastAPI, a relatively new addition to the backend API ecosystem, competes with established Python giants such as Flask and Django. While Flask and Django have been recognized as leading frameworks in this space, it's important to see how they compare to FastAPI.

### Django Vs FastAPI

Django is a feature-rich Python backend framework that includes a variety of built-in libraries to meet the needs of various projects. It has powerful features like ORM, authentication mechanisms, and routing capabilities, which make it suitable for developing complex web applications.

FastAPI, on the other hand, shines as a nimble microframework that was purposefully designed to be lightweight. While it lacks a large library ecosystem, it compensates by being extremely fast. Unlike Django, which is limited by its app system, FastAPI uses modern Python techniques to unlock its inherent advantages and improve its performance.

### Flask Vs FastAPI

Flask is a lightweight framework used by Python developers to quickly build web applications. Flask stands out for its design, giving developers more control and flexibility when structuring their applications, ensuring that the applications are tailored to their specific needs or requirements.

FastAPI, on the other hand, focuses on developing highly performant and scalable applications with exceptional speed. It has additional benefits discussed in this article and is best suited for complex applications.

### Pyramid Vs FastAPI

Pyramid is yet another intriguing flavor of one of Python's most popular backend frameworks. It adheres to the "use only what you need" philosophy, which means it provides a minimalistic core that can be supplemented with various add-ons and libraries. This modular approach enables developers to pick and choose which components they need for their specific use cases, resulting in a lightweight and highly customizable framework.

FastAPI, on the other hand, prioritizes developer productivity and ease of use. It has a simple and intuitive API design, as well as clear documentation and extensive examples. It also comes with some pre-built tools, such as auto-documentation generation.

## Getting Started

With FastAPI, you can easily set up a project in a few steps. Firstly, like any other Python project, you'll need to set up your virtual environment. After that, you’ll need to install the packages, FastAPI and Uvicorn.

To do this, run the command:

```py
python -m pip install fastapi 'uvicorn[standard]'
```

With that, you can start creating the endpoints required for your application.

The packages **fastapi** and **uvicorn** are essential for setting up a FastAPI project. The package **uvicorn** creates the server that runs the FastAPI setup, while **fastapi** provides the necessary methods and configurations for creating endpoints.

### Creating your first route

To create your first route, create a file, **main.py,** which will contain all your code. Open the file in your text editor and add the following:

```py
from fastapi import FastAPI

fastapi = FastAPI()

@fastapi.get("/")
async def home():
    return {"data": "Hello World"}
```

In the above snippet, you’ll notice that you are importing the **FastAPI** class from the **fastapi** module and then instantiating the class.

The instance of the `FastAPI` class can then be used as a decorator for the handler function to set up the endpoints. This instance provides the REST API verbs such as PUT, DELETE, PATCH, GET, and POST and a way to set the resource path.

Once that is done, you can run the following command on the terminal to start the server.

```tsx
uvicorn main:fastapi --reload
```

The **main** represents the module import, and the **fastapi** is an instance of the FastAPI. The command above starts the server, which can then be accessed via the browser at **http://127.0.0.1:8000**.





 <div >
   <img  src="https://refine.ams3.cdn.digitaloceanspaces.com/blog/2023-08-07-fast-api/cli.png"  alt="introduction to fast api " />
</div>



To add another route, simply create a handler function e.g.

```py
def handler():
    return { "data": "from handler"}
```

After that, using the FastAPI instance you created, add the decorator

```py
@fastapi.post("/home-page")
```

To the function. This converts the function to an API endpoint.

You can use **@fastapi.post**, **@fastapi.put** or **@fastapi.patch** or **@fastapi.delete** to create different endpoints.

### Managing request and response bodies using FastAPI models

Sending and receiving data to an endpoint is a fundamental aspect of API development. When it comes to the sending aspect, there are multiple methods for sending data to an endpoint, one of which includes the following:

**Path parameters**: This method involves attaching short data directly to the URL path. To implement this functionality in a FastAPI's endpoint, you can refer to the example below:

```py
# other data goes here
@fastapi.get("/{name}")
async def get_name(name: str):
    return { "name": name }
```

In the above example, the **name** is a path parameter that is extracted from the URL and passed as a parameter to the **get_name** function. This way, you can conveniently access the data sent through the URL path.

**Query parameters**: They are similar to the path parameter, but the difference is that the query parameters are appended to the URL after a question mark ("?"). To implement query parameters in a FastAPI's endpoint, you can refer to the example below:

```py
@fastapi.get("/")
async def get_api_data(data_type: str, skip: int = 0, limit: int = 10):
	return { "data_type": data_type, "skip": skip, "limit": limit }
```

In the above example, the **skip** and **limit** parameters are query parameters. They are provided via the URL after the endpoint ("/") using the format **?key=value**. The **skip** and **limit** parameters have default values of 0 and 10, respectively, but can be overridden by providing different values in the URL.

**Body parameters:** This type is different from the previously mentioned. It’s a method whereby the data is encoded and appended to the request made to the endpoint. The body parameter is implemented in the following way:

```py
from pydantic import BaseModel
from fastapi import FastAPI

fastapi = FastAPI()
class Item(BaseModel):
    name: str
    price: float

@fastapi.post("/items")
async def create_item(item: Item):
    return {"item": item}
```

In the above example, we define an **Item** class that inherits from **BaseModel** provided by Pydantic. This system defines the whole request body parameters and provides more context to FastAPI.

**Headers and Cookies**: One of the major ways of sending extra information for context to the server is through headers and cookies. To do this with FastAPI, simply follow the example below. \
For Headers:

```py
from fastapi import FastAPI, Header

app = FastAPI()

@app.get("/items")
async def read_items(user_agent: str = Header(None)):
    return {"User-Agent": user_agent}
```

In the above example, the **user_agent** parameter is given the default, **Header(None)**, which tells FastAPI to extract the value of the header from the request. If the header is present in the request, **user_agent** will be set to it; otherwise,  **user_agent** will be set to **None**.

For Cookies:

```py
from fastapi import FastAPI, Cookie

app = FastAPI()

@app.get("/items/")
async def read_items(session_token: str = Cookie(None)):
    return {"session_token": session_token}
```

In this example, similarly to headers,  the **session_token** parameter is given the default value of **Cookie(None)**, indicating that FastAPI should inject the cookies set on the request into the **session_token** parameter. If no cookies are set, then the value of the **session_token** will be None.

### Preview the API Documentation

Once the server is running on the browser, visit **[http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs)**

 <div >
   <img  src="https://refine.ams3.cdn.digitaloceanspaces.com/blog/2023-08-07-fast-api/preivew-api.png"  alt="introduction to fast api " />
</div>


## Understanding FastAPI by building a REST API for an inventory application

To demonstrate the power of FastAPI, you’ll be building a REST API for a hypothetical inventory application. This API will be connected to a database, support image uploads, and have protected routes. This API will have the following endpoints:

- **GET /items** - To fetch all the items stored on the server
- **GET /items/{item_id}** - to get a specific item from the server
- **POST /items** - to get add a new item to our server
- **PATCH /items/{item_id}** - to update the item on the server
- **DELETE  /items/{item_id}** - to delete an item from the inventory

Then you’ll be creating more endpoints to handle the serving of files, and you'll also be adding a database and some sort of authentication to some of the endpoints using a middleware.

### Prerequisites

To follow along, you’ll need to have the following:

- Knowledge of Python
- Knowledge of HTTP, JSON, REST API and Python’s Virtual Environment
- A terminal
- Python 3.10 installed

### Setting the project

To do this, set up a virtual environment by following the [instructions](https://docs.python.org/3/library/venv.html). Once done, you must create the **src** directory in the virtual environment folder. This is where your code will reside.

Open the directory using your code editor.



 <div className="centered-image" >
   <img   width="300px"  src="https://refine.ams3.cdn.digitaloceanspaces.com/blog/2023-08-07-fast-api/folder-structure.png"  alt="introduction to fast api " />
</div>




### Installing Dependencies

You'll only need essential dependencies such as Uvicorn and FastAPI to accomplish this. However, since you'll be enhancing the server's capabilities by incorporating a database connection and a file upload system, installing additional dependencies like databases and SQLAlchemy is necessary.

To proceed, run the command:

```py
pip install 'fastapi[all]' 'uvicorn[standard]' databases sqlalchemy
```

This will install all the necessary dependencies for this project.

### Creating Your Endpoints

In your **src** directory, create a new file, **main.py**; this will be the entry point of your application.

So on the **main.py**, add the following:

```py
from fastapi import FastAPI
from fastapi import HTTPException
from .utils import find_item
from pydantic import BaseModel, Field
from typing import Optional

fastapi = FastAPI()

inventory = [
  { "id": 1, "name": "Treasure", "quantity": 3 }
]


class Item(BaseModel):
  name: str
  quantity: int

class ItemUpdate(BaseModel):
  name: Optional[str] = Field(None, description="Optional name of the item")
  quantity: Optional[int] = Field(None, description="Optional quantity of the item")

@fastapi.get("/items") 
async def get_items():
  return {"items": inventory}


@fastapi.get("/items/{item_id}")
async def get_item(item_id: int):
  item, idx = find_item(inventory, lambda x: x["id"] == item_id)
  return { "item": item }

@fastapi.delete("/items/{item_id}")
async def delete_item(item_id: int, authenticated: bool = Depends(authenticate)):
  item, idx = find_item(inventory, lambda x: x["id"] == item_id)
  if idx == -1: return HTTPException(404, "item not found")
  inventory.pop(idx)
  return { "item": item }



@fastapi.post("/items")
async def add_item(data: Item):
  item = {
    "id": len(inventory) + 1,
    "name": data.name,
    "quantity": data.quantity
  }
  inventory.append(item)
  return item

@fastapi.patch("/items/{item_id}")
async def update_item(item_id: int, item_update: ItemUpdate):
  item, idx = find_item(inventory, lambda x: x["id"] == item_id)
  if idx == -1:
    raise HTTPException(status_code=404, detail="Item not found")

  if item_update.name is not None:
    item["name"] = item_update.name

  if item_update.quantity is not None:
    item["quantity"] = item_update.quantity

  inventory[idx] = item

  return item
```

In the snippet above, there are five handlers for the endpoints. You have a local **inventory** data store that saves all the added **items**. The **ItemUpdate** class specifies the body parameter for the patch endpoint, allowing for optional parameters. The **Optional** class from the **typing** module and the **Field** class from **pydantic** are imported for creating these optional fields.

Add the [utility functions](https://github.com/Otrex/refine-fastapi/blob/main/src/utils.py) to a file, **utils.py**, in the src directory.

Following that, you can run the server to test the endpoints via the documentation.

## Advanced Concepts in FastAPI

APIs are usually not basic, like the inventory API created. Sometimes, you'll need to persist data or validate credentials before performing requests or handling files.

For your inventory app, you'll be adding the validation middleware, the file upload, and the database.

### Implementing the Authentication Middleware

Middlewares are like the valves of the API world. They can be used to do a lot of things, like restricting access to certain users, adding extra context to the request, and many more. To demonstrate its capability, you are going to create a middleware that allows access to clients that have a certain credential and add it to some of the endpoints of the inventory app.

Create a new file in the **src** directory called **middleware.py** and add the following code:

```py
from fastapi import HTTPException, Depends
from fastapi.security import HTTPBasic, HTTPBasicCredentials

security = HTTPBasic()

def authenticate(credentials: HTTPBasicCredentials = Depends(security)):
    correct_username = 'admin'
    correct_password = 'password'
    if credentials.username != correct_username or credentials.password != correct_password:
        raise HTTPException(status_code=401, detail="Unauthorized")
    return True
```

In the case above, the middleware, `authenticate`, implements basic authentication. To add this to your desired route handler, go to the **main.py** file, locate the endpoints you want to add **authentication** and add the parameter:

```py
authenticated: bool = Depends(authenticate)
```

After that, the handler will look like this:

```py
@fastapi.post("/items")
async def add_item(data: Item, authenticated: bool = Depends(authenticate)):
  item = {
    "name": data.name,
    "id": len(inventory) + 1,
    "quantity": data.quantity
  }
  inventory.append(item)
  return item
```

Ensure you import the **authenticate** from the **middleware.py** you created and **Depends** from **fastapi.**

### Database Integration

To add a database to our existing inventory app, we must first ensure that **sqlalchemy** and **databases** are installed. Then create a file, “**database.py**” in the **src** directory, Then we need to import the following:

```py
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
```

You’ll be using SQLite due to its simplicity.

Next, you'll need to create the database engine by adding the code to the file

```py
DATABASE_URL = "sqlite:///./database.db"
engine = create_engine(DATABASE_URL)
```

This is the basic connection configuration for the database.
After that, create the **SessionLocal** class, which creates a database session when called, and the **Base** class, which will serve as the **BaseModel** for all the database models to inherit.

On the same **database.py** file, add the following:

```py
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
```

Next, create a database model by creating by adding the following:

```py
class DBItem(Base):
    __tablename__ = "items"

    id = Column(Integer, primary_key=True, index=True)
    quantity = Column(Integer)
    name = Column(String)
```

After that, you'll need to import the **DBItem** and the **SessionLocal** from the  **database.py** file to the **main.py** file.

Additionally, make sure to update the route handlers accordingly.

```py
@fastapi.post("/items")
def create_item(item: Item, authenticated: bool = Depends(authenticate)):
    db = SessionLocal()
    new_item = DBItem(name=item.name, quantity=item.quantity)
    db.add(new_item)
    db.commit()
    db.refresh(new_item)
    return {"item" :new_item}

@fastapi.get("/items")
async def get_items():
  db = SessionLocal()
  items = db.query(DBItem).all()
  return { "items": items }


@fastapi.get("/items/{item_id}")
def get_item(item_id: int):
    db = SessionLocal()
    item = db.query(DBItem).filter(DBItem.id == item_id).first()
    if not item:
        raise HTTPException(status_code=404, detail="Item not found")
    return {"item": item}

@fastapi.patch("/items/{item_id}")
def update_item(item_id: int, item: ItemUpdate, authenticated: bool = Depends(authenticate)):
    db = SessionLocal()
    db_item = db.query(DBItem).filter(DBItem.id == item_id).first()
    if not db_item:
        raise HTTPException(status_code=404, detail="Item not found")
    db_item.name = item.name
    db_item.quantity = item.quantity
    db.commit()
    db.refresh(db_item)
    return { "item": db_item}

@fastapi.delete("/items/{item_id}")
def delete_item(item_id: int, authenticated: bool = Depends(authenticate)):
    db = SessionLocal()
    db_item = db.query(DBItem).filter(DBItem.id == item_id).first()
    if not db_item:
        raise HTTPException(status_code=404, detail="Item not found")
    db.delete(db_item)
    db.commit()
    return {"message": "Item deleted"}
```

In the code above, you’ll notice that you are instantiating the **SessionLocal** for each endpoint, which is then used to perform the database queries. After the query is performed, you can then commit it to the database for persistence.

### File Uploads

With FastAPI, file uploads can be done very easily. We’ll use our existing API to demonstrate how file uploads and serving can be achieved. First, you’ll have to update the DBItem in your **database.py** file by adding the following:

```py
class DBItem(Base):
    # other database fields goes here
    image_src = Column(String)
```

After that, on the **main.py**, import **File** and **UploadFile** from **fastapi**, import **os** and then import **FileResponse** from **starlette.responses**. After that, add the following:

```py
@fastapi.patch("/item-image/{item_id}")
async def upload_file(item_id: int, file: UploadFile = File()):
    db = SessionLocal()
    db_item = db.query(DBItem).filter(DBItem.id == item_id).first()
    if not db_item:
        raise HTTPException(status_code=404, detail="Item not found")
      
    file_path = os.path.join("uploads", file.filename)
    with open(file_path, "wb") as f:
        f.write(await file.read())
        
    db_item.image_src = file.filename
    db.commit()
    db.refresh(db_item)
    
    return {"item": db_item}
  
@fastapi.get("/static/{file}")
async def serve_file(file: str):
    return FileResponse(os.path.join("uploads", file))
```

The first route handler in the snippet above updates the **image_src** field of the specified item and uploads the file to the server. Then the second, **serve_file**, handles the file retrieval from the server.

Starlette offers several other types of responses, including the **FileResponse**, which can then be used on the route handler.




 <div className="centered-image" >
   <img    src="https://refine.ams3.cdn.digitaloceanspaces.com/blog/2023-08-07-fast-api/swagger-last.png"  alt="introduction to fast api" />
</div>

The source code for this inventory API can be found [here](https://github.com/Otrex/refine-fastapi).

## Conclusion

Congratulations on making it this far! By now, you should have gained valuable insight into using FastAPI to build your backend application. FastAPI is an exceptional, user-friendly, and highly effective API development tool. It provides the flexibility associated with a microframework and delivers exceptional performance, making it an excellent choice for your API development requirements.

## Resources

- [FastAPI documentation](https://fastapi.tiangolo.com)
- [Starlette documentation](https://www.starlette.io/)
- [Pydantic documentation](https://docs.pydantic.dev/latest/)