Guide
Requirements
Section titled “Requirements”| Package | Required | Purpose |
|---|---|---|
lexigram | Yes | Core framework |
lexigram-contracts | Yes | Protocol definitions |
lexigram-web | Yes | HTTP server integration |
lexigram-cache | Optional | Persisted queries |
lexigram-auth | Optional | Authentication |
lexigram-resilience | Optional | Rate limiting |
Problem
Section titled “Problem”Building a production GraphQL server involves more than defining types and resolvers. You need query depth limiting, complexity analysis, DataLoader batching, subscription handling, rate limiting, metrics, and persisted queries. lexigram-graphql provides all of this as a turnkey Strawberry-based GraphQL server integrated with the Lexigram DI container.
Mental model
Section titled “Mental model”Schema types (Strawberry) │ ▼SchemaBuilder ──► SchemaValidator │ │ ├── query ├── depth_limit ├── mutation ├── complexity └── subscription └── alias_limit │ ▼GraphQLExecutor ──► Extensions │ ├── Tracing └──► Result ├── Metrics └── RateLimit │ ▼GraphQLController ──► HTTP (lexigram-web) orStandalone executorCore concepts
Section titled “Core concepts”Schema definition
Section titled “Schema definition”Use Strawberry types to define your schema:
import strawberry
@strawberry.typeclass Query: @strawberry.field async def users(self) -> list[User]: return await user_service.list_all()
@strawberry.typeclass Mutation: @strawberry.mutation async def create_user(self, name: str, email: str) -> User: return await user_service.create(name=name, email=email)
@strawberry.typeclass Subscription: @strawberry.subscription async def user_created(self) -> AsyncIterator[User]: async for user in user_service.events(): yield userAuto-discovery
Section titled “Auto-discovery”Scan packages for Strawberry types automatically:
provider = GraphQLProvider.auto_discover("my_app.graphql")Context factory
Section titled “Context factory”Access the DI container and DataLoader instances in resolvers:
from lexigram.graphql import GraphQLContext
@strawberry.typeclass Query: @strawberry.field async def me(self, info: strawberry.types.Info) -> User: ctx: GraphQLContext = info.context user_service = await ctx.resolve(UserService) return await user_service.get_current()DataLoader
Section titled “DataLoader”Solve N+1 queries with batching:
from lexigram.graphql import create_loader
loader = create_loader( load_fn=lambda keys: user_repo.get_by_ids(keys),)users = await loader.load_many(["user-1", "user-2", "user-3"])Security
Section titled “Security”| Feature | Config | Default |
|---|---|---|
| Depth limit | depth_limit.max_depth | 10 |
| Complexity limit | complexity.max_complexity | 1000 |
| Alias limit | (always on) | 15 |
| Rate limiting | rate_limit.requests_per_minute | 60 |
| Introspection | introspection.allowed_environments | dev + test |
| Playground | playground.enabled | auto-disabled in production |
Typical usage
Section titled “Typical usage”from lexigram.app import Applicationfrom lexigram.graphql import GraphQLModule, GraphQLConfig
config = GraphQLConfig( debug=True, depth_limit=DepthLimitConfig(max_depth=15),)
app.add_module(GraphQLModule.configure( config=config, query_class=Query, mutation_class=Mutation,))Best practices
Section titled “Best practices”- ✅ Use DataLoader for all list-fetching resolvers — prevents N+1 queries
- ✅ Enable depth and complexity limits in production — protects against abusive queries
- ✅ Use persisted queries for high-traffic endpoints — reduces bandwidth and parsing overhead
- ✅ Set
playground.enabled: falsein production — enabled by default; auto-disables whenLEX_ENV=production - ❌ Don’t share resolvers between queries and mutations — mutations have different error semantics
- ❌ Don’t expose internal services directly in resolvers — create dedicated resolver methods