Requirements
Section titled “Requirements”| Package | Required | Purpose |
|---|---|---|
oridecon | Yes | Core framework |
oridecon-contracts | Yes | Protocol definitions |
oridecon-web | Yes | HTTP server integration |
oridecon-cache | Optional | Persisted queries |
oridecon-auth | Optional | Authentication |
oridecon-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. oridecon-graphql provides all of this as a turnkey Strawberry-based GraphQL server integrated with the Oridecon 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 (oridecon-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 oridecon.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 oridecon.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 oridecon.app import Applicationfrom oridecon.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 whenORI_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