One folder per feature
Every Austial project uses the same layout: one folder per feature under src/modules, each holding its module, controller, service and DTOs. The CLI generates it for you, so new features land in the same place every time.
my-app/
├── pyproject.toml
├── AGENTS.md
├── src/
│ ├── main.py
│ ├── app_module.py
│ ├── app_controller.py
│ ├── app_service.py
│ └── modules/
│ └── health/
│ ├── health_module.py
│ ├── health_controller.py
│ ├── health_service.py
│ ├── health_dto.py
│ └── guards/api_key_guard.py
└── tests/
├── unit/
└── e2e/Modules compose the application
A module is a class annotated with @Module(). It declares which controllers and providers belong together and which other modules it imports. The root module wires the feature modules into one application graph.
from austial import Module
from .cats_controller import CatsController
from .cats_service import CatsService
@Module(
controllers=[CatsController],
providers=[CatsService],
)
class CatsModule:
passA request pipeline you do not hand-roll
Instead of sprinkling Depends() through every route, Austial runs each request through the same ordered pipeline: guards, then pipes, then interceptors wrapping the handler, with exception filters turning errors into one consistent JSON shape. Middleware sits in front of all of it.
- Guards decide whether a request may reach the handler.
- Pipes validate and transform input.
- Interceptors run logic before and after the handler.
- Exception filters convert thrown exceptions into clean error responses.
Still FastAPI underneath
Austial does not hide FastAPI. Routes are registered as real APIRouters, request bodies are Pydantic models, and the generated Swagger UI is available at /docs in every scaffolded project.
One bootstrap file
src/main.py is the only place the application is assembled. It creates the app from the root module, registers global pipes and filters, and returns something uvicorn can serve. Under the hood the result is a thin wrapper around a real FastAPI instance.
from austial import AustialFactory
from austial.common.exceptions import AllExceptionsFilter
from austial.common.pipes import ValidationPipe
from .app_module import AppModule
def _build():
app = AustialFactory.create(AppModule, title="my-app")
app.use_global_pipes(ValidationPipe())
app.use_global_filters(AllExceptionsFilter())
app.enable_cors()
return app
app = _build()One error shape for the whole API
In a plain FastAPI project, error responses drift: some routes raise HTTPException, validation failures come back as 422 with FastAPI's own body, and custom handlers add a third shape. Austial funnels all of them through exception filters, so every error looks like this.
{
"statusCode": 404,
"message": "Widget #999 not found",
"error": "Not Found",
"timestamp": "2026-07-12T10:15:00.000000+00:00",
"path": "/widgets/999"
}What a feature looks like on disk
A feature groups its controller, service, DTOs and entity behind its own module, and the root module imports it. austial generate resource cats creates exactly this layout and registers the module for you.
src/modules/cats/
├── cats_module.py
├── cats_controller.py
├── cats_service.py
├── entities/cats_entity.py
└── dto/
├── create_cats_dto.py
└── update_cats_dto.pyFrequently asked questions
How should I structure a large FastAPI project?
Group code by feature, not by type: one folder per feature with its own module, controller, service and DTOs. Austial makes that the default and generates the files with austial generate resource.
Can I add Austial to an existing FastAPI app?
Austial is a framework you build an application with, scaffolded using austial new. It is built on FastAPI rather than a plugin for an existing app, so the usual path is to start new services with it.
Does Austial replace FastAPI?
No. It is built on FastAPI and adds structure: modules, dependency injection and a request lifecycle. FastAPI still handles routing and OpenAPI.