toni
GraphQL

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-graphqltoni-juniper
ContextData (type-erased map)Concrete struct (your type)
Type safetyRuntime lookupCompile-time
FlexibilityEasier to add more data laterExplicit, 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.

On this page