FastAPI with Django ORM and Admin
Published: 2022-12-03 07:11:14

## Introduction 😉
I'm Manato Kato, Japanese college student, and I love Python.
I'd like to introduce my new project "**_FastAPI with Django ORM and Admin_**".


## Beginning 📍
### Programming is a means to an end
The reason why I do programming is because I want to create something and give it shape. Therefore, I see programming as just a means to an end.


### The first technology I learned
**My first programming language was Python**, and I've been doing contests and internships in Python ever since. And **I often use Django** when writing server-side code. This is because Django was the first framework I ever touched, and I used Django+DRF in my internship.


### Disadvantages of Django
However, Django has some disadvantages.
- Not very good for API development
  - DRF is a must
  - Tends to be complex and verbose even with DRF
- **slow**.
  - cf. https://qiita.com/bee2/items/0ad260ab9835a2087dae


### Looking for a new framework
So I decided to touch a new framework.
As mentioned above, I did not want to learn a new language because what I want to do is create things and programming is just a means to an end. (I was trying to get introduced to the Go language at one point, but gave it up due to lack of time).
After much research, I learned that a framework called FastAPI was coming along quite well.


### Advantages of FastAPI
When I touched FastAPI, it was a revolution.
- I can include **typedefs** in Python.
  - I was very happy to see this feature, as I had a hard time with DX dropping due to the lack of types in Django development at my internship.
- Ability to output **API documentation** by default.
  - Genius.
  - It's very effective in the division of labor with the frontend.
- **fast**.
  - cf. [Python web framework performance comparison](https://qiita.com/bee2/items/0ad260ab9835a2087dae)


### Disadvantages of FastAPI
There are a lot of advantages as mentioned above, but I still miss Django.
- **Admin Page**.
  - This is still where the beauty of Django lies!
  - I really want it.
- ORM
  - My hands have grown comfortable with Django's ORM.


### Then...
Let's merge FastAPI and Django!!!


## Code 👨🏻‍💻
https://github.com/kathmandu777/fastapi-django-template

### Directory structure
```sh
├── README.md
├── docker-compose.yml
├── fastapi
│   ├── Dockerfile
│   ├── app
│   │   ├── __init__.py
│   │   ├── admin
│   │   │   ├── __init__.py
│   │   │   └── user.py
│   │   ├── api
│   │   │   ├── __init__.py
│   │   │   ├── auth.py
│   │   │   └── user.py
│   │   ├── apps.py
│   │   ├── dependencies
│   │   │   ├── __init__.py
│   │   │   └── auth.py
│   │   ├── migrations
│   │   │   ├── 0001_initial.py
│   │   │   └── __init__.py
│   │   ├── models
│   │   │   ├── __init__.py
│   │   │   ├── base.py
│   │   │   └── user.py
│   │   ├── routers
│   │   │   ├── __init__.py
│   │   │   ├── auth.py
│   │   │   ├── health.py
│   │   │   └── user.py
│   │   └── schemas
│   │       ├── __init__.py
│   │       ├── auth.py
│   │       └── user.py
│   ├── config
│   │   ├── __init__.py
│   │   ├── asgi.py
│   │   ├── exceptions.py
│   │   ├── jwt.py
│   │   ├── log.py
│   │   ├── password.py
│   │   ├── settings
│   │   │   ├── base.py
│   │   │   ├── local.py
│   │   │   └── production.py
│   │   └── urls.py
│   ├── fastapi.env
│   ├── manage.py
│   ├── media
│   ├── poetry.lock
│   ├── pyproject.toml
│   ├── scripts
│   │   ├── runlocalserver.sh
│   │   └── startserver.sh
│   └── static
│       └── admin
├── poetry.lock
└── pyproject.toml
```
I use Django as a reference, and use a separate style for each application.
- `models`: Django ORM
- `routers`: FastAPI routers
- `schemas`: FastAPI Pydantic models
- `api`: FastAPI view


### mounts
```py
"""
Django settings
"""
django_app = get_asgi_application()


"""
FastAPI settings
"""
from app.routers import auth_router, health_router, user_router

from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles

fastapi_app = FastAPI()

# routers
fastapi_app.include_router(health_router, tags=["health"], prefix="/health")

# to mount Django
fastapi_app.mount("/django", django_app)
fastapi_app.mount("/static", StaticFiles(directory="static"), name="static")
fastapi_app.mount("/media", StaticFiles(directory="media"), name="media")
```

Create an asgi application for Django and FastAPI, and mount the Django application from the FastAPI application.

### Models
Same as when creating in Django.

```py
class User(AbstractBaseUser, PermissionsMixin, BaseModelMixin):
    objects = UserManager()

    email = models.EmailField(_("email address"), unique=True)

    MIN_LENGTH_USERNAME = 1
    MAX_LENGTH_USERNAME = 20
    username = models.CharField(
        _("username"),
        max_length=MAX_LENGTH_USERNAME,
    )

    # permissions
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)

    ...
```


### Schemas
```py
from uuid import UUID

from pydantic import BaseModel


class ReadUserSchema(BaseModel):
    uuid: UUID
    username: str
    email: str

    class Config:
        orm_mode = True


class CreateUserSchema(BaseModel):
    username: str
    email: str
    password: str
```
`orm_mode = True` is important.



### Routers
Same as when creating in FastAPI.

```py
user_router = APIRouter()


@user_router.get("/", response_model=ReadUserSchema)
async def get(request: Request, current_user: User = Depends(get_current_user)) -> User:
    return UserAPI.get(request, current_user)


@user_router.post(
    "/",
    response_model=ReadUserSchema,
)
async def create(request: Request, schema: CreateUserSchema) -> User:
    return await UserAPI.create(request, schema)
```

### API
```py
class UserAPI:
    @classmethod
    def get(cls, request: Request, current_user: User) -> User:
        return current_user

    @classmethod
    async def create(cls, request: Request, schema: CreateUserSchema) -> User:
        user = await User.objects.filter(email=schema.email).afirst()
        if user:
            raise HTTPException(status_code=400, detail="Email already registered")
        schema.password = hash_password(schema.password)
        return await sync_to_async(User.objects.create)(**schema.dict())
```
It is important to apply asynchronous processing to the DB using `sync_to_async`, `afirst`, etc. Synchronous processing will be very slow.


## Asynchronous Processing in Django 4.1 🐍
Django 4.1 has been updated to allow asynchronous processing in the ORM, so you can now develop with a combination of FastAPI and Django like this. All of the reference articles below do not do asynchronous processing, so I expect them to be quite slow.


## Finally 🏁
I am happy to build my ideal style. Since it is in the template repository, I hope you will use it.
As I am new to FastAPI, I am sure there are some weird things I am doing. Please send me Issue, PR.


## References 📚
### Using FastAPI with Django - Stavros' Stuff
https://www.stavros.io/posts/fastapi-with-django
https://qiita.com/Ningensei848/items/ac72ff6edf4d887cdcc1

My encounter with this article was the beginning of this project.


### The integration of FastAPI and Django ORM
https://kigawas.me/posts/integrate-fastapi-and-django-orm/
https://github.com/kigawas/fastapi-django

Most helpful.


### Learn to Use Django with FastAPI Frameworks
https://nsikakimoh.com/learn/django-and-fastapi-combo-tutorials

I found it while writing this article.
I think it differs from this project in that it uses wsgi to run Django and does not support async for the Django ORM.