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.urlexample 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

Other Posts

Powered By Swish