I am building users API with CRUD operation in fastapi and i'd love to hear feedback about it. I have been exploring fastapi in the past weeks and im trying to create API with best practice (scalable - no pagination yet, robust, effective and efficient)
route.py
from fastapi import APIRouter, Depends
from users.serializer import User
from users.schema import UserUpdate, UserResponse, UserCreate, UserFilter
user_router = APIRouter(
prefix="/users",
tags= ["Users"]
)
@user_router.get("/",response_model=list[UserResponse])
def get_users():
return User.get_all()
@user_router.get("/filter", response_model=list[UserResponse])
def filter_user(query: UserFilter = Depends()):
filter = User.filter(dict(query))
return filter
@user_router.get("/{id}", response_model=UserResponse)
def get_user(id: str):
user = User.get(id)
return user
@user_router.post("/", response_model=UserResponse)
def save_user(user: UserCreate):
new_user = User(dict(user))
return new_user.create()
@user_router.put("/{id}", response_model=UserResponse)
def update_user(id: str, updated_user: UserUpdate):
user = User(updated_user)
return user.update(id)
@user_router.delete("/{id}")
def delete_user(id: str):
user = User.delete(id)
return user
schema.py
from typing import Optional
from pydantic import BaseModel, EmailStr, model_validator
from datetime import datetime
class UserCreate(BaseModel):
username: str
email: EmailStr
password: str
first_name: str
last_name: str
role: str
status: str
created_at: Optional[datetime] = datetime.now()
updated_at: Optional[datetime] = datetime.now()
class Config:
orm_mode: True
class UserUpdate(BaseModel):
username: str
email: EmailStr
first_name: str
last_name: str
role: str
status: str
created_at: Optional[datetime] = datetime.now()
updated_at: Optional[datetime] = datetime.now()
class Config:
orm_mode: True
class UserResponse(BaseModel):
id: str
username: str
email: EmailStr
first_name: str
last_name: str
role: str
status: str
created_at: Optional[datetime] = datetime.now()
updated_at: Optional[datetime] = datetime.now()
class UserLogin(BaseModel):
username: str
password: str
class UserFilter(BaseModel):
username: Optional[str] = None
email: Optional[EmailStr] = None
first_name: Optional[str] = None
last_name: Optional[str] = None
role: Optional[str] = None
status: Optional[str] = None
serializer.py
from utils.passwords import PasswordHandler
from gateways.database import DatabaseGateway
from fastapi import HTTPException, status
collection_name = "users"
class User:
def __init__(self, data) -> None:
self.data = data
def create(self):
password_handler = PasswordHandler()
self.data["password"] = password_handler.hash(self.data["password"])
user = DatabaseGateway(collection_name)
return user_serializer(user.save_document(self.data))
def update(self, id):
del self.data.created_at
user = DatabaseGateway(collection_name)
return user_serializer(user.update_document(id, self.data))
def delete(id):
user = DatabaseGateway(collection_name)
result = user.delete_document(id)
if result:
return user_serializer(result)
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="user not found!")
def filter(filter):
clean_filter = {k: v for k, v in filter.items() if v is not None}
users = DatabaseGateway(collection_name)
filtered_document = users.filter_document(clean_filter)
serialized_users = serialize_list(filtered_document)
if serialized_users:
return serialized_users
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="user not found!")
def get_all():
users = DatabaseGateway(collection_name)
serialized_users = serialize_list(users.get_collection())
if serialized_users:
return serialized_users
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="users not found!")
def get(id):
user = DatabaseGateway(collection_name)
result = user.get_document(id)
if result:
return user_serializer(result)
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="user not found!")
def user_serializer(data) -> dict:
user = {
"id": str(data["_id"]),
"username": data["username"],
"email": data["email"],
"first_name": data["first_name"],
"last_name": data["last_name"],
"role": data["role"],
"status": data["status"],
"created_at": data["created_at"],
"updated_at": data["updated_at"]
}
return user
def serialize_list(users) -> list:
return [user_serializer(user) for user in users]
gateway/database.py
from config.database import Database
from bson import ObjectId
from fastapi import HTTPException
class DatabaseGateway:
def __init__(self, collection_name):
self.collection_name = collection_name
def save_document(self, data):
document = Database.collection(self.collection_name).insert_one(dict(data))
data["_id"] = str(document.inserted_id)
return data
def get_collection(self):
return Database.collection(self.collection_name).find()
def get_document(self, id:str):
vba = ObjectId.is_valid(id)
if vba:
return Database.collection(self.collection_name).find_one({"_id": ObjectId(id)})
def update_document(self, id, data:dict):
document = Database.collection(self.collection_name)
return document.find_one_and_update({"_id": ObjectId(id)}, {"$set": dict(data)}, return_document=True)
def delete_document(self, id:str):
vba = ObjectId.is_valid(id)
if vba:
return Database.collection(self.collection_name).find_one_and_delete({"_id": ObjectId(id)})
def filter_document(self, filter):
return Database.collection(self.collection_name).find(filter)
config/database.py
client = MongoClient(config("db_client"))
db = client.fms
# list of modules/collection (users, author, books)
collections = {
"users": db['users']
}
class Database:
def collection(collection: str):
if collection in collections:
return collections[collection]
I'd love your review or opinion on this, small or big. Thanks a lot!