Bustan (بستان — garden) brings NestJS's architecture to Python: modules that declare what they need and what they expose, providers resolved by a container, and controllers that stay thin.
@Injectable()
class TaskService: ...
@Controller("/tasks")
class TaskController:
def __init__(self, tasks: TaskService) -> None:
self.tasks = tasks
@Module(controllers=[TaskController], providers=[TaskService])
class TaskModule:
pass
TaskService is injected because the constructor asks for it by type. Nothing
else to register.
Why#
Python has excellent routers. What it lacks is a convention for structure that survives a codebase growing past one file — where a newcomer can tell, from the module declarations alone, what depends on what.
Per-handler dependency injection, the usual Python answer, is genuinely good for small services. It just leaves the dependency graph implicit, scattered across every route signature.
Visibility is the design#
A provider is private to its module unless exported, and reachable only by modules that import the owner. Two lists, and the whole graph falls out of them:
$ bustan graph
AppModule
├── TaskModule
│ ├── provides TaskService
│ └── exports TaskService
└── ReportModule
├── imports TaskModule
└── provides ReportService
Everything-global is more convenient for about three weeks; after that "what breaks if I change this" has no answer short of running everything.
Resolution is eager#
The graph is walked at startup, providers are constructed in dependency order, and cycles are errors before the first request. A framework that resolves lazily turns a structural mistake into an intermittent one.
The same strictness rejects a request-scoped provider injected into a singleton — which would otherwise capture the first request's instance forever and surface weeks later as one user seeing another's data.
Trade-offs#
Ceremony. A three-endpoint service is more code here than in a bare router, and for that shape of problem the bare router is the better tool. The trade pays off when a second person joins or a module has to move — and if the codebase never gets there, you simply paid.
Docs are in the Bustan app.