Skip to content

The REST API server starterkit based on the FastAPI framework.

Notifications You must be signed in to change notification settings

Burize/fast-api-starterkit

Repository files navigation

FastAPI starterkit

The REST API server starterkit based on the FastAPI framework. The starterkit implements the asynchronous programming model (async / await).

Main stack of libraries:

Features

Class based view

It is convenient and clear to combine all routes related to one model into a class. They use the same dependencies and work with the same model.

from dataclasses import dataclass
from http import HTTPStatus
from uuid import UUID

from injector import inject

from auth.models import User
from auth.repositories import UserRepository
from core.api import APIRouter
from core.api import controller
from core.exceptions import ConflictException

router = APIRouter()


@dataclass
class CreateUserDTO:
    username: str
    password: str
    email: str


@dataclass
class UserDTO:
    id: UUID
    username: str
    email: str

    
@controller
class UserController:
    @inject
    def __init__(self, user_repository: UserRepository):
        self._user_repository = user_repository

    @router.post('', no_authetication=True, status_code=HTTPStatus.CREATED)
    async def create(self, dto: CreateUserDTO) -> UserDTO:
        user_with_same_email = await self._user_repository.find_user_by_email(dto.email)
        if user_with_same_email:
            raise ConflictException('Email is already taken')

        user_with_same_username = await self._user_repository.find_user_by_username(dto.username)
        if user_with_same_username:
            raise ConflictException('Username is already taken')

        user = User(username=dto.username, password=dto.password, email=dto.email)
        await self._user_repository.save(user)

        return UserDTO(id=user.id, email=user.email, username=user.username)

Dependency injection

The dependency injection provided by FastAPI has several significant cons:

  • It creates a new thread for any class based dependency. More precisely, for any non-asynchronous function, and the __init__ can not be async.
  • It is not possibly to get dependency without anotation (Depends or Annotated). Moreover, annotations do not work everywere. For example, middlewares are maintained by Starlette (FastAPI is based on it), so FastAPI's annotations can not be used in arguments.
  • You need to provide an annotation for each function argument. It brings a lot of boilerplate code.

Therefore, the another library (Injector) is used for dependency injection.

For example, you can easily get SessionStorage in the middleware:

from fastapi import Request
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.middleware.base import RequestResponseEndpoint

from core import settings
from core.inject import injector
from core.session import SessionStorage


class UserSessionMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint):
        response = await call_next(request)

        session_id = request.cookies.get('session_id', None)
        user_id = getattr(request.state, 'user_id', None)
        is_logout = 'logout' in request.url.path
        if is_logout or not session_id or not user_id:
            return response

        session_storage = injector.get(SessionStorage)
        await session_storage.prolong_session(session_id)
        response.set_cookie(key=settings.USER_SESSION_NAME, max_age=settings.USER_SESSION_MAX_AGE, value=session_id)
        return response

About

The REST API server starterkit based on the FastAPI framework.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published