Creating a CRUD App with FastAPI (Part one)
This article was first published in medium https://gabbyprecious.medium.com/creating-a-crud-app-with-fastapi-part-one-7c049292ad37 and migrated here.
Creating a CRUD web API could be as hard as a nut or as easy as your first hello world app in a new languange. Building a web app with FastAPI is one way around the tussle.
Introduction
FastAPI is one of Python's popular web framework.. FastAPI is popular mostly because it comes battery included with awesome tooling such as for it's speed(due being based on Starlette, using ASGI), easy type hint for validation, easy serialization and amazingly two API documentation providers.
Learning FastAPI, it's expected that you should be familiar with Python (cause it could be strenuous to pick up but would get easy along the way with basic python knowledge) and using the Terminal. Knowledge of Django or Flask will be a plus although not necessary.
In this series we will be creating a MiniTwitter site, it's going to have basic features like users, followers, and posts, and CRUD functionality on posts and users. CRUD stands for Create, Read, Update and Delete. Those are basic actions performed on most sites especially social media sites.
First Steps
Let's get started, first you create a directory named minitwitter and change to the directory, create a virtual environment for FastAPI and install FastAPI and Uvicorn.
On windows:
mkdir minitwitter
cd minitwitter
mkvirtualenv fast-api
pip install fastapi uvicorn, mysql-python
Note: if mysql-python doesn't install
pip install pymysql
Fast-api comes with fastapi-sqlalchemy and pydantic when trying to install. If it doesn't, try installing them individually via pip
FastAPI doesn't have it's server like Django and Flask, so Uvicorn is an ASGI server which will be used for production and serving of a FastAPI
Running First Program
Let's run our first app create file, main.py inside minitwitter using your favourite IDE , write this:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
from fastapi import FASTAPI
takes the FastAPI class from the package, which is used in app = FASTAPI()
for initializing app
as an instance of the class.
app.get("/")
performs a get function on the app. Using the HTTP:// method
async def root()
is a basic function that returns a dict/ json return {"message:"Hello World"}
On the terminal, run:
uvicorn main:app --reload
main
refers to the main.py file and app
refers to the instance app in the file. --reload
tells the server to update after changes in the files**(this is only recommended for development)**.
Your app is hosted locally on http://127.0.0.1:8000/ FastAPI provides you with an API interactive docs which you can use when creating your API, you can use either the http://127.0.0.1:8000/docs or http://127.0.0.1:8000/redocs.
Alembic Setup
Now let's start building the model for our minitwitter, first we install Alembic. Alembic is a migration tool used in python for connecting sqlachemly classes as models on a database. To install and run alembic run these commands:
pip install alembic
alembic init alembic
alembic init alembic
creates a alembic.ini file and an alembic
folder in your project directory, your directory should look like this:
To build a database we will use MySQL as it is easy to use. You can install mysql(link)If you have MySQL running go and run MySQL command line and create a database
CREATE DATABASE minitwitter;
Creating for production we will be connecting to the database in an .env
file that won't be uploaded on Github or Docker. Create a .env file, pip install python-dotenv
, python-dotenv is a package used in reading .env files.
Go to the alembic.ini file and edit line 26
sqlalchemy.url = driver://user:pass@localhost/dbname
delete the example link;
sqlalchemy.url =
In the .env file, edit it to
SQLALCHEMY_DATABASE_URI =mysql://root:root@localhost/minitwitter
if mysql-python had refused to install, run this instead,
SQLALCHEMY_DATABASE_URI='mysql+pymysql://root:root@localhost/minitwitter'
Note we are following the abovesqlalchemy.url
example and can edit your personal username and password.
Create a model.py
file in your base directory.
Open the alembic\env.py
file, add this line of code after line 6.
import os, sys
from dotenv import load_dotenv
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
load_dotenv(os.path.join(BASE_DIR, ".env"))
sys.path.append(BASE_DIR)
this is used to access the base directory of your project, and load the .env file. Then after line 17, write
config.set_main_option("sqlalchemy.url", os.environ["SQLALCHEMY_DATABASE_URI"])
This code is to overwrite the sqlachemy url in the alembic.ini file. After line 29
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
Import the model.py
file and overwrite target_metadata
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
import model
target_metadata = model.Base.metadata
This is to enable detection of models and auto-generation into a migration file by alembic
Modelling and First Migration
Let's edit model.py
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
first_name = Column(String(150))
last_name = Column(String(150))
username = Column(String(150))
email = Column(String(150))
password = Column(String(150))
Let's make our first migration. On the terminal, run:
alembic revision --autogenerate -m "create user table"
alembic upgrade head
alembic revision --autogenerate -m "create user table"
creates a migration file, without --autogenerate
the models.py isn't detected for the migration file. alembic upgrade head
upgrades to the database changes in the migration file.
To check if our migration was correct, On MySQL terminal, run:
minitwitter;
show tables;
You should see something like this:
Adding Data to Database
To be able to add data to our database we will need a pydantic schema of each model that helps in serializing and deserializing models into JSON objects to be read by the browser and database. Create a schema.py
file in the base of our project. In schema.py, write;
from pydantic import BaseModel
class UserBase(BaseModel):
username: str
class UserCreate(UserBase):
first_name: str
last_name: str
email: str
password: str
class User(UserBase):
id: int
class Config:
orm_mode = True
The UserBase
class is a class on which other users class like UserCreate
and User
is based on, UserCreate
is used for validation and sending data to the server, while User
to send values and rows from the database.
Create a folder routers
folder in our project directory, add the following files, __init__.py
, and users.py
in our project directory create a __init__.py
file also. Your directory should be looking like this:
Building with production in mind, it is necessary that the password are hashed so they can't be read. To hash our password and also read hashed password for validation, we will need to use the bcrypt library.
In routers\user.py
add the following lines;
import bcrypt
from fastapi import APIRouter
from fastapi_sqlalchemy import db
#the following line of code are to import the user in our model and schema
from model import User as ModelUser
from schema import UserCreate as SchemaUser
from schema import User as Users
router = APIRouter()
@router.post("/register", response_model=Users)
async def create_user(user: SchemaUser):
hashed_password = bcrypt.hashpw(user.password.encode('utf-8'), bcrypt.gensalt())
User = ModelUser(first_name=user.first_name, last_name=user.last_name, username=user.username, email=user.email, password = hashed_password)
db.session.add(User)
db.session.commit()
db.session.refresh(User)
return User
Here, we imported bcrypt
for hashing our password, APIRouter
to creates route outside our app
instance in main.py
and connect to it(similar to blueprints in Flask) and db
to commit our user to the database. Also we imported the User
model and the UserCreate
schema for taking in our values and validating it respectively, while Users
from schema.py
to send user's info back.
router = APIRouter
creates an instance.
/register
takes in values from SchemaUser
in def create_user(user: SchemaUser)
hashes the password, passes the value to the ModelUser
and sends it to the database, refreshes the User
values and returns back User
with the Users
schema.
Now, edit your main.py
file to look like this:
from fastapi import Depends, FastAPI, Header, HTTPException
#this line imports users.py from the routers folder
from routers import users
import os
from fastapi_sqlalchemy import DBSessionMiddleware #middleware helper
#Also it will be will be import load_dotenv to connect to our db
from dotenv import load_dotenv
#this line is to connect to our base dir and connect to our .env file
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
load_dotenv(os.path.join(BASE_DIR, ".env"))
app = FastAPI()
#this is to access the db so any route can acccess the database session
app.add_middleware(DBSessionMiddleware,
db_url=os.environ["SQLALCHEMY_DATABASE_URI"])
@app.get("/")
async def root():
return {"message": "Hello World"}
#this imports the route in the user into the main file
app.include_router(users.router)
Now let's run the server again, then go to http://127.0.0.1:8000/docs
It should look like this:
Here you can see the two routes created on our app, /
and /register
and their methods. click on the POST method on user, click on Try it out and fill in the values and Execute. You can try it multiple times to create different users.
In the next part, we will be creating user authentication, follow function and more.
You can locate the repo here. You can also reach out via the comment session or on send a message on Twitter