Skip to Content
Introduction

Introduction

Austial is a batteries-included web framework for Python, built on top of FastAPI  and inspired by NestJS . It gives Python decorator-driven modules/controllers/providers, real constructor-based dependency injection, the full request lifecycle (guards, pipes, interceptors, exception filters, middleware), a config layer, a database layer, Terminus-style health checks, a testing module — and a CLI so you can scaffold new projects and generate artifacts.

FastAPI is a fantastic ASGI toolkit, but it doesn’t prescribe an application architecture. Austial is opinionated on top of FastAPI: modules own controllers and providers, providers get dependency-injected by type, and the request pipeline

guards -> pipes -> interceptors (wrapping) -> handler -> exception filters

is a first-class concept instead of something you hand-roll with Depends() everywhere.

Why Austial

Routing, guards, and interceptors are all declared with decorators that read top to bottom, in the order they execute:

from austial import Controller, Get, UseGuards, UseInterceptors from austial.common.interceptors import TransformInterceptor from .api_key_guard import ApiKeyGuard @Controller("cats") class CatsController: @Get(":id") @UseGuards(ApiKeyGuard) @UseInterceptors(TransformInterceptor()) async def find_one(self, id: int): ...

Path params use a familiar :id syntax (translated to FastAPI’s {id} under the hood), so route strings stay short, and the decorator stack — guard, interceptor, handler — reads top to bottom in the order it executes.

Architecture at a glance

  • Modules@Module(imports=, controllers=, providers=, exports=) compose the application graph.
  • Controllers@Controller(prefix) plus @Get/@Post/@Put/@Patch/@Delete/@Options/@Head declare routes.
  • Providers@Injectable(scope=Scope.DEFAULT | Scope.TRANSIENT); the container resolves __init__ type hints recursively, singleton by default. Custom providers use provider dicts: {"provide": TOKEN, "useClass"/"useValue"/"useFactory": ..., "inject": [...]}.
  • BootstrapAustialFactory.create(AppModule) returns an AustialApplication; await app.listen(8000) starts it.
  • GuardsCanActivate ABC + ExecutionContext, applied with @UseGuards(...).
  • InterceptorsNestInterceptor ABC (intercept(context, call_next)), applied with @UseInterceptors(...).
  • PipesPipeTransform ABC, including the built-in ValidationPipe.
  • Exception filtersExceptionFilter ABC + @Catch(...), with a default AllExceptionsFilter.
  • MiddlewareNestMiddleware ABC, wired up via configure(consumer) on modules.
  • ConfigConfigModule.for_root(env_file=".env") / ConfigService.
  • DatabaseDatabaseModule.for_root_async(use_factory=, inject=) over async SQLAlchemy.
  • Health checksaustial.terminus.HealthCheckService + MemoryHealthIndicator, DatabaseHealthIndicator.
  • Testingaustial.testing.Test.create_testing_module(...).compile().
  • CLIaustial new <name> / austial generate|g module|controller|service|resource <name>.

A note on file names

Some JavaScript frameworks name files with dots (health.controller.ts) because Node resolves modules by explicit relative path strings. Python’s import a.b instead resolves a as a package and b as a submodule — a literal file health.controller.py isn’t importable via a normal import statement. So Austial uses underscore suffixes instead: health_controller.py, health_service.py, health_module.py, health_dto.py. The directory layout is one folder per feature under src/modules/.

What’s not (yet) implemented

Austial is deliberately opinionated but not a 1:1 port of any single framework. A few concepts are intentionally out of scope for now, so you don’t go looking for them:

  • Request-scoped providers (Scope.REQUEST) — only Scope.DEFAULT (singleton) and Scope.TRANSIENT exist today.
  • Module imports/exports visibility enforcement — every module’s providers currently register into one flat, application-wide container (visibility is documentation-only, not enforced at resolve time).
  • RxJS-style interceptor streamsCallHandler.handle() is a plain async continuation, not an Observable.

None of this blocks building a real API with the full request lifecycle — it’s just worth knowing up front.

Where to go next

Last updated on