Schema
Juniper follows a code-first approach to define a GraphQL schema.
TIP: For a schema-first approach, consider using a
juniper-from-schema
crate for generating ajuniper
-based code from a schema file.
GraphQL schema consists of three object types: a query root, a mutation root, and a subscription root.
The query root operation type must be provided and must be an Object type.
The mutation root operation type is optional; if it is not provided, the service does not support mutations. If it is provided, it must be an Object type.
Similarly, the subscription root operation type is also optional; if it is not provided, the service does not support subscriptions. If it is provided, it must be an Object type.
The query, mutation, and subscription root types must all be different types if provided.
In Juniper, the RootNode
type represents a schema. When the schema is first created, Juniper will traverse the entire object graph and register all types it can find. This means that if we define a GraphQL object somewhere but never use or reference it, it won't be exposed in a GraphQL schema.
Both query and mutation objects are regular GraphQL objects, defined like any other object in Juniper. The mutation and subscription objects, however, are optional, since schemas can be read-only and do not require subscriptions.
TIP: If mutation/subscription functionality is not needed, consider using the predefined
EmptyMutation
/EmptySubscription
types for stubbing them in aRootNode
.
extern crate juniper; use juniper::{ graphql_object, EmptySubscription, FieldResult, GraphQLObject, RootNode, }; #[derive(GraphQLObject)] struct User { name: String, } struct Query; #[graphql_object] impl Query { fn user_with_username(username: String) -> FieldResult<Option<User>> { // Look up user in database... unimplemented!() } } struct Mutation; #[graphql_object] impl Mutation { fn sign_up_user(name: String, email: String) -> FieldResult<User> { // Validate inputs and save user in database... unimplemented!() } } type Schema = RootNode<'static, Query, Mutation, EmptySubscription>; fn main() {}
NOTE: It's considered a good practice to name query, mutation, and subscription root types as
Query
,Mutation
, andSubscription
respectively.
The usage of subscriptions is a little different from the mutation and query objects, so they are discussed in the separate chapter.
Export
Many tools in GraphQL ecosystem require a schema definition to operate on. With Juniper we can export our GraphQL schema defined in Rust code either represented in the GraphQL schema language or in JSON.
SDL (schema definition language)
To generate an SDL (schema definition language) representation of a GraphQL schema defined in Rust code, the as_sdl()
method should be used for the direct extraction (requires enabling the schema-language
Juniper feature):
extern crate juniper; use juniper::{ graphql_object, EmptyMutation, EmptySubscription, FieldResult, RootNode, }; struct Query; #[graphql_object] impl Query { fn hello(&self) -> FieldResult<&str> { Ok("hello world") } } fn main() { // Define our schema in Rust. let schema = RootNode::new( Query, EmptyMutation::<()>::new(), EmptySubscription::<()>::new(), ); // Convert the Rust schema into the GraphQL SDL schema. let result = schema.as_sdl(); let expected = "\ schema { query: Query } type Query { hello: String! } "; #[cfg(not(target_os = "windows"))] assert_eq!(result, expected); }
JSON
To export a GraphQL schema defined in Rust code as JSON (often referred to as schema.json
), the specially crafted introspection query should be issued. Juniper provides a convenience introspect()
function to introspect the entire schema, which result can be serialized into JSON:
extern crate juniper; extern crate serde_json; use juniper::{ graphql_object, EmptyMutation, EmptySubscription, GraphQLObject, IntrospectionFormat, RootNode, }; #[derive(GraphQLObject)] struct Example { id: String, } struct Query; #[graphql_object] impl Query { fn example(id: String) -> Example { unimplemented!() } } type Schema = RootNode<'static, Query, EmptyMutation, EmptySubscription>; fn main() { // Run the built-in introspection query. let (res, _errors) = juniper::introspect( &Schema::new(Query, EmptyMutation::new(), EmptySubscription::new()), &(), IntrospectionFormat::default(), ).unwrap(); // Serialize the introspection result into JSON. let json_result = serde_json::to_string_pretty(&res); assert!(json_result.is_ok()); }
TIP: We still can convert the generated JSON into a GraphQL schema language representation by using tools like
graphql-json-to-sdl
command line utility.