Coming from Starlette / FastAPIο
Status: π΄ DA REVISIONARE
If you already know Starlette or FastAPI, genro-asgi will feel familiar in the small β you still decorate a callable to make a route, params still bind to the signature, and you still get OpenAPI and Swagger for free. It differs in the large: in what you build and how features appear. This page maps the two so you can carry your intuition across and know where it stops.
The mental model, side by sideο
FastAPI gives you an application object; you attach path operations to it with function decorators, and a dependency-injection container supplies each operation with what it declares. Middleware and security are attached to the app; you run it under uvicorn.
genro-asgi gives you a server that mounts applications. An application is a
class whose @route-decorated methods are its endpoints. The server itself is
a composition of capability mixins β auth, sessions, tasks, plugins β that
always exist and are fed by config data rather than switched on structurally.
There is no dependency-injection container: a handler reaches what it needs
through the object graph (its application, the server, the request), not through
declared dependencies. You start the server by calling .serve() from your own
entry point β there is no dedicated command-line launcher.
The other pivotal difference is that routing is protocol-neutral. In FastAPI a
path operation is an HTTP thing. In genro-asgi a @route describes an operation
independently of transport, which is exactly why the same method tree can be
served as REST, as an OpenAPI schema, and as MCP tools without being rewritten.
Concept mappingο
Task |
FastAPI / Starlette |
genro-asgi |
|---|---|---|
Create the app |
|
subclass |
Define a route |
|
|
Path / query params |
function args + |
method args bind to the query string, typed, with defaults |
Request body / validation |
pydantic model as a param |
the |
JSON response |
|
|
HTML response |
|
|
Dependency injection |
|
no DI container β reach through the object graph ( |
Middleware |
|
|
Auth / security |
|
|
Sessions |
|
|
Mount a sub-app |
|
secondary mounts (dict by URL prefix); demux on first path segment |
OpenAPI / Swagger |
automatic at |
|
Start the server |
|
|
WebSocket / streaming |
|
|
Tools for an AI agent |
(not built in) |
|
The same endpoint, side by sideο
A tiny search endpoint with a typed query parameter, returning JSON, documented in OpenAPI.
FastAPI β shown for comparison only, illustrative:
# For comparison β this is FastAPI, not genro-asgi.
from fastapi import FastAPI
app = FastAPI(title="Shop API", version="1.0.0")
@app.get("/search")
def search(q: str = "", max_price: float = 100.0) -> dict:
return {"query": q, "hits": []}
# uvicorn module:app --port 8000
genro-asgi β real, verified API:
from genro_asgi import AsgiServer, OpenApiApplication
from genro_routes import route
class Shop(OpenApiApplication):
openapi_info = {"title": "Shop API", "version": "1.0.0"}
@route()
def search(self, q: str = "", max_price: float = 100.0) -> dict:
return {"query": q, "hits": []}
server = AsgiServer(primary=Shop(), plugins={"openapi": True, "pydantic": True})
server.serve(host="127.0.0.1", port=8000)
Both answer GET /search?q=moka&max_price=30. The FastAPI version documents at
/docs and /openapi.json; the genro-asgi version at /_meta/docs and
/_meta/schema_json. The visible difference is small β a method on a class
instead of a free function, and a server that mounts the app instead of being
the app. The invisible difference is the one that matters: switch the base class
to McpOpenApiApplication and mark search with
@route(channel_channels="mcp,rest"), and the same method is now both a REST
endpoint and an MCP tool.
Honest notes on the differencesο
No dependency-injection container. There is no
Depends. Handlers reach collaborators through the object graph β the application holdsself.server, the request holdsself.application. If you lean heavily on FastAPIβs DI for wiring, that pattern does not port; you compose objects instead.No dedicated CLI. There is no
genro-asgi servecommand and no.run()method. You write a Python entry point that builds the server and calls.serve(), which boots a programmatic uvicorn loop and blocks.port=0asks the OS for a free port (useful in tests).Routing is protocol-neutral by design. A
@routeis not inherently an HTTP operation. That is the reason REST, OpenAPI and MCP share one route tree β and the reason a method only becomes an MCP tool when you opt it in withchannel_channels. The flip side: think of a route as βan operationβ, not βan HTTP verb on a pathβ.Features are config, not construction. You do not add a capability by restructuring code; auth, sessions, tasks and plugins already exist on
AsgiServerand are fed by kwargs (auth=...,middleware=...,tasks=...,plugins=...). A capability you did not configure is present but idle, not absent.The OpenAPI prefix is
_meta. Not/docsand/openapi.jsonβ the Swagger UI is at/_meta/docsand the schema at/_meta/schema_json.An internal
_serverapp is always mounted. Login, task management and the system OpenAPI live under/_server/...with no setup on your part β there is no FastAPI equivalent you need to wire up.
Where to go nextο
Getting started β the runnable hello-world.
Core concepts β the server/application model and the demux rule in full.
How-to guides β auth, sessions, OpenAPI, MCP, tasks, streaming, middleware.