Your First App
Prerequisites
Section titled “Prerequisites”uv add lexigram-webA Minimal API
Section titled “A Minimal API”Three files — a factory, a controller, and an entry point.
1. Application Factory
Section titled “1. Application Factory”The create_app() function is your composition root — it wires providers and returns a ready-to-boot application:
from lexigram import Application, LexigramConfigfrom lexigram.web import WebProvider
def create_app() -> Application: app = Application(name="my-app")
# Web layer — discovers Controller subclasses in the given package app.add_provider(WebProvider.auto_discover("my_app.controllers"))
return app2. Controller
Section titled “2. Controller”Controllers group related routes under a common prefix. Dependencies are injected via the constructor:
from lexigram.web import Controller, get
class HelloController(Controller): prefix = "/api"
@get("/hello") async def hello(self) -> dict: return {"message": "Hello, Lexigram!"}
@get("/hello/{name}") async def hello_name(self, name: str) -> dict: return {"message": f"Hello, {name}!"}3. Run It
Section titled “3. Run It”# Using uvicorn (or granian)uvicorn my_app.app:create_app --factory
{"message": "Hello, Lexigram!"}Adding a Service with DI
Section titled “Adding a Service with DI”Use @singleton to mark a class for the DI container. Type-hint it in your controller — Lexigram injects it automatically:
from lexigram import singleton
@singletonclass GreetingService: def greet(self, name: str) -> str: return f"Hello, {name}! Welcome to Lexigram."from lexigram.web import Controller, getfrom my_app.services import GreetingService
class HelloController(Controller): prefix = "/api"
def __init__(self, greeting: GreetingService) -> None: self.greeting = greeting # injected by the container
@get("/hello/{name}") async def hello(self, name: str) -> dict: return {"message": self.greeting.greet(name)}How DI Works Here
Section titled “How DI Works Here”@singletonmarksGreetingServicewith__lexigram_injectable__metadataWebProvider.auto_discover()scans the package and finds the marked class- At boot, the container registers
GreetingServiceas a singleton - When
HelloControlleris instantiated, the container resolvesGreetingServicefrom the constructor type hints
Registering Services Explicitly
Section titled “Registering Services Explicitly”For more control, create a Provider instead of using decorators:
from lexigram.di.provider import Providerfrom lexigram.contracts.core import ProviderPriorityfrom lexigram.contracts.core.di import ContainerRegistrarProtocol
class AppProvider(Provider): name = "app" priority = ProviderPriority.DOMAIN
async def register(self, container: ContainerRegistrarProtocol) -> None: from my_app.services import GreetingService
container.singleton(GreetingService, GreetingService)from lexigram import Applicationfrom lexigram.web import WebProviderfrom my_app.providers.app_provider import AppProvider
def create_app() -> Application: app = Application(name="my-app")
app.add_provider(AppProvider()) app.add_provider(WebProvider.auto_discover("my_app.controllers"))
return appUsing the Result Type
Section titled “Using the Result Type”Return Result[T, E] from your service layer to make errors explicit:
from lexigram import singletonfrom lexigram.result import Result, Ok, Err
@singletonclass UserService: async def find(self, user_id: str) -> Result[dict, str]: if user_id == "0": return Err("User not found") return Ok({"id": user_id, "name": "Ada Lovelace"})from lexigram.web import Controller, getfrom lexigram.result import Resultfrom my_app.services import UserService
class UserController(Controller): prefix = "/api/users"
def __init__(self, users: UserService) -> None: self.users = users
@get("/{user_id}") async def get_user(self, user_id: str) -> Result[dict, str]: return await self.users.find(user_id)The ResultResponseMapper auto-converts to HTTP responses:
| Result | HTTP Response |
|---|---|
Ok(value) | 200 with JSON body |
Err("string") | 400 with error message |
Err(NotFoundError(...)) | 404 |
Err(ValidationError(...)) | 422 |
Using Application.boot() Context Manager
Section titled “Using Application.boot() Context Manager”For scripts or tests, use the context manager form:
import asynciofrom lexigram import Applicationfrom lexigram.web import WebProviderfrom my_app.providers.app_provider import AppProvider
async def main(): async with Application.boot( name="my-app", providers=[AppProvider(), WebProvider()], ) as app: # Application is started and running print(f"App state: {app.state}") # AppState.RUNNING # Shutdown happens automatically on exit
asyncio.run(main())Project Layout So Far
Section titled “Project Layout So Far”my-app/├── pyproject.toml└── src/ └── my_app/ ├── __init__.py ├── app.py # create_app() ├── services.py # @singleton GreetingService ├── providers/ │ └── app_provider.py # explicit Provider └── controllers/ ├── __init__.py ├── hello_controller.py └── user_controller.pyNext Steps
Section titled “Next Steps”- Project Structure — Pattern 2 and Pattern 3 layouts
- Core Concepts — Providers, DI, Result type, and modules
- Configuration — YAML config, env vars, and profiles