How to Build a Python API from Scratch with FastAPI
Daniel Adeboye
Posted on September 5, 2024
Python has always been my go-to when building APIs for any of my projects, and in this tutorial, we’re going to build a simple blog application API using FastAPI. We’ll start with structuring our FastAPI project, installing the necessary dependencies, creating our application model structure, writing the functionalities for our application, and finally testing out our API.
💡 If you are interested in building something similar using Python Flask, you can check out my 1-hour-long video tutorial on Youtube.
Prerequisites
To follow this tutorial, you should have:
- Basic knowledge of Python
- Basic knowledge of API and how it works.
These are the steps that we will cover:
- Step 1 - Project Setup
- Step 2 - Running our first basic application
- Step 3 - Creating our Application Model Structure
- Step 4 - Writing our Application CRUD functionalities
- Step 5 - Testing our API endpoints
- Step 6 - Exercise
Step 1 - Project Setup
In this section we will focus on setting up our project, starting with folder creation, environment setup, and installing necessary dependencies.
Folder Creation
On your terminal create a project directory with any name of your choice. In my case I will call my project “anvil-blog”. You can also do this using your computer’s file explorer user interface.
$ mkdir anvil-blog
After creating your application folder, you will need to change your directory into “anvil-blog”.
$ cd anvil-blog
Environment Setup
Inside your project directory, we will create and activate a virtual environment, which will house all of our application dependencies. In this case, I called it “env”.
$ python -m venv env
$ env/bin/activate
Installing Dependencies
To build our API using FastAPI, we need to install necessary dependencies that will help our application run smoothly, and below are what we need:
- FastAPI - A web framework for building APIs with Python.
- Uvicorn - An ASGI web server implementation for Python
- Pydantic - A data validation library for Python.
Run the command below to install these packages:
(env)$ pip install fastapi uvicorn pydantic
Step 2 - Running our first basic application
Application Setup
Now that we have our project environment setup, it’s time to write some FastAPI code. Create a new file in your project folder and name it “main.py”, this is where we’ll write all of our application code.
Let’s add this code to our “main.py” file.
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def anvil():
return {"hello": "world"}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
Let’s break down the above code:
-
@app.get("/")
defines a route for our FastAPI application. When someone accesses the root URL (/
), theanvil
function is called. This function simply returns a JSON response with the message"hello": "world"
. -
if __name__ == "__main__":
ensures that our code runs only when the script is executed directly. Inside this block,uvicorn.run
is called to start the Uvicorn server, which serves our FastAPI app at the specified host (0.0.0.0
) and port (8000
).
Running our Application Server
To run our application server run the command below in your terminal:
(env)$ python main.py
You should get similar result as the image below:
You can now access your application at http://localhost:8000/
.
🎉 Congratulations, we have successfully run our first FastAPI application.
Step 3 - Creating our Application Model Structure ****
Earlier we installed Pydantic, a library that helps us validate and serialize data schemas, we’ll use this library to create our application model structure.
Let’s add this code to our “main.py” file:
from pydantic import BaseModel
from typing import List, Optional
from uuid import UUID, uuid4
class Blog(BaseModel):
id: Optional[UUID] = None
title: str
description: str
Now, let's break down the code:
- We've used
UUID
also known as universally unique identifier, to ensure that eachBlog
instance has a unique identifier. This helps us guarantee that theid
for each blog post is always unique. - The
Optional
type indicates that theid
field is not required when a new blog post is created. If theid
is not provided, it defaults toNone
, allowing the system to generate it automatically.
Creating a Temporary Database for our Model
Because we are not using a real database in this tutorial, we are going to use a list instead. Let’s add the code below under our Blog
model:
blogs = []
Here is what your “main.py” file should look like:
import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List, Optional
from uuid import UUID, uuid4
app = FastAPI()
class Blog(BaseModel):
id: Optional[UUID] = None
title: str
description: str
blogs = []
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
Step - 4 Writing our Application CRUD functionalities
You might be curious about what CRUD stands for. It represents the four main operations commonly performed in most applications: Create, Read, Update, and Delete.
Create Blog Post API
In the Create Blog Post API, we're going to allow users to create blog posts. Here's how it works:
@app.post("/blogs/", response_model=Blog)
def create_blog(blog: Blog):
blog.id = uuid4()
blogs.append(blog)
return blog
Now, let's break down the code:
-
response_model=Blog
tells FastAPI to use theBlog
model to structure the JSON response returned from the/blogs/
route. -
(blog: Blog)
specifies that we want to accept a new blog post using ourBlog
model. -
blogs.append(blog)
as you might already know adds the new blog post to our temporary database, which is represented by theblogs[]
.
Get Blog List API
In the Get Blog List API, we're going to allow users to retrieve all the blog posts they have created. Here's how it works:
@app.get("/blogs/", response_model=list[Blog])
def list_blogs():
return blogs
Now, let's break down the code:
-
response_model=list[Blog]
as you might already know tells FastAPI that the response will be a list ofBlog
objects. -
return blogs
: This line returns the entire list of blog posts stored in our temporary database, which is theblogs[]
.
Update Blog Post API
In the Update Blog Post API, we allow users to modify an existing blog post by providing its unique identifier (UUID) and the new data they wish to update. Here’s how it works:
@app.put("/blog/{blog_id}", response_model=Blog)
def update_blog(blog_id: UUID, blog_update: Blog):
for idx, blog in enumerate(blogs):
if blog.id == blog_id:
updated_blog = blog.copy(update=blog_update.dict(exclude_unset=True))
blogs[idx] = updated_blog
return updated_blog
raise HTTPException(status_code=404, detail="Blog not found")
The above code looks a bit more complex than the previous ones we've written, so let’s break it down:
-
@app.put("/blogs/{blog_id}")
route makes the update function accessible. The{blog_id}
part is a path parameter that takes the unique identifier of the blog post you want to update. -
blog_update: Blog
: This parameter indicates that the request body should contain aBlog
model with the new data to be applied. - The
update_blog
function iterates through theblogs
list and checks for a blog post with a matchingblog_id
. When it finds a match, it updates that entry using thecopy(update=blog_update.dict(exclude_unset=True))
method. This method updates only the fields provided in the request, leaving the others unchanged. - If the blog post is found and updated, the function returns the updated blog. If not, it raises an
HTTPException
with a 404 status code, indicating that the blog post was not found.
Delete Blog Post API
For this functionality, we want to allow users to remove a blog post by providing its unique identifier (UUID).
Let’s add this code to our "main.py" file:
@app.delete("/blogs/{blog_id}", response_model=Blog)
def delete_blog(blog_id: UUID):
for idx, blog in enumerate(blogs):
if blog.id == blog_id:
return blogs.pop(idx)
raise HTTPException(status_code=404, detail="Blog not found")
Let’s break down the above code:
-
@app.delete("/blogs/{blog_id}")
as you might already know, this route provides access to the delete function. Similar to the update route,{blog_id}
is a path parameter that takes the unique identifier of the blog post you want to delete. - The
delete_blog
function iterates through theblogs
list, and if it finds a blog post with a matchingblog_id
, it removes that post from the list usingpop(idx)
and returns the deleted blog. - If the blog post is not found, the function raises an
HTTPException
with a 404 status code, indicating that the blog post was not found.
💡 You can access the entire code for this tutorial on GitHub.
Step 5 - Testing our APIs
To test our application APIs, start your FastAPI project server using the command below in your terminal:
(env)$ python main.py
FastAPI, by default, automatically generates API documentation without any extra setup. To access your project’s API documentation, head over to http://localhost:8000/docs#/
.
You can also test your API endpoints using the API documentation.
Step 6 - Exercise
Write an API that gets individual blogs via their id
. Submit your answer as a pull request on this repository. I will give you feedback in 1-2 weeks. If you want a faster review, you can tag me on X with the URL to your pull request.
Conclusion
In this article, we learned how to build a Python API using FastAPI. We covered setting up a FastAPI project, defining the project model structure with Pydantic, implementing CRUD functionalities for our blog, and testing our APIs.
Posted on September 5, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 30, 2024