Our First API Route

Git Commit:  create user functionality
Time for our first real API route. Till now we have made very simple routes, This time we will be using pydantic schemas, database connections, and dependencies all at one endpoint. We are going to make a route that will enable the creation of users. Why are we doing this first? It's because, in our 'Blog' model, we have a foreign key to the user's table. So, we need an owner_id which is basically the id of the user model. Let's jump into the code but before that create this new folder structure:

backend/
├─.env
├─alembic/
├─apis/
│ ├─base.py                   #new file
│ └─v1/                       #new folder
│   └─route_user.py           #new file
├─core/
│ └─config.py
├─db/
│ ├─base.py
│ ├─base_class.py
│ ├─models/        
│ ├─repository/                 #new folder
│ │ └─user.py                   #new file      
│ └─session.py
├─alembic.ini
├─main.py
├─requirements.txt
├─schemas/

Now, we need to type the below lines in apis > v1 > route_user.py

from fastapi import APIRouter
from sqlalchemy.orm import Session
from fastapi import Depends

from schemas.user import UserCreate
from db.session import get_db
from db.repository.user import create_new_user

router = APIRouter()


@router.post("/")
def create_user(user : UserCreate,db: Session = Depends(get_db)):
    user = create_new_user(user=user,db=db)
    return user 

  • We will receive a post request at this endpoint.
  • create_user function will receive the user from request and the UserCreate schema will validate that it has a email in proper format, and a password.
  • We will get the database session using the dependency get_db that we made in the session.py file.
  • We are again calling a completely new function named create_new_user and passing the user data from the request and database session.
  • Here we are trying to follow a design pattern named repository pattern. In this way, our code that interacts with the database is completely separated from this route. In the future, If we want to use some other ORM e.g. Tortoise or Pweee then it will become easier as the ORM code is completely separated from the route. The responsibility of user creation is passed on to the create_new_user function so let's implement it.

Type the following in db > repository > user.py

from sqlalchemy.orm import Session

from schemas.user import UserCreate
from db.models.user import User
from core.hashing import Hasher


def create_new_user(user:UserCreate,db:Session):
    user = User(
        email = user.email,
        password=Hasher.get_password_hash(user.password),
        is_active=True,
        is_superuser=False
        )
    db.add(user)
    db.commit()
    db.refresh(user)
    return user
  1. Now we need to include the router in route_user.py file to our app in main.py file. But think of it, if we have 100 routers, should we import and include 100 routes in the main.py file? No, it will get cluttered, so, I am going to make a new router in apis > base.py file which will contain the information of other routers. Put the following code in apis > base.py
from fastapi import APIRouter

from apis.v1 import route_user


api_router = APIRouter()
api_router.include_router(route_user.router,prefix="",tags=["users"])

Just one step more, make sure we import this 'api_router' in the main.py file and include it with our app: main.py

from apis.base import api_router  #new

###

def include_router(app):   
	app.include_router(api_router)


def start_application():
    app = FastAPI(title=settings.PROJECT_NAME,version=settings.PROJECT_VERSION)
    create_tables()
    include_router(app)   #new
    return app

All done. Let's verify its working:


But there is a problem with this approach, Just look at all the data we are showing to our users. 
{
  "password": "$2b$12$qsPsF3.JVAwWYqsioLvuJOOSiJCZuWRv5yCBCuFdumoA1bYRlXhCO",
  "email": "[email protected]",
  "is_superuser": false,
  "id": 3,
  "is_active": true
}

We are passing hashed_password, id, and is_superuser status, These are implementation details and from a security point of view, we should not pass these data. Let's restrict our response to only username, email, and is_active status. Remember pydantic schemas? They are used to validate request data. Similarly, they can be used to restrict data to have only a limited number of fields.
Let's add a new schema in schemas > user.py
 

from pydantic import BaseModel,EmailStr, Field


#properties required during user creation
class UserCreate(BaseModel):
    email : EmailStr
    password : str = Field(..., min_length=4)


#new
class ShowUser(BaseModel):
    id: int
    email : EmailStr
    is_active : bool

    class Config():  #tells pydantic to convert even non dict obj to json
        orm_mode = True

and we need to specify this ShowUser schema as a response_model of our post route. I believe it would be better to call it request_schema!!  Ok, now we modify our apis > version1 > route_users.py as follows:

...
from fastapi import APIRouter, status          #modified
from schemas.users import UserCreate,ShowUser  #modified
from db.session import get_db
...

router = APIRouter()


@router.post("/",response_model = ShowUser, status_code=status.HTTP_201_CREATED)          #modified
def create_user(user : UserCreate,db: Session = Depends(get_db)):
    user = create_new_user(user=user,db=db)
    return user 

All done, now we are not passing hashed password, is_superuser in the response.

FastAPITutorial

Brige the gap between Tutorial hell and Industry. We want to bring in the culture of Clean Code, Test Driven Development.

We know, we might make it hard for you but definitely worth the efforts.

Contacts

Refunds:

Refund Policy
Social

Follow us on our social media channels to stay updated.

© Copyright 2022-23 Team FastAPITutorial