Juniper Integration
Use toni-juniper to add a GraphQL endpoint powered by the juniper crate, with a strongly-typed context.
toni-juniper integrates Toni with the Juniper GraphQL library. Unlike toni-async-graphql, Juniper uses a concrete, user-defined context type — every resolver receives your context struct directly, giving you full compile-time type safety.
Installation
[dependencies]
toni = "0.2"
toni-juniper = "0.1"
juniper = "0.16"
toni-axum = "0.1"
tokio = { version = "1", features = ["full"] }1. Define the context
Your context struct is what Juniper resolvers receive. It holds references to services or data they need:
use juniper::Context as JuniperContext;
pub struct AppContext {
pub users_service: UsersService,
pub db: DatabaseService,
}
impl JuniperContext for AppContext {}2. Define the schema
use juniper::{graphql_object, EmptyMutation, EmptySubscription, RootNode};
pub struct QueryRoot;
#[graphql_object(context = AppContext)]
impl QueryRoot {
fn users(context: &AppContext) -> Vec<String> {
context.users_service.find_all_names()
}
fn user(context: &AppContext, id: i32) -> Option<String> {
context.users_service.find_one_name(id as u32)
}
}
pub type AppSchema = RootNode<
'static,
QueryRoot,
EmptyMutation<AppContext>,
EmptySubscription<AppContext>,
>;3. Build the context
Implement ContextBuilder to populate the context for each request, using injected Toni services:
use toni_juniper::ContextBuilder;
use toni::injectable;
#[injectable(pub struct AppContextBuilder {
#[inject]
users_service: UsersService,
#[inject]
db: DatabaseService,
})]
impl AppContextBuilder {
pub fn new(users_service: UsersService, db: DatabaseService) -> Self {
Self { users_service, db }
}
}
impl ContextBuilder for AppContextBuilder {
type Context = AppContext;
fn build(&self) -> AppContext {
AppContext {
users_service: self.users_service.clone(),
db: self.db.clone(),
}
}
}4. Register the module
use toni_juniper::GraphQLModule;
use juniper::{EmptyMutation, EmptySubscription, RootNode};
let schema = RootNode::new(QueryRoot, EmptyMutation::new(), EmptySubscription::new());
let graphql_module = GraphQLModule::for_root(schema, AppContextBuilder)
.with_path("/api/graphql")
.with_playground(true); // disable in production
#[module(
imports: [graphql_module, UsersModule, DatabaseModule],
)]
pub struct AppModule;5. Run the server
#[tokio::main]
async fn main() {
let mut app = ToniFactory::create(AppModule, AxumAdapter::new()).await;
app.listen(3000, "127.0.0.1").await;
}Comparison with async-graphql
toni-async-graphql | toni-juniper | |
|---|---|---|
| Context | Data (type-erased map) | Concrete struct (your type) |
| Type safety | Runtime lookup | Compile-time |
| Flexibility | Easier to add more data later | Explicit, all-or-nothing |
If your team values explicit, compile-checked context and doesn't mind the boilerplate of a concrete context type, Juniper is the stronger choice. If you want to incrementally add services to the context without changing the context type, async-graphql's Data container is more flexible.