# OhMyAPI > Think: Django RestFramework, but less clunky and 100% async. OhMyAPI is a Django-flavored web-application scaffolding framework and management layer. Built around FastAPI and TortoiseORM, it is 100% async. It is ***blazingly fast***, ***fun to use*** and comes with ***batteries included***! **Features** - Django-like project-layout and -structure - Django-like project-level settings.py - Django-like models via TortoiseORM - Django-like `Model.Meta` class for model configuration - Easily convert your query results to `pydantic` models via `Model.Schema` - Django-like migrations (`makemigrations` & `migrate`) via Aerich - Django-like CLI tooling (`startproject`, `startapp`, `shell`, `serve`, etc) - Various optional builtin apps you can hook into your project - Highly configurable and customizable - 100% async **Goals** - combine FastAPI, TortoiseORM and Aerich migrations into a high-productivity web-application framework - tie everything neatly together into a concise API - while ***AVOIDING*** any additional abstractions ontop of Tortoise's model-system or FastAPI's routing system --- ## Getting started **Creating a Project** ``` pip install ohmyapi ohmyapi startproject myproject cd myproject ``` This will create the following directory structure: ``` myproject/ - pyproject.toml - README.md - settings.py ``` Run your project with: ``` ohmyapi serve ``` In your browser go to: - http://localhost:8000/docs **Creating an App** Create a new app by: ``` ohmyapi startapp tournament ``` This will create the following directory structure: ``` myproject/ - tournament/ - __init__.py - models.py - routes.py - pyproject.toml - README.md - settings.py ``` Add 'tournament' to your `INSTALLED_APPS` in `settings.py`. ### Models Write your first model in `turnament/models.py`: ```python from ohmyapi.db import Model, field from datetime import datetime from decimal import Decimal from uuid import UUID class Tournament(Model): id: UUID = field.data.UUIDField(primary_key=True) name: str = field.TextField() created: datetime = field.DatetimeField(auto_now_add=True) def __str__(self): return self.name class Event(Model): id: UUID = field.data.UUIDField(primary_key=True) name: str = field.TextField() tournament: UUID = field.ForeignKeyField('tournament.Tournament', related_name='events') participants: field.ManyToManyRelation[Team] = field.ManyToManyField('tournament.Team', related_name='events', through='event_team') modified: datetime = field.DatetimeField(auto_now=True) prize: Decimal = field.DecimalField(max_digits=10, decimal_places=2, null=True) def __str__(self): return self.name class Team(Model): id: UUID = field.data.UUIDField(primary_key=True) name: str = field.TextField() def __str__(self): return self.name ``` ### API Routes Next, create your endpoints in `tournament/routes.py`: ```python from ohmyapi.router import APIRouter, HTTPException, HTTPStatus from ohmyapi.db.exceptions import DoesNotExist from .models import Tournament # Expose your app's routes via `router = fastapi.APIRouter`. # Use prefixes wisely to avoid cross-app namespace-collisions. # Tags improve the UX of the OpenAPI docs at /docs. router = APIRouter(prefix="/tournament", tags=['Tournament']) @router.get("/") async def list(): queryset = Tournament.all() return await Tournament.Schema.model.from_queryset(queryset) @router.post("/", status_code=HTTPStatus.CREATED) async def post(tournament: Tournament.Schema.readonly): queryset = Tournament.create(**payload.model_dump()) return await Tournament.Schema.model.from_queryset(queryset) @router.get("/:id") async def get(id: str): try: queryset = Tournament.get(id=id) return await Tournament.Schema.model.from_queryset_single(tournament) except DoesNotExist: raise HTTPException(status_code=404, detail="not found") ... ``` ## Migrations Before we can run the app, we need to create and initialize the database. Similar to Django, first run: ``` ohmyapi makemigrations [ ] # no app means all INSTALLED_APPS ``` This will create a `migrations/` folder in you project root. ``` myproject/ - tournament/ - __init__.py - models.py - routes.py - migrations/ - tournament/ - pyproject.toml - README.md - settings.py ``` Apply your migrations via: ``` ohmyapi migrate [ ] # no app means all INSTALLED_APPS ``` Run your project: ``` ohmyapi serve ``` ## Authentication A builtin auth app is available. Simply add `ohmyapi_auth` to your INSTALLED_APPS and define a JWT_SECRET in your `settings.py`. Remember to `makemigrations` and `migrate` for the necessary tables to be created in the database. `settings.py`: ``` INSTALLED_APPS = [ 'ohmyapi_auth', ... ] JWT_SECRET = "t0ps3cr3t" ``` After restarting your project you will have access to the `ohmyapi_auth` app. It comes with a `User` and `Group` model, as well as endpoints for JWT auth. You can use the models as `ForeignKeyField` in your application models: ```python from ohmyapi.db import Model, field from ohmyapi_auth.models import User class Team(Model): [...] members: field.ManyToManyRelation[User] = field.ManyToManyField('ohmyapi_auth.User', related_name='tournament_teams', through='tournament_teams') [...] ``` Remember to run `makemigrations` and `migrate` in order for your model changes to take effect in the database. Create a super-user: ``` ohmyapi createsuperuser ``` ## Permissions ### API-Level Permissions Use FastAPI's `Depends` pattern to implement API-level access-control. In your `routes.py`: ```python from ohmyapi.router import APIRouter, Depends from ohmyapi_auth import ( models as auth, permissions, ) from .models import Tournament router = APIRouter(prefix="/tournament", tags=["Tournament"]) @router.get("/") async def list(user: auth.User = Depends(permissions.require_authenticated)): queryset = Tournament.all() return await Tournament.Schema.model.from_queryset(queryset) ... ``` ### Model-Level Permissions Use Tortoise's `Manager` to implement model-level permissions. ```python from ohmyapi.db import Manager from ohmyapi_auth.models import User class TeamManager(Manager): async def for_user(self, user: User): return await self.filter(members=user).all() class Team(Model): [...] class Meta: manager = TeamManager() ``` Use the custom manager in your FastAPI route handler: ```python from ohmyapi.router import APIRouter from ohmyapi_auth import ( models as auth, permissions, ) router = APIRouter(prefix="/tournament", tags=["Tournament"]) @router.get("/teams") async def teams(user: auth.User = Depends(permissions.require_authenticated)): queryset = Team.for_user(user) return await Tournament.Schema.model.from_queryset(queryset) ``` ## Shell Similar to Django, you can attach to an interactive shell with your project already loaded inside. ``` ohmyapi shell Python 3.13.7 (main, Aug 15 2025, 12:34:02) [GCC 15.2.1 20250813] Type 'copyright', 'credits' or 'license' for more information IPython 9.5.0 -- An enhanced Interactive Python. Type '?' for help. OhMyAPI Shell | Project: {{ project_name }} [{{ project_path }}] Find your loaded project singleton via identifier: `p` ``` ```python In [1]: p Out[1]: In [2]: p.apps Out[2]: {'ohmyapi_auth': { "models": [ "Group", "User" ], "routes": [ { "path": "/auth/login", "name": "login", "methods": [ "POST" ], "endpoint": "login", "response_model": null, "tags": [ "auth" ] }, { "path": "/auth/refresh", "name": "refresh_token", "methods": [ "POST" ], "endpoint": "refresh_token", "response_model": null, "tags": [ "auth" ] }, { "path": "/auth/introspect", "name": "introspect", "methods": [ "GET" ], "endpoint": "introspect", "response_model": null, "tags": [ "auth" ] }, { "path": "/auth/me", "name": "me", "methods": [ "GET" ], "endpoint": "me", "response_model": null, "tags": [ "auth" ] } ] }} ```