2019-03-23 08:08:09 -05:00
<!DOCTYPE HTML>
< html lang = "en" class = "sidebar-visible no-js" >
< head >
<!-- Book generated using mdBook -->
< meta charset = "UTF-8" >
< title > Juniper - GraphQL Server for Rust< / title >
< meta content = "text/html; charset=utf-8" http-equiv = "Content-Type" >
< meta name = "description" content = "Documentation for juniper, a GraphQL server library for Rust." >
< meta name = "viewport" content = "width=device-width, initial-scale=1" >
< meta name = "theme-color" content = "#ffffff" / >
< link rel = "shortcut icon" href = "favicon.png" >
< link rel = "stylesheet" href = "css/variables.css" >
< link rel = "stylesheet" href = "css/general.css" >
< link rel = "stylesheet" href = "css/chrome.css" >
< link rel = "stylesheet" href = "css/print.css" media = "print" >
<!-- Fonts -->
< link rel = "stylesheet" href = "FontAwesome/css/font-awesome.css" >
< link href = "https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel = "stylesheet" type = "text/css" >
< link href = "https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel = "stylesheet" type = "text/css" >
<!-- Highlight.js Stylesheets -->
< link rel = "stylesheet" href = "highlight.css" >
< link rel = "stylesheet" href = "tomorrow-night.css" >
< link rel = "stylesheet" href = "ayu-highlight.css" >
<!-- Custom theme stylesheets -->
< / head >
< body class = "light" >
<!-- Provide site root to javascript -->
< script type = "text/javascript" > var path _to _root = "" ; < / script >
<!-- Work around some values being stored in localStorage wrapped in quotes -->
< script type = "text/javascript" >
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') & & theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') & & sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
< / script >
<!-- Set the theme before any content is loaded, prevents flash -->
< script type = "text/javascript" >
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = 'light'; }
document.body.className = theme;
document.querySelector('html').className = theme + ' js';
< / script >
<!-- Hide / unhide sidebar before it is displayed -->
< script type = "text/javascript" >
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
< / script >
< nav id = "sidebar" class = "sidebar" aria-label = "Table of contents" >
2019-04-09 19:26:56 -05:00
< ol class = "chapter" > < li class = "affix" > < a href = "index.html" > Introduction< / a > < / li > < li class = "affix" > < a href = "quickstart.html" > Quickstart< / a > < / li > < li class = "affix" > < a href = "types/index.html" > Type System< / a > < / li > < li > < a href = "types/objects/defining_objects.html" > < strong aria-hidden = "true" > 1.< / strong > Defining objects< / a > < / li > < li > < ol class = "section" > < li > < a href = "types/objects/complex_fields.html" > < strong aria-hidden = "true" > 1.1.< / strong > Complex fields< / a > < / li > < li > < a href = "types/objects/using_contexts.html" > < strong aria-hidden = "true" > 1.2.< / strong > Using contexts< / a > < / li > < li > < a href = "types/objects/error_handling.html" > < strong aria-hidden = "true" > 1.3.< / strong > Error handling< / a > < / li > < / ol > < / li > < li > < a href = "types/other-index.html" > < strong aria-hidden = "true" > 2.< / strong > Other types< / a > < / li > < li > < ol class = "section" > < li > < a href = "types/enums.html" > < strong aria-hidden = "true" > 2.1.< / strong > Enums< / a > < / li > < li > < a href = "types/interfaces.html" > < strong aria-hidden = "true" > 2.2.< / strong > Interfaces< / a > < / li > < li > < a href = "types/input_objects.html" > < strong aria-hidden = "true" > 2.3.< / strong > Input objects< / a > < / li > < li > < a href = "types/scalars.html" > < strong aria-hidden = "true" > 2.4.< / strong > Scalars< / a > < / li > < li > < a href = "types/unions.html" > < strong aria-hidden = "true" > 2.5.< / strong > Unions< / a > < / li > < / ol > < / li > < li > < a href = "schema/schemas_and_mutations.html" > < strong aria-hidden = "true" > 3.< / strong > Schemas and mutations< / a > < / li > < li > < a href = "servers/index.html" > < strong aria-hidden = "true" > 4.< / strong > Adding A Server< / a > < / li > < li > < ol class = "section" > < li > < a href = "servers/official.html" > < strong aria-hidden = "true" > 4.1.< / strong > Official Server Integrations< / a > < / li > < li > < ol class = "section" > < li > < a href = "servers/warp.html" > < strong aria-hidden = "true" > 4.1.1.< / strong > Warp< / a > < / li > < li > < a href = "servers/rocket.html" > < strong aria-hidden = "true" > 4.1.2.< / strong > Rocket< / a > < / li > < li > < a href = "servers/iron.html" > < strong aria-hidden = "true" > 4.1.3.< / strong > Iron< / a > < / li > < li > < a href = "servers/hyper.html" > < strong aria-hidden = "true" > 4.1.4.< / strong > Hyper< / a > < / li > < / ol > < / li > < li > < a href = "servers/third-party.html" > < strong aria-hidden = "true" > 4.2.< / strong > Third Party Integrations< / a > < / li > < / ol > < / li > < li > < a href = "advanced/index.html" > < strong aria-hidden = "true" > 5.< / strong > Advanced Topics< / a > < / li > < li > < ol class = "section" > < li > < a href = "advanced/introspection.html" > < strong aria-hidden = "true" > 5.1.< / strong > Introspection< / a > < / li > < li > < a href = "advanced/non_struct_objects.html" > < strong aria-hidden = "true" > 5.2.< / strong > Non-struct objects< / a > < / li > < li > < a href = "advanced/objects_and_generics.html" > < strong aria-hidden = "true" > 5.3.< / strong > Objects and generics< / a > < / li > < li > < a href = "advanced/multiple_ops_per_request.html" > < strong aria-hidden = "true" > 5.4.< / strong > Multiple operations per request< / a > < / li > < / ol > < / li > < / ol >
2019-03-23 08:08:09 -05:00
< / nav >
< div id = "page-wrapper" class = "page-wrapper" >
< div class = "page" >
< div id = "menu-bar" class = "menu-bar" >
< div id = "menu-bar-sticky-container" >
< div class = "left-buttons" >
< button id = "sidebar-toggle" class = "icon-button" type = "button" title = "Toggle Table of Contents" aria-label = "Toggle Table of Contents" aria-controls = "sidebar" >
< i class = "fa fa-bars" > < / i >
< / button >
< button id = "theme-toggle" class = "icon-button" type = "button" title = "Change theme" aria-label = "Change theme" aria-haspopup = "true" aria-expanded = "false" aria-controls = "theme-list" >
< i class = "fa fa-paint-brush" > < / i >
< / button >
< ul id = "theme-list" class = "theme-popup" aria-label = "Themes" role = "menu" >
< li role = "none" > < button role = "menuitem" class = "theme" id = "light" > Light < span class = "default" > (default)< / span > < / button > < / li >
< li role = "none" > < button role = "menuitem" class = "theme" id = "rust" > Rust< / button > < / li >
< li role = "none" > < button role = "menuitem" class = "theme" id = "coal" > Coal< / button > < / li >
< li role = "none" > < button role = "menuitem" class = "theme" id = "navy" > Navy< / button > < / li >
< li role = "none" > < button role = "menuitem" class = "theme" id = "ayu" > Ayu< / button > < / li >
< / ul >
< button id = "search-toggle" class = "icon-button" type = "button" title = "Search. (Shortkey: s)" aria-label = "Toggle Searchbar" aria-expanded = "false" aria-keyshortcuts = "S" aria-controls = "searchbar" >
< i class = "fa fa-search" > < / i >
< / button >
< / div >
< h1 class = "menu-title" > Juniper - GraphQL Server for Rust< / h1 >
< div class = "right-buttons" >
< a href = "print.html" title = "Print this book" aria-label = "Print this book" >
< i id = "print-button" class = "fa fa-print" > < / i >
< / a >
< / div >
< / div >
< / div >
< div id = "search-wrapper" class = "hidden" >
< form id = "searchbar-outer" class = "searchbar-outer" >
< input type = "search" name = "search" id = "searchbar" name = "searchbar" placeholder = "Search this book ..." aria-controls = "searchresults-outer" aria-describedby = "searchresults-header" >
< / form >
< div id = "searchresults-outer" class = "searchresults-outer hidden" >
< div id = "searchresults-header" class = "searchresults-header" > < / div >
< ul id = "searchresults" >
< / ul >
< / div >
< / div >
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
< script type = "text/javascript" >
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
< / script >
< div id = "content" class = "content" >
< main >
< a class = "header" href = "#juniper" id = "juniper" > < h1 > Juniper< / h1 > < / a >
< p > Juniper is a < a href = "http://graphql.org" > GraphQL< / a > server library for Rust. Build type-safe and fast API
servers with minimal boilerplate and configuration.< / p >
< p > < a href = "http://graphql.org" > GraphQL< / a > is a data query language developed by Facebook intended to
serve mobile and web application frontends.< / p >
< p > < em > Juniper< / em > makes it possible to write GraphQL servers in Rust that are
type-safe and blazingly fast. We also try to make declaring and resolving
GraphQL schemas as convenient as possible as Rust will allow.< / p >
< p > Juniper does not include a web server - instead it provides building blocks to
make integration with existing servers straightforward. It optionally provides a
pre-built integration for the < a href = "https://hyper.rs" > Hyper< / a > , < a href = "http://ironframework.io" > Iron< / a > , < a href = "https://rocket.rs" > Rocket< / a > , and < a href = "https://github.com/seanmonstar/warp" > Warp< / a > frameworks, including
embedded < a href = "https://github.com/graphql/graphiql" > Graphiql< / a > for easy debugging.< / p >
< ul >
< li > < a href = "https://crates.io/crates/juniper" > Cargo crate< / a > < / li >
< li > < a href = "https://docs.rs/juniper" > API Reference< / a > < / li >
< / ul >
< a class = "header" href = "#features" id = "features" > < h2 > Features< / h2 > < / a >
< p > Juniper supports the full GraphQL query language according to the
< a href = "http://facebook.github.io/graphql" > specification< / a > , including interfaces, unions, schema
introspection, and validations.
It does not, however, support the schema language.< / p >
< p > As an exception to other GraphQL libraries for other languages, Juniper builds
non-null types by default. A field of type < code > Vec< Episode> < / code > will be converted into
< code > [Episode!]!< / code > . The corresponding Rust type for e.g. < code > [Episode]< / code > would be
< code > Option< Vec< Option< Episode> > > < / code > .< / p >
< a class = "header" href = "#integrations" id = "integrations" > < h2 > Integrations< / h2 > < / a >
< a class = "header" href = "#data-types" id = "data-types" > < h3 > Data types< / h3 > < / a >
< p > Juniper has automatic integration with some very common Rust crates to make
building schemas a breeze. The types from these crates will be usable in
your Schemas automatically.< / p >
< ul >
< li > < a href = "https://crates.io/crates/uuid" > uuid< / a > < / li >
< li > < a href = "https://crates.io/crates/url" > url< / a > < / li >
< li > < a href = "https://crates.io/crates/chrono" > chrono< / a > < / li >
< / ul >
< a class = "header" href = "#web-frameworks" id = "web-frameworks" > < h3 > Web Frameworks< / h3 > < / a >
< ul >
< li > < a href = "https://hyper.rs" > hyper< / a > < / li >
< li > < a href = "https://rocket.rs" > rocket< / a > < / li >
< li > < a href = "http://ironframework.io" > iron< / a > < / li >
< li > < a href = "https://github.com/seanmonstar/warp" > warp< / a > < / li >
< / ul >
< a class = "header" href = "#api-stability" id = "api-stability" > < h2 > API Stability< / h2 > < / a >
< p > Juniper has not reached 1.0 yet, thus some API instability should be expected.< / p >
< a class = "header" href = "#quickstart" id = "quickstart" > < h1 > Quickstart< / h1 > < / a >
< p > This page will give you a short introduction to the concepts in Juniper.< / p >
< a class = "header" href = "#installation" id = "installation" > < h2 > Installation< / h2 > < / a >
< p > !FILENAME Cargo.toml< / p >
< pre > < code class = "language-toml" > [dependencies]
juniper = " 0.11"
< / code > < / pre >
< a class = "header" href = "#schema-example" id = "schema-example" > < h2 > Schema example< / h2 > < / a >
< p > Exposing simple enums and structs as GraphQL is just a matter of adding a custom
derive attribute to them. Juniper includes support for basic Rust types that
naturally map to GraphQL features, such as < code > Option< T> < / code > , < code > Vec< T> < / code > , < code > Box< T> < / code > ,
< code > String< / code > , < code > f64< / code > , and < code > i32< / code > , references, and slices.< / p >
< p > For more advanced mappings, Juniper provides multiple macros to map your Rust
types to a GraphQL schema. The most important one is the
< a href = "https://docs.rs/juniper/latest/juniper/macro.graphql_object.html" > graphql_object!< / a > macro that is used for declaring an object with
resolvers, which you will use for the < code > Query< / code > and < code > Mutation< / code > roots.< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > use juniper::{FieldResult};
# struct DatabasePool;
# impl DatabasePool {
# fn get_connection(& self) -> FieldResult< DatabasePool> { Ok(DatabasePool) }
2019-04-09 19:26:56 -05:00
# fn find_human(& self, _id: & str) -> FieldResult< Human> { Err(" " )? }
# fn insert_human(& self, _human: & NewHuman) -> FieldResult< Human> { Err(" " )? }
2019-03-23 08:08:09 -05:00
# }
#[derive(juniper::GraphQLEnum)]
enum Episode {
NewHope,
Empire,
Jedi,
}
#[derive(juniper::GraphQLObject)]
#[graphql(description=" A humanoid creature in the Star Wars universe" )]
struct Human {
id: String,
name: String,
appears_in: Vec< Episode> ,
home_planet: String,
}
// There is also a custom derive for mapping GraphQL input objects.
#[derive(juniper::GraphQLInputObject)]
#[graphql(description=" A humanoid creature in the Star Wars universe" )]
struct NewHuman {
name: String,
appears_in: Vec< Episode> ,
home_planet: String,
}
// Now, we create our root Query and Mutation types with resolvers by using the
// graphql_object! macro.
// Objects can have contexts that allow accessing shared state like a database
// pool.
struct Context {
// Use your real database pool here.
pool: DatabasePool,
}
// To make our context usable by Juniper, we have to implement a marker trait.
impl juniper::Context for Context {}
struct Query;
juniper::graphql_object!(Query: Context |& self| {
field apiVersion() -> & str {
" 1.0"
}
// Arguments to resolvers can either be simple types or input objects.
// The executor is a special (optional) argument that allows accessing the context.
field human(& executor, id: String) -> FieldResult< Human> {
// Get the context from the executor.
let context = executor.context();
// Get a db connection.
let connection = context.pool.get_connection()?;
// Execute a db query.
// Note the use of `?` to propagate errors.
let human = connection.find_human(& id)?;
// Return the result.
Ok(human)
}
});
struct Mutation;
juniper::graphql_object!(Mutation: Context |& self| {
field createHuman(& executor, new_human: NewHuman) -> FieldResult< Human> {
let db = executor.context().pool.get_connection()?;
let human: Human = db.insert_human(& new_human)?;
Ok(human)
}
});
// A root schema consists of a query and a mutation.
// Request queries can be executed against a RootNode.
type Schema = juniper::RootNode< 'static, Query, Mutation> ;
2019-04-09 19:26:56 -05:00
# fn main() {
# let _ = Schema::new(Query, Mutation{});
# }
2019-03-23 08:08:09 -05:00
< / code > < / pre > < / pre >
< p > We now have a very simple but functional schema for a GraphQL server!< / p >
< p > To actually serve the schema, see the guides for our various < a href = "./servers/index.html" > server integrations< / a > .< / p >
< p > You can also invoke the executor directly to get a result for a query:< / p >
< a class = "header" href = "#executor" id = "executor" > < h2 > Executor< / h2 > < / a >
< p > You can invoke < code > juniper::execute< / code > directly to run a GraphQL query:< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > # // Only needed due to 2018 edition because the macro is not accessible.
2019-05-02 12:22:08 -05:00
# #[macro_use] extern crate juniper;
2019-03-23 08:08:09 -05:00
use juniper::{FieldResult, Variables, EmptyMutation};
#[derive(juniper::GraphQLEnum, Clone, Copy)]
enum Episode {
NewHope,
Empire,
Jedi,
}
struct Query;
juniper::graphql_object!(Query: Ctx |& self| {
field favoriteEpisode(& executor) -> FieldResult< Episode> {
// Use the special & executor argument to fetch our fav episode.
Ok(executor.context().0)
}
});
// Arbitrary context data.
struct Ctx(Episode);
// A root schema consists of a query and a mutation.
// Request queries can be executed against a RootNode.
type Schema = juniper::RootNode< 'static, Query, EmptyMutation< Ctx> > ;
fn main() {
// Create a context object.
let ctx = Ctx(Episode::NewHope);
// Run the executor.
let (res, _errors) = juniper::execute(
" query { favoriteEpisode }" ,
None,
& Schema::new(Query, EmptyMutation::new()),
& Variables::new(),
& ctx,
).unwrap();
// Ensure the value matches.
assert_eq!(
res,
graphql_value!({
2019-04-09 19:26:56 -05:00
" favoriteEpisode" : " NEW_HOPE" ,
2019-03-23 08:08:09 -05:00
})
);
}
< / code > < / pre > < / pre >
< a class = "header" href = "#type-system" id = "type-system" > < h1 > Type System< / h1 > < / a >
< p > Most of the work in working with juniper consists of mapping the
GraphQL type system to the Rust types your application uses.< / p >
< p > Juniper provides some convenient abstractions that try to make this process
as painless as possible.< / p >
< p > Find out more in the individual chapters below.< / p >
< ul >
2019-04-09 19:26:56 -05:00
< li > < a href = "objects/defining_objects.html" > Defining objects< / a >
2019-03-23 08:08:09 -05:00
< ul >
2019-04-09 19:26:56 -05:00
< li > < a href = "objects/complex_fields.html" > Complex fields< / a > < / li >
< li > < a href = "objects/using_contexts.html" > Using contexts< / a > < / li >
< li > < a href = "objects/error_handling.html" > Error handling< / a > < / li >
2019-03-23 08:08:09 -05:00
< / ul >
< / li >
2019-04-09 19:26:56 -05:00
< li > < a href = "other-index.html" > Other types< / a >
2019-03-23 08:08:09 -05:00
< ul >
2019-04-09 19:26:56 -05:00
< li > < a href = "enums.html" > Enums< / a > < / li >
< li > < a href = "interfaces.html" > Interfaces< / a > < / li >
< li > < a href = "input_objects.html" > Input objects< / a > < / li >
< li > < a href = "scalars.html" > Scalars< / a > < / li >
< li > < a href = "unions.html" > Unions< / a > < / li >
2019-03-23 08:08:09 -05:00
< / ul >
< / li >
< / ul >
< a class = "header" href = "#defining-objects" id = "defining-objects" > < h1 > Defining objects< / h1 > < / a >
< p > While any type in Rust can be exposed as a GraphQL object, the most common one
is a struct.< / p >
< p > There are two ways to create a GraphQL object in Juniper. If you've got a simple
struct you want to expose, the easiest way is to use the custom derive
attribute. The other way is described in the < a href = "complex_fields.html" > Complex fields< / a >
chapter.< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > #[derive(juniper::GraphQLObject)]
struct Person {
name: String,
age: i32,
}
# fn main() {}
< / code > < / pre > < / pre >
< p > This will create a GraphQL object type called < code > Person< / code > , with two fields: < code > name< / code >
of type < code > String!< / code > , and < code > age< / code > of type < code > Int!< / code > . Because of Rust's type system,
everything is exported as non-null by default. If you need a nullable field, you
can use < code > Option< T> < / code > .< / p >
< p > We should take advantage of the
fact that GraphQL is self-documenting and add descriptions to the type and
fields. Juniper will automatically use associated doc comments as GraphQL
descriptions:< / p >
< p > !FILENAME GraphQL descriptions via Rust doc comments< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > #[derive(juniper::GraphQLObject)]
/// Information about a person
struct Person {
/// The person's full name, including both first and last names
name: String,
/// The person's age in years, rounded down
age: i32,
}
# fn main() {}
< / code > < / pre > < / pre >
< p > Objects and fields without doc comments can instead set a < code > description< / code >
via the < code > graphql< / code > attribute. The following example is equivalent to the above:< / p >
< p > !FILENAME GraphQL descriptions via attribute< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > #[derive(juniper::GraphQLObject)]
#[graphql(description=" Information about a person" )]
struct Person {
#[graphql(description=" The person's full name, including both first and last names" )]
name: String,
#[graphql(description=" The person's age in years, rounded down" )]
age: i32,
}
# fn main() {}
< / code > < / pre > < / pre >
< p > Descriptions set via the < code > graphql< / code > attribute take precedence over Rust
doc comments. This enables internal Rust documentation and external GraphQL
documentation to differ:< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > #[derive(juniper::GraphQLObject)]
#[graphql(description=" This description shows up in GraphQL" )]
/// This description shows up in RustDoc
struct Person {
#[graphql(description=" This description shows up in GraphQL" )]
/// This description shows up in RustDoc
name: String,
/// This description shows up in both RustDoc and GraphQL
age: i32,
}
# fn main() {}
< / code > < / pre > < / pre >
< a class = "header" href = "#relationships" id = "relationships" > < h2 > Relationships< / h2 > < / a >
< p > You can only use the custom derive attribute under these circumstances:< / p >
< ul >
< li > The annotated type is a < code > struct< / code > ,< / li >
< li > Every struct field is either
< ul >
< li > A primitive type (< code > i32< / code > , < code > f64< / code > , < code > bool< / code > , < code > String< / code > , < code > juniper::ID< / code > ), or< / li >
< li > A valid custom GraphQL type, e.g. another struct marked with this attribute,
or< / li >
< li > A container/reference containing any of the above, e.g. < code > Vec< T> < / code > , < code > Box< T> < / code > ,
< code > Option< T> < / code > < / li >
< / ul >
< / li >
< / ul >
< p > Let's see what that means for building relationships between objects:< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > #[derive(juniper::GraphQLObject)]
struct Person {
name: String,
age: i32,
}
#[derive(juniper::GraphQLObject)]
struct House {
address: Option< String> , // Converted into String (nullable)
inhabitants: Vec< Person> , // Converted into [Person!]!
}
# fn main() {}
< / code > < / pre > < / pre >
< p > Because < code > Person< / code > is a valid GraphQL type, you can have a < code > Vec< Person> < / code > in a
struct and it'll be automatically converted into a list of non-nullable < code > Person< / code >
objects.< / p >
< a class = "header" href = "#renaming-fields" id = "renaming-fields" > < h2 > Renaming fields< / h2 > < / a >
< p > By default, struct fields are converted from Rust's standard < code > snake_case< / code > naming
convention into GraphQL's < code > camelCase< / code > convention:< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > #[derive(juniper::GraphQLObject)]
struct Person {
first_name: String, // Would be exposed as firstName in the GraphQL schema
last_name: String, // Exposed as lastName
}
# fn main() {}
< / code > < / pre > < / pre >
< p > You can override the name by using the < code > graphql< / code > attribute on individual struct
fields:< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > #[derive(juniper::GraphQLObject)]
struct Person {
name: String,
age: i32,
#[graphql(name=" websiteURL" )]
website_url: Option< String> , // Now exposed as websiteURL in the schema
}
# fn main() {}
< / code > < / pre > < / pre >
< a class = "header" href = "#deprecating-fields" id = "deprecating-fields" > < h2 > Deprecating fields< / h2 > < / a >
< p > To deprecate a field, you specify a deprecation reason using the < code > graphql< / code >
attribute:< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > #[derive(juniper::GraphQLObject)]
struct Person {
name: String,
age: i32,
#[graphql(deprecation=" Please use the name field instead" )]
first_name: String,
}
# fn main() {}
< / code > < / pre > < / pre >
< p > The < code > name< / code > , < code > description< / code > , and < code > deprecation< / code > arguments can of course be
combined. Some restrictions from the GraphQL spec still applies though; you can
only deprecate object fields and enum values.< / p >
< a class = "header" href = "#skipping-fields" id = "skipping-fields" > < h2 > Skipping fields< / h2 > < / a >
< p > By default all fields in a < code > GraphQLObject< / code > are included in the generated GraphQL type. To prevent including a specific field, annotate the field with < code > #[graphql(skip)]< / code > :< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > #[derive(juniper::GraphQLObject)]
struct Person {
name: String,
age: i32,
#[graphql(skip)]
# #[allow(dead_code)]
password_hash: String, // This cannot be queried or modified from GraphQL
}
# fn main() {}
< / code > < / pre > < / pre >
< a class = "header" href = "#complex-fields" id = "complex-fields" > < h1 > Complex fields< / h1 > < / a >
< p > If you've got a struct that can't be mapped directly to GraphQL, that contains
computed fields or circular structures, you have to use a more powerful tool:
the < code > graphql_object!< / code > macro. This macro lets you define GraphQL objects similar
to how you define methods in a Rust < code > impl< / code > block for a type. Continuing with the
example from the last chapter, this is how you would define < code > Person< / code > using the
macro:< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" >
struct Person {
name: String,
age: i32,
}
juniper::graphql_object!(Person: () |& self| {
field name() -> & str {
self.name.as_str()
}
field age() -> i32 {
self.age
}
});
# fn main() { }
< / code > < / pre > < / pre >
< p > While this is a bit more verbose, it lets you write any kind of function in the
field resolver. With this syntax, fields can also take arguments:< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > #[derive(juniper::GraphQLObject)]
struct Person {
name: String,
age: i32,
}
struct House {
inhabitants: Vec< Person> ,
}
juniper::graphql_object!(House: () |& self| {
// Creates the field inhabitantWithName(name), returning a nullable person
field inhabitant_with_name(name: String) -> Option< & Person> {
self.inhabitants.iter().find(|p| p.name == name)
}
});
# fn main() {}
< / code > < / pre > < / pre >
< p > To access global data such as database connections or authentication
information, a < em > context< / em > is used. To learn more about this, see the next
chapter: < a href = "using_contexts.html" > Using contexts< / a > .< / p >
< a class = "header" href = "#description-renaming-and-deprecation" id = "description-renaming-and-deprecation" > < h2 > Description, renaming, and deprecation< / h2 > < / a >
< p > Like with the derive attribute, field names will be converted from < code > snake_case< / code >
to < code > camelCase< / code > . If you need to override the conversion, you can simply rename
the field. Also, the type name can be changed with an alias:< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > struct Person {
name: String,
website_url: String,
}
juniper::graphql_object!(Person: () as " PersonObject" |& self| {
field name() -> & str {
self.name.as_str()
}
field websiteURL() -> & str {
self.website_url.as_str()
}
});
# fn main() { }
< / code > < / pre > < / pre >
< a class = "header" href = "#more-features" id = "more-features" > < h2 > More features< / h2 > < / a >
< p > GraphQL fields expose more features than Rust's standard method syntax gives us:< / p >
< ul >
< li > Per-field description and deprecation messages< / li >
< li > Per-argument default values< / li >
< li > Per-argument descriptions< / li >
< / ul >
< p > These, and more features, are described more thorougly in < a href = "https://docs.rs/juniper/0.8.1/juniper/macro.graphql_object.html" > the reference
documentation< / a > .< / p >
< a class = "header" href = "#using-contexts" id = "using-contexts" > < h1 > Using contexts< / h1 > < / a >
< p > The context type is a feature in Juniper that lets field resolvers access global
data, most commonly database connections or authentication information. The
context is usually created from a < em > context factory< / em > . How this is defined is
specific to the framework integration you're using, so check out the
2019-04-09 19:26:56 -05:00
documentation for either the < a href = "../../servers/iron.html" > Iron< / a > or < a href = "../../servers/rocket.html" > Rocket< / a >
2019-03-23 08:08:09 -05:00
integration.< / p >
< p > In this chapter, we'll show you how to define a context type and use it in field
resolvers. Let's say that we have a simple user database in a < code > HashMap< / code > :< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > # #![allow(dead_code)]
# use std::collections::HashMap;
struct Database {
users: HashMap< i32, User> ,
}
struct User {
id: i32,
name: String,
friend_ids: Vec< i32> ,
}
# fn main() { }
< / code > < / pre > < / pre >
< p > We would like a < code > friends< / code > field on < code > User< / code > that returns a list of < code > User< / code > objects.
In order to write such a field though, the database must be queried.< / p >
< p > To solve this, we mark the < code > Database< / code > as a valid context type and assign it to
the user object. Then, we use the special < code > & executor< / code > argument to access the
current context object:< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > # use std::collections::HashMap;
extern crate juniper;
struct Database {
users: HashMap< i32, User> ,
}
struct User {
id: i32,
name: String,
friend_ids: Vec< i32> ,
}
// 1. Mark the Database as a valid context type for Juniper
impl juniper::Context for Database {}
// 2. Assign Database as the context type for User
juniper::graphql_object!(User: Database |& self| {
// 3. Use the special executor argument
field friends(& executor) -> Vec< & User> {
// 4. Use the executor to access the context object
let database = executor.context();
// 5. Use the database to lookup users
self.friend_ids.iter()
.map(|id| database.users.get(id).expect(" Could not find user with ID" ))
.collect()
}
field name() -> & str { self.name.as_str() }
field id() -> i32 { self.id }
});
# fn main() { }
< / code > < / pre > < / pre >
< p > You only get an immutable reference to the context, so if you want to affect
change to the execution, you'll need to use < a href = "https://doc.rust-lang.org/book/first-edition/mutability.html#interior-vs-exterior-mutability" > interior
mutability< / a >
using e.g. < code > RwLock< / code > or < code > RefCell< / code > .< / p >
< a class = "header" href = "#error-handling" id = "error-handling" > < h1 > Error handling< / h1 > < / a >
< p > Rust
< a href = "https://doc.rust-lang.org/book/second-edition/ch09-00-error-handling.html" > provides< / a >
two ways of dealing with errors: < code > Result< T, E> < / code > for recoverable errors and
< code > panic!< / code > for unrecoverable errors. Juniper does not do anything about panicking;
it will bubble up to the surrounding framework and hopefully be dealt with
there.< / p >
< p > For recoverable errors, Juniper works well with the built-in < code > Result< / code > type, you
can use the < code > ?< / code > operator or the < code > try!< / code > macro and things will generally just work
as you expect them to:< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > # extern crate juniper;
use std::{
str,
path::PathBuf,
fs::{File},
io::{Read},
};
use juniper::FieldResult;
struct Example {
filename: PathBuf,
}
juniper::graphql_object!(Example: () |& self| {
field contents() -> FieldResult< String> {
let mut file = File::open(& self.filename)?;
let mut contents = String::new();
file.read_to_string(& mut contents)?;
Ok(contents)
}
field foo() -> FieldResult< Option< String> > {
// Some invalid bytes.
let invalid = vec![128, 223];
match str::from_utf8(& invalid) {
Ok(s) => Ok(Some(s.to_string())),
Err(e) => Err(e)?,
}
}
});
# fn main() {}
< / code > < / pre > < / pre >
< p > < code > FieldResult< T> < / code > is an alias for < code > Result< T, FieldError> < / code > , which is the error
type all fields must return. By using the < code > ?< / code > operator or < code > try!< / code > macro, any type
that implements the < code > Display< / code > trait - which are most of the error types out
there - those errors are automatically converted into < code > FieldError< / code > .< / p >
< p > When a field returns an error, the field's result is replaced by < code > null< / code > , an
additional < code > errors< / code > object is created at the top level of the response, and the
execution is resumed. For example, with the previous example and the following
query:< / p >
< pre > < code class = "language-graphql" > {
example {
contents
foo
}
}
< / code > < / pre >
< p > If < code > str::from_utf8< / code > resulted in a < code > std::str::Utf8Error< / code > , the following would be
returned:< / p >
< p > !FILENAME Response for nullable field with error< / p >
< pre > < code class = "language-js" > {
" data" : {
" example" : {
contents: " < Contents of the file> " ,
foo: null,
}
},
" errors" : [
" message" : " invalid utf-8 sequence of 2 bytes from index 0" ,
" locations" : [{ " line" : 2, " column" : 4 }])
]
}
< / code > < / pre >
< p > If an error is returned from a non-null field, such as the
example above, the < code > null< / code > value is propagated up to the first nullable parent
field, or the root < code > data< / code > object if there are no nullable fields.< / p >
< p > For example, with the following query:< / p >
< pre > < code class = "language-graphql" > {
example {
contents
}
}
< / code > < / pre >
< p > If < code > File::open()< / code > above resulted in < code > std::io::ErrorKind::PermissionDenied< / code > , the
following would be returned:< / p >
< p > !FILENAME Response for non-null field with error and no nullable parent< / p >
< pre > < code class = "language-js" > {
" errors" : [
" message" : " Permission denied (os error 13)" ,
" locations" : [{ " line" : 2, " column" : 4 }])
]
}
< / code > < / pre >
< a class = "header" href = "#structured-errors" id = "structured-errors" > < h2 > Structured errors< / h2 > < / a >
< p > Sometimes it is desirable to return additional structured error information
to clients. This can be accomplished by implementing < a href = "https://docs.rs/juniper/latest/juniper/trait.IntoFieldError.html" > < code > IntoFieldError< / code > < / a > :< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > # #[macro_use] extern crate juniper;
enum CustomError {
WhateverNotSet,
}
impl juniper::IntoFieldError for CustomError {
fn into_field_error(self) -> juniper::FieldError {
match self {
CustomError::WhateverNotSet => juniper::FieldError::new(
" Whatever does not exist" ,
2019-05-02 12:22:08 -05:00
graphql_value!({
2019-03-23 08:08:09 -05:00
" type" : " NO_WHATEVER"
}),
),
}
}
}
struct Example {
whatever: Option< bool> ,
}
juniper::graphql_object!(Example: () |& self| {
field whatever() -> Result< bool, CustomError> {
if let Some(value) = self.whatever {
return Ok(value);
}
Err(CustomError::WhateverNotSet)
}
});
# fn main() {}
< / code > < / pre > < / pre >
< p > The specified structured error information is included in the < a href = "https://facebook.github.io/graphql/June2018/#sec-Errors" > < code > extensions< / code > < / a > key:< / p >
< pre > < code class = "language-js" > {
" errors" : [
" message" : " Whatever does not exist" ,
" locations" : [{ " line" : 2, " column" : 4 }]),
" extensions" : {
" type" : " NO_WHATEVER"
}
]
}
< / code > < / pre >
< a class = "header" href = "#other-types" id = "other-types" > < h1 > Other Types< / h1 > < / a >
< p > The GraphQL type system provides several types in additon to objects.< / p >
< p > Find out more about each type below:< / p >
< ul >
2019-04-09 19:26:56 -05:00
< li > < a href = "enums.html" > Enums< / a > < / li >
< li > < a href = "interfaces.html" > Interfaces< / a > < / li >
< li > < a href = "input_objects.html" > Input objects< / a > < / li >
< li > < a href = "scalars.html" > Scalars< / a > < / li >
< li > < a href = "unions.html" > Unions< / a > < / li >
2019-03-23 08:08:09 -05:00
< / ul >
< a class = "header" href = "#enums" id = "enums" > < h1 > Enums< / h1 > < / a >
< p > Enums in GraphQL are string constants grouped together to represent a set of
possible values. Simple Rust enums can be converted to GraphQL enums by using a
custom derive attribute:< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > #[derive(juniper::GraphQLEnum)]
enum Episode {
NewHope,
Empire,
Jedi,
}
# fn main() {}
< / code > < / pre > < / pre >
< p > Juniper converts all enum variants to uppercase, so the corresponding string
values for these variants are < code > NEWHOPE< / code > , < code > EMPIRE< / code > , and < code > JEDI< / code > , respectively. If
you want to override this, you can use the < code > graphql< / code > attribute, similar to how
2019-04-09 19:26:56 -05:00
it works when < a href = "objects/defining_objects.html" > defining objects< / a > :< / p >
2019-03-23 08:08:09 -05:00
< pre > < pre class = "playpen" > < code class = "language-rust" > #[derive(juniper::GraphQLEnum)]
enum Episode {
#[graphql(name=" NEW_HOPE" )]
NewHope,
Empire,
Jedi,
}
# fn main() {}
< / code > < / pre > < / pre >
< a class = "header" href = "#documentation-and-deprecation" id = "documentation-and-deprecation" > < h2 > Documentation and deprecation< / h2 > < / a >
< p > Just like when defining objects, the type itself can be renamed and documented,
while individual enum variants can be renamed, documented, and deprecated:< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > #[derive(juniper::GraphQLEnum)]
#[graphql(name=" Episode" , description=" An episode of Star Wars" )]
enum StarWarsEpisode {
#[graphql(deprecated=" We don't really talk about this one" )]
ThePhantomMenace,
#[graphql(name=" NEW_HOPE" )]
NewHope,
#[graphql(description=" Arguably the best one in the trilogy" )]
Empire,
Jedi,
}
# fn main() {}
< / code > < / pre > < / pre >
< a class = "header" href = "#interfaces" id = "interfaces" > < h1 > Interfaces< / h1 > < / a >
< p > GraphQL interfaces map well to interfaces known from common object-oriented
languages such as Java or C#, but Rust has unfortunately not a concept that maps
perfectly to them. Because of this, defining interfaces in Juniper can require a
little bit of boilerplate code, but on the other hand gives you full control
over which type is backing your interface.< / p >
< p > To highlight a couple of different ways you can implement interfaces in Rust,
let's have a look at the same end-result from a few different implementations:< / p >
< a class = "header" href = "#traits" id = "traits" > < h2 > Traits< / h2 > < / a >
< p > Traits are maybe the most obvious concept you want to use when building
interfaces. But because GraphQL supports downcasting while Rust doesn't, you'll
have to manually specify how to convert a trait into a concrete type. This can
be done in a couple of different ways:< / p >
< a class = "header" href = "#downcasting-via-accessor-methods" id = "downcasting-via-accessor-methods" > < h3 > Downcasting via accessor methods< / h3 > < / a >
< pre > < pre class = "playpen" > < code class = "language-rust" > #[derive(juniper::GraphQLObject)]
struct Human {
id: String,
home_planet: String,
}
#[derive(juniper::GraphQLObject)]
struct Droid {
id: String,
primary_function: String,
}
trait Character {
fn id(& self) -> & str;
// Downcast methods, each concrete class will need to implement one of these
fn as_human(& self) -> Option< & Human> { None }
fn as_droid(& self) -> Option< & Droid> { None }
}
impl Character for Human {
fn id(& self) -> & str { self.id.as_str() }
fn as_human(& self) -> Option< & Human> { Some(& self) }
}
impl Character for Droid {
fn id(& self) -> & str { self.id.as_str() }
fn as_droid(& self) -> Option< & Droid> { Some(& self) }
}
juniper::graphql_interface!(< 'a> & 'a Character: () as " Character" where Scalar = < S> |& self| {
field id() -> & str { self.id() }
instance_resolvers: |_| {
// The left hand side indicates the concrete type T, the right hand
// side should be an expression returning Option< T>
& Human => self.as_human(),
& Droid => self.as_droid(),
}
});
# fn main() {}
< / code > < / pre > < / pre >
< p > The < code > instance_resolvers< / code > declaration lists all the implementors of the given
interface and how to resolve them.< / p >
< p > As you can see, you lose a bit of the point with using traits: you need to list
all the concrete types in the trait itself, and there's a bit of repetition
going on.< / p >
< a class = "header" href = "#using-an-extra-database-lookup" id = "using-an-extra-database-lookup" > < h3 > Using an extra database lookup< / h3 > < / a >
< p > If you can afford an extra database lookup when the concrete class is requested,
you can do away with the downcast methods and use the context instead. Here,
we'll use two hashmaps, but this could be two tables and some SQL calls instead:< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > # use std::collections::HashMap;
#[derive(juniper::GraphQLObject)]
#[graphql(Context = " Database" )]
struct Human {
id: String,
home_planet: String,
}
#[derive(juniper::GraphQLObject)]
#[graphql(Context = " Database" )]
struct Droid {
id: String,
primary_function: String,
}
struct Database {
humans: HashMap< String, Human> ,
droids: HashMap< String, Droid> ,
}
impl juniper::Context for Database {}
trait Character {
fn id(& self) -> & str;
}
impl Character for Human {
fn id(& self) -> & str { self.id.as_str() }
}
impl Character for Droid {
fn id(& self) -> & str { self.id.as_str() }
}
juniper::graphql_interface!(< 'a> & 'a Character: Database as " Character" where Scalar = < S> |& self| {
field id() -> & str { self.id() }
instance_resolvers: |& context| {
& Human => context.humans.get(self.id()),
& Droid => context.droids.get(self.id()),
}
});
# fn main() {}
< / code > < / pre > < / pre >
< p > This removes the need of downcast methods, but still requires some repetition.< / p >
< a class = "header" href = "#placeholder-objects" id = "placeholder-objects" > < h2 > Placeholder objects< / h2 > < / a >
< p > Continuing on from the last example, the trait itself seems a bit unneccesary.
Maybe it can just be a struct containing the ID?< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > # use std::collections::HashMap;
#[derive(juniper::GraphQLObject)]
#[graphql(Context = " Database" )]
struct Human {
id: String,
home_planet: String,
}
#[derive(juniper::GraphQLObject)]
#[graphql(Context = " Database" )]
struct Droid {
id: String,
primary_function: String,
}
struct Database {
humans: HashMap< String, Human> ,
droids: HashMap< String, Droid> ,
}
impl juniper::Context for Database {}
struct Character {
id: String,
}
juniper::graphql_interface!(Character: Database where Scalar = < S> |& self| {
field id() -> & str { self.id.as_str() }
instance_resolvers: |& context| {
& Human => context.humans.get(& self.id),
& Droid => context.droids.get(& self.id),
}
});
# fn main() {}
< / code > < / pre > < / pre >
< p > This reduces repetition some more, but might be impractical if the interface's
surface area is large.< / p >
< a class = "header" href = "#enums-1" id = "enums-1" > < h2 > Enums< / h2 > < / a >
< p > Using enums and pattern matching lies half-way between using traits and using
placeholder objects. We don't need the extra database call in this case, so
we'll remove it.< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > #[derive(juniper::GraphQLObject)]
struct Human {
id: String,
home_planet: String,
}
#[derive(juniper::GraphQLObject)]
struct Droid {
id: String,
primary_function: String,
}
# #[allow(dead_code)]
enum Character {
Human(Human),
Droid(Droid),
}
juniper::graphql_interface!(Character: () where Scalar = < S> |& self| {
field id() -> & str {
match *self {
Character::Human(Human { ref id, .. }) |
Character::Droid(Droid { ref id, .. }) => id,
}
}
instance_resolvers: |_| {
& Human => match *self { Character::Human(ref h) => Some(h), _ => None },
& Droid => match *self { Character::Droid(ref d) => Some(d), _ => None },
}
});
# fn main() {}
< / code > < / pre > < / pre >
< a class = "header" href = "#input-objects" id = "input-objects" > < h1 > Input objects< / h1 > < / a >
< p > Input objects are complex data structures that can be used as arguments to
GraphQL fields. In Juniper, you can define input objects using a custom derive
attribute, similar to simple objects and enums:< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > #[derive(juniper::GraphQLInputObject)]
struct Coordinate {
latitude: f64,
longitude: f64
}
struct Root;
# #[derive(juniper::GraphQLObject)] struct User { name: String }
juniper::graphql_object!(Root: () |& self| {
field users_at_location(coordinate: Coordinate, radius: f64) -> Vec< User> {
// Send coordinate to database
# unimplemented!()
}
});
# fn main() {}
< / code > < / pre > < / pre >
< a class = "header" href = "#documentation-and-renaming" id = "documentation-and-renaming" > < h2 > Documentation and renaming< / h2 > < / a >
2019-04-09 19:26:56 -05:00
< p > Just like the < a href = "objects/defining_objects.html" > other< / a > < a href = "enums.html" > derives< / a > , you can rename
2019-03-23 08:08:09 -05:00
and add documentation to both the type and the fields:< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > #[derive(juniper::GraphQLInputObject)]
#[graphql(name=" Coordinate" , description=" A position on the globe" )]
struct WorldCoordinate {
#[graphql(name=" lat" , description=" The latitude" )]
latitude: f64,
#[graphql(name=" long" , description=" The longitude" )]
longitude: f64
}
struct Root;
# #[derive(juniper::GraphQLObject)] struct User { name: String }
juniper::graphql_object!(Root: () |& self| {
field users_at_location(coordinate: WorldCoordinate, radius: f64) -> Vec< User> {
// Send coordinate to database
# unimplemented!()
}
});
# fn main() {}
< / code > < / pre > < / pre >
< a class = "header" href = "#scalars" id = "scalars" > < h1 > Scalars< / h1 > < / a >
< p > Scalars are the primitive types at the leaves of a GraphQL query: numbers,
strings, and booleans. You can create custom scalars to other primitive values,
but this often requires coordination with the client library intended to consume
the API you're building.< / p >
< p > Since any value going over the wire is eventually transformed into JSON, you're
also limited in the data types you can use. Typically, you represent your custom
scalars as strings.< / p >
< p > In Juniper, you use the < code > graphql_scalar!< / code > macro to create a custom scalar. In
this example, we're representing a user ID as a string wrapped in a custom type:< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > use juniper::Value;
struct UserID(String);
juniper::graphql_scalar!(UserID {
description: " An opaque identifier, represented as a string"
resolve(& self) -> Value {
Value::scalar(self.0.clone())
}
from_input_value(v: & InputValue) -> Option< UserID> {
// If there's a parse error here, simply return None. Juniper will
// present an error to the client.
v.as_scalar_value::< String> ().map(|s| UserID(s.to_owned()))
}
from_str< 'a> (value: ScalarToken< 'a> ) -> juniper::ParseScalarResult< 'a, juniper::DefaultScalarValue> {
< String as juniper::ParseScalarValue> ::from_str(value)
}
});
# fn main() {}
< / code > < / pre > < / pre >
< a class = "header" href = "#built-in-scalars" id = "built-in-scalars" > < h2 > Built-in scalars< / h2 > < / a >
< p > Juniper has built-in support for:< / p >
< ul >
< li > < code > i32< / code > as < code > Int< / code > < / li >
< li > < code > f64< / code > as < code > Float< / code > < / li >
< li > < code > String< / code > and < code > & str< / code > as < code > String< / code > < / li >
< li > < code > bool< / code > as < code > Boolean< / code > < / li >
< li > < code > juniper::ID< / code > as < code > ID< / code > . This type is defined < a href = "http://facebook.github.io/graphql/#sec-ID" > in the
spec< / a > as a type that is serialized
as a string but can be parsed from both a string and an integer.< / li >
< / ul >
< a class = "header" href = "#non-standard-scalars" id = "non-standard-scalars" > < h3 > Non-standard scalars< / h3 > < / a >
< p > Juniper has built-in support for UUIDs from the < a href = "https://doc.rust-lang.org/uuid/uuid/index.html" > uuid
crate< / a > . This support is enabled
by default, but can be disabled if you want to reduce the number of dependencies
in your application.< / p >
< a class = "header" href = "#unions" id = "unions" > < h1 > Unions< / h1 > < / a >
< p > From a server's point of view, GraphQL unions are similar to interfaces: the
only exception is that they don't contain fields on their own.< / p >
< p > In Juniper, the < code > graphql_union!< / code > has identical syntax to the < a href = "interfaces.html" > interface
macro< / a > , but does not support defining fields. Therefore, the same
considerations about using traits, placeholder types, or enums still apply to
unions.< / p >
< p > If we look at the same examples as in the interfaces chapter, we see the
similarities and the tradeoffs:< / p >
< a class = "header" href = "#traits-1" id = "traits-1" > < h2 > Traits< / h2 > < / a >
< a class = "header" href = "#downcasting-via-accessor-methods-1" id = "downcasting-via-accessor-methods-1" > < h3 > Downcasting via accessor methods< / h3 > < / a >
< pre > < pre class = "playpen" > < code class = "language-rust" > #[derive(juniper::GraphQLObject)]
struct Human {
id: String,
home_planet: String,
}
#[derive(juniper::GraphQLObject)]
struct Droid {
id: String,
primary_function: String,
}
trait Character {
// Downcast methods, each concrete class will need to implement one of these
fn as_human(& self) -> Option< & Human> { None }
fn as_droid(& self) -> Option< & Droid> { None }
}
impl Character for Human {
fn as_human(& self) -> Option< & Human> { Some(& self) }
}
impl Character for Droid {
fn as_droid(& self) -> Option< & Droid> { Some(& self) }
}
juniper::graphql_union!(< 'a> & 'a Character: () as " Character" where Scalar = < S> |& self| {
instance_resolvers: |_| {
// The left hand side indicates the concrete type T, the right hand
// side should be an expression returning Option< T>
& Human => self.as_human(),
& Droid => self.as_droid(),
}
});
# fn main() {}
< / code > < / pre > < / pre >
< a class = "header" href = "#using-an-extra-database-lookup-1" id = "using-an-extra-database-lookup-1" > < h3 > Using an extra database lookup< / h3 > < / a >
< p > FIXME: This example does not compile at the moment< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > # use std::collections::HashMap;
#[derive(juniper::GraphQLObject)]
#[graphql(Context = " Database" )]
struct Human {
id: String,
home_planet: String,
}
#[derive(juniper::GraphQLObject)]
#[graphql(Context = " Database" )]
struct Droid {
id: String,
primary_function: String,
}
struct Database {
humans: HashMap< String, Human> ,
droids: HashMap< String, Droid> ,
}
impl juniper::Context for Database {}
trait Character {
fn id(& self) -> & str;
}
impl Character for Human {
fn id(& self) -> & str { self.id.as_str() }
}
impl Character for Droid {
fn id(& self) -> & str { self.id.as_str() }
}
juniper::graphql_union!(< 'a> & 'a Character: Database as " Character" where Scalar = < S> |& self| {
instance_resolvers: |& context| {
& Human => context.humans.get(self.id()),
& Droid => context.droids.get(self.id()),
}
});
# fn main() {}
< / code > < / pre > < / pre >
< a class = "header" href = "#placeholder-objects-1" id = "placeholder-objects-1" > < h2 > Placeholder objects< / h2 > < / a >
< pre > < pre class = "playpen" > < code class = "language-rust" > # use std::collections::HashMap;
#[derive(juniper::GraphQLObject)]
#[graphql(Context = " Database" )]
struct Human {
id: String,
home_planet: String,
}
#[derive(juniper::GraphQLObject)]
#[graphql(Context = " Database" )]
struct Droid {
id: String,
primary_function: String,
}
struct Database {
humans: HashMap< String, Human> ,
droids: HashMap< String, Droid> ,
}
impl juniper::Context for Database {}
struct Character {
id: String,
}
juniper::graphql_union!(Character: Database where Scalar = < S> |& self| {
instance_resolvers: |& context| {
& Human => context.humans.get(& self.id),
& Droid => context.droids.get(& self.id),
}
});
# fn main() {}
< / code > < / pre > < / pre >
< a class = "header" href = "#enums-2" id = "enums-2" > < h2 > Enums< / h2 > < / a >
< pre > < pre class = "playpen" > < code class = "language-rust" > #[derive(juniper::GraphQLObject)]
struct Human {
id: String,
home_planet: String,
}
#[derive(juniper::GraphQLObject)]
struct Droid {
id: String,
primary_function: String,
}
# #[allow(dead_code)]
enum Character {
Human(Human),
Droid(Droid),
}
juniper::graphql_union!(Character: () where Scalar = < S> |& self| {
instance_resolvers: |_| {
& Human => match *self { Character::Human(ref h) => Some(h), _ => None },
& Droid => match *self { Character::Droid(ref d) => Some(d), _ => None },
}
});
# fn main() {}
< / code > < / pre > < / pre >
< a class = "header" href = "#schemas" id = "schemas" > < h1 > Schemas< / h1 > < / a >
< p > A schema consists of two types: a query object and a mutation object (Juniper
does not support subscriptions yet). These two define the root query fields
and mutations of the schema, respectively.< / p >
< p > Both query and mutation objects are regular GraphQL objects, defined like any
other object in Juniper. The mutation object, however, is optional since schemas
can be read-only.< / p >
< p > In Juniper, the < code > RootNode< / code > type represents a schema. You usually don't have to
2019-04-09 19:26:56 -05:00
create this object yourself: see the framework integrations for < a href = "../servers/iron.html" > Iron< / a >
and < a href = "../servers/rocket.html" > Rocket< / a > how schemas are created together with the handlers
2019-03-23 08:08:09 -05:00
themselves.< / p >
< p > When the schema is first created, Juniper will traverse the entire object graph
and register all types it can find. This means that if you define a GraphQL
object somewhere but never references it, it will not be exposed in a schema.< / p >
< a class = "header" href = "#the-query-root" id = "the-query-root" > < h2 > The query root< / h2 > < / a >
< p > The query root is just a GraphQL object. You define it like any other GraphQL
object in Juniper, most commonly using the < code > graphql_object!< / code > macro:< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > # use juniper::FieldResult;
# #[derive(juniper::GraphQLObject)] struct User { name: String }
struct Root;
juniper::graphql_object!(Root: () |& self| {
field userWithUsername(username: String) -> FieldResult< Option< User> > {
// Look up user in database...
# unimplemented!()
}
});
# fn main() { }
< / code > < / pre > < / pre >
< a class = "header" href = "#mutations" id = "mutations" > < h2 > Mutations< / h2 > < / a >
< p > Mutations are < em > also< / em > just GraphQL objects. Each mutation is a single field that
usually performs some mutating side-effect, such as updating a database.< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > # use juniper::FieldResult;
# #[derive(juniper::GraphQLObject)] struct User { name: String }
struct Mutations;
juniper::graphql_object!(Mutations: () |& self| {
field signUpUser(name: String, email: String) -> FieldResult< User> {
// Validate inputs and save user in database...
# unimplemented!()
}
});
# fn main() { }
< / code > < / pre > < / pre >
< a class = "header" href = "#adding-a-server" id = "adding-a-server" > < h1 > Adding A Server< / h1 > < / a >
< p > To allow using Juniper with the HTTP server of your choice,
it does < strong > not< / strong > come with a built in HTTP server.< / p >
< p > To actually get a server up and running, there are multiple official and
third-party integration crates that will get you there.< / p >
< ul >
2019-04-09 19:26:56 -05:00
< li > < a href = "official.html" > Official Server Integrations< / a >
2019-03-23 08:08:09 -05:00
< ul >
2019-04-09 19:26:56 -05:00
< li > < a href = "hyper.html" > Hyper< / a > < / li >
< li > < a href = "warp.html" > Warp< / a > < / li >
< li > < a href = "rocket.html" > Rocket< / a > < / li >
< li > < a href = "iron.html" > Iron< / a > < / li >
< li > < a href = "hyper.html" > Hyper< / a > < / li >
2019-03-23 08:08:09 -05:00
< / ul >
< / li >
2019-04-09 19:26:56 -05:00
< li > < a href = "third-party.html" > Third Party Integrations< / a >
2019-03-23 08:08:09 -05:00
< ul >
< li > < a href = "https://github.com/actix/examples/tree/master/juniper" > Actix-Web< / a > < / li >
< li > < a href = "https://github.com/finchers-rs/finchers-juniper" > Finchers< / a > < / li >
< li > < a href = "https://github.com/tsukuyomi-rs/tsukuyomi/tree/master/examples/juniper" > Tsukuyomi< / a > < / li >
< / ul >
< / li >
< / ul >
< a class = "header" href = "#official-server-integrations" id = "official-server-integrations" > < h1 > Official Server Integrations< / h1 > < / a >
< p > Juniper provides official integration crates for several popular Rust server
libraries.< / p >
< ul >
2019-04-09 19:26:56 -05:00
< li > < a href = "hyper.html" > Hyper< / a > < / li >
< li > < a href = "warp.html" > Warp< / a > < / li >
< li > < a href = "rocket.html" > Rocket< / a > < / li >
< li > < a href = "iron.html" > Iron< / a > < / li >
2019-03-23 08:08:09 -05:00
< / ul >
< a class = "header" href = "#integrating-with-warp" id = "integrating-with-warp" > < h1 > Integrating with Warp< / h1 > < / a >
< p > < a href = "https://crates.io/crates/warp" > Warp< / a > is a super-easy, composable, web server framework for warp speeds.
The fundamental building block of warp is the Filter: they can be combined and composed to express rich requirements on requests. Warp is built on < a href = "https://hyper.rs/" > Hyper< / a > and works on
Rust's stable channel.< / p >
< p > Juniper's Warp integration is contained in the < a href = "https://github.com/graphql-rust/juniper/tree/master/juniper_warp" > < code > juniper_warp< / code > < / a > crate:< / p >
< p > !FILENAME Cargo.toml< / p >
< pre > < code class = "language-toml" > [dependencies]
juniper = " 0.10"
juniper_warp = " 0.1.0"
< / code > < / pre >
< p > Included in the source is a < a href = "https://github.com/graphql-rust/juniper/tree/master/juniper_warp/examples/warp_server" > small example< / a > which sets up a basic GraphQL and < a href = "https://github.com/graphql/graphiql" > GraphiQL< / a > handler.< / p >
< a class = "header" href = "#integrating-with-rocket" id = "integrating-with-rocket" > < h1 > Integrating with Rocket< / h1 > < / a >
< p > < a href = "https://rocket.rs/" > Rocket< / a > is a web framework for Rust that makes it simple to write fast web applications without sacrificing flexibility or type safety. All with minimal code. Rocket
does not work on Rust's stable channel and instead requires the nightly
channel.< / p >
< p > Juniper's Rocket integration is contained in the < a href = "https://github.com/graphql-rust/juniper/tree/master/juniper_rocket" > < code > juniper_rocket< / code > < / a > crate:< / p >
< p > !FILENAME Cargo.toml< / p >
< pre > < code class = "language-toml" > [dependencies]
juniper = " 0.10"
juniper_rocket = " 0.2.0"
< / code > < / pre >
< p > Included in the source is a < a href = "https://github.com/graphql-rust/juniper/blob/master/juniper_rocket/examples/rocket_server.rs" > small example< / a > which sets up a basic GraphQL and < a href = "https://github.com/graphql/graphiql" > GraphiQL< / a > handler.< / p >
< a class = "header" href = "#integrating-with-iron" id = "integrating-with-iron" > < h1 > Integrating with Iron< / h1 > < / a >
< p > < a href = "http://ironframework.io" > Iron< / a > is a library that's been around for a while in the Rust sphere but lately
hasn't seen much of development. Nevertheless, it's still a solid library with a
familiar request/response/middleware architecture that works on Rust's stable
channel.< / p >
< p > Juniper's Iron integration is contained in the < code > juniper_iron< / code > crate:< / p >
< p > !FILENAME Cargo.toml< / p >
< pre > < code class = "language-toml" > [dependencies]
juniper = " 0.10"
juniper_iron = " 0.2.0"
< / code > < / pre >
< p > Included in the source is a < a href = "https://github.com/graphql-rust/juniper_iron/blob/master/examples/iron_server.rs" > small
example< / a >
which sets up a basic GraphQL and < a href = "https://github.com/graphql/graphiql" > GraphiQL< / a > handler.< / p >
< a class = "header" href = "#basic-integration" id = "basic-integration" > < h2 > Basic integration< / h2 > < / a >
< p > Let's start with a minimal schema and just get a GraphQL endpoint up and
running. We use < a href = "https://github.com/iron/mount" > mount< / a > to attach the GraphQL handler at < code > /graphql< / code > .< / p >
< p > The < code > context_factory< / code > function will be executed on every request and can be used
to set up database connections, read session token information from cookies, and
set up other global data that the schema might require.< / p >
< p > In this example, we won't use any global data so we just return an empty value.< / p >
< pre > < code class = "language-rust ignore" > extern crate juniper;
extern crate juniper_iron;
extern crate iron;
extern crate mount;
use mount::Mount;
use iron::prelude::*;
use juniper::EmptyMutation;
use juniper_iron::GraphQLHandler;
fn context_factory(_: & mut Request) -> IronResult< ()> {
Ok(())
}
struct Root;
graphql_object!(Root: () |& self| {
field foo() -> String {
" Bar" .to_owned()
}
});
# #[allow(unreachable_code, unused_variables)]
fn main() {
let mut mount = Mount::new();
let graphql_endpoint = GraphQLHandler::new(
context_factory,
Root,
EmptyMutation::< ()> ::new(),
);
mount.mount(" /graphql" , graphql_endpoint);
let chain = Chain::new(mount);
# return;
Iron::new(chain).http(" 0.0.0.0:8080" ).unwrap();
}
< / code > < / pre >
< a class = "header" href = "#accessing-data-from-the-request" id = "accessing-data-from-the-request" > < h2 > Accessing data from the request< / h2 > < / a >
< p > If you want to access e.g. the source IP address of the request from a field
2019-04-09 19:26:56 -05:00
resolver, you need to pass this data using Juniper's < a href = "../types/objects/using_contexts.html" > context feature< / a > .< / p >
2019-03-23 08:08:09 -05:00
< pre > < code class = "language-rust ignore" > # extern crate juniper;
# extern crate juniper_iron;
# extern crate iron;
# use iron::prelude::*;
use std::net::SocketAddr;
struct Context {
remote_addr: SocketAddr,
}
impl juniper::Context for Context {}
fn context_factory(req: & mut Request) -> IronResult< Context> {
Ok(Context {
remote_addr: req.remote_addr
})
}
struct Root;
graphql_object!(Root: Context |& self| {
field my_addr(& executor) -> String {
let context = executor.context();
format!(" Hello, you're coming from {}" , context.remote_addr)
}
});
# fn main() {
# let _graphql_endpoint = juniper_iron::GraphQLHandler::new(
# context_factory,
# Root,
# juniper::EmptyMutation::< Context> ::new(),
# );
# }
< / code > < / pre >
< a class = "header" href = "#accessing-global-data" id = "accessing-global-data" > < h2 > Accessing global data< / h2 > < / a >
< p > FIXME: Show how the < code > persistent< / code > crate works with contexts using e.g. < code > r2d2< / code > .< / p >
< a class = "header" href = "#integrating-with-hyper" id = "integrating-with-hyper" > < h1 > Integrating with Hyper< / h1 > < / a >
< p > < a href = "https://hyper.rs/" > Hyper< / a > is a is a fast HTTP implementation that many other Rust web frameworks
leverage. It offers asynchronous I/O via the tokio runtime and works on
Rust's stable channel.< / p >
< p > Hyper is not a higher-level web framework and accordingly
does not include ergonomic features such as simple endpoint routing,
baked-in HTTP responses, or reusable middleware. For GraphQL, those aren't
large downsides as all POSTs and GETs usually go through a single endpoint with
a few clearly-defined response payloads.< / p >
< p > Juniper's Hyper integration is contained in the < a href = "https://github.com/graphql-rust/juniper/tree/master/juniper_hyper" > < code > juniper_hyper< / code > < / a > crate:< / p >
< p > !FILENAME Cargo.toml< / p >
< pre > < code class = "language-toml" > [dependencies]
juniper = " 0.10"
juniper_hyper = " 0.1.0"
< / code > < / pre >
< p > Included in the source is a < a href = "https://github.com/graphql-rust/juniper/blob/master/juniper_hyper/examples/hyper_server.rs" > small example< / a > which sets up a basic GraphQL and < a href = "https://github.com/graphql/graphiql" > GraphiQL< / a > handler.< / p >
< a class = "header" href = "#third-party-integrations" id = "third-party-integrations" > < h1 > Third-Party Integrations< / h1 > < / a >
< p > There are several examples or third party integration crates that are not
officially maintained by Juniper developers.< / p >
< ul >
< li > < a href = "https://github.com/actix/examples/tree/master/juniper" > Actix-Web< / a > < / li >
< li > < a href = "https://github.com/finchers-rs/finchers-juniper" > Finchers< / a > < / li >
< li > < a href = "https://github.com/tsukuyomi-rs/tsukuyomi/tree/master/examples/juniper" > Tsukuyomi< / a > < / li >
< / ul >
< a class = "header" href = "#advanced-topics" id = "advanced-topics" > < h1 > Advanced Topics< / h1 > < / a >
< p > The chapters below cover some more advanced scenarios.< / p >
< ul >
2019-04-09 19:26:56 -05:00
< li > < a href = "introspection.html" > Introspection< / a > < / li >
< li > < a href = "non_struct_objects.html" > Non-struct objects< / a > < / li >
< li > < a href = "objects_and_generics.html" > Objects and generics< / a > < / li >
< li > < a href = "multiple_ops_per_request.html" > Multiple operations per request< / a > < / li >
2019-03-23 08:08:09 -05:00
< / ul >
2019-04-09 19:26:56 -05:00
< a class = "header" href = "#introspection" id = "introspection" > < h1 > Introspection< / h1 > < / a >
< p > GraphQL defines a special built-in top-level field called < code > __schema< / code > . Querying
for this field allows one to < a href = "https://graphql.org/learn/introspection/" > introspect the schema< / a >
at runtime to see what queries and mutations the GraphQL server supports.< / p >
< p > Because introspection queries are just regular GraphQL queries, Juniper supports
them natively. For example, to get all the names of the types supported one
could execute the following query against Juniper:< / p >
< pre > < code class = "language-graphql" > {
__schema {
types {
name
}
}
}
< / code > < / pre >
< a class = "header" href = "#schema-introspection-output-as-json" id = "schema-introspection-output-as-json" > < h2 > Schema introspection output as JSON< / h2 > < / a >
< p > Many client libraries and tools in the GraphQL ecosystem require a complete
representation of the server schema. Often this representation is in JSON and
referred to as < code > schema.json< / code > . A complete representation of the schema can be
produced by issuing a specially crafted introspection query.< / p >
< p > Juniper provides a convenience function to introspect the entire schema. The
result can then be converted to JSON for use with tools and libraries such as
< a href = "https://github.com/graphql-rust/graphql-client" > graphql-client< / a > :< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > # // Only needed due to 2018 edition because the macro is not accessible.
# extern crate juniper;
# extern crate serde_json;
use juniper::{EmptyMutation, FieldResult, IntrospectionFormat};
// Define our schema.
#[derive(juniper::GraphQLObject)]
struct Example {
id: String,
}
struct Context;
impl juniper::Context for Context {}
struct Query;
juniper::graphql_object!(Query: Context |& self| {
field example(& executor, id: String) -> FieldResult< Example> {
unimplemented!()
}
});
type Schema = juniper::RootNode< 'static, Query, EmptyMutation< Context> > ;
fn main() {
// Create a context object.
let ctx = Context{};
// Run the built-in introspection query.
let (res, _errors) = juniper::introspect(
& Schema::new(Query, EmptyMutation::new()),
& ctx,
IntrospectionFormat::default(),
).unwrap();
// Convert introspection result to json.
let json_result = serde_json::to_string_pretty(& res);
assert!(json_result.is_ok());
}
< / code > < / pre > < / pre >
2019-03-23 08:08:09 -05:00
< a class = "header" href = "#non-struct-objects" id = "non-struct-objects" > < h1 > Non-struct objects< / h1 > < / a >
< p > Up until now, we've only looked at mapping structs to GraphQL objects. However,
any Rust type can be mapped into a GraphQL object. In this chapter, we'll look
at enums, but traits will work too - they don't < em > have< / em > to be mapped into GraphQL
interfaces.< / p >
< p > Using < code > Result< / code > -like enums can be a useful way of reporting e.g. validation
errors from a mutation:< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > # #[derive(juniper::GraphQLObject)] struct User { name: String }
#[derive(juniper::GraphQLObject)]
struct ValidationError {
field: String,
message: String,
}
# #[allow(dead_code)]
enum SignUpResult {
Ok(User),
Error(Vec< ValidationError> ),
}
juniper::graphql_object!(SignUpResult: () |& self| {
field user() -> Option< & User> {
match *self {
SignUpResult::Ok(ref user) => Some(user),
SignUpResult::Error(_) => None,
}
}
field error() -> Option< & Vec< ValidationError> > {
match *self {
SignUpResult::Ok(_) => None,
SignUpResult::Error(ref errors) => Some(errors)
}
}
});
# fn main() {}
< / code > < / pre > < / pre >
< p > Here, we use an enum to decide whether a user's input data was valid or not, and
it could be used as the result of e.g. a sign up mutation.< / p >
< p > While this is an example of how you could use something other than a struct to
represent a GraphQL object, it's also an example on how you could implement
error handling for " expected" errors - errors like validation errors. There are
no hard rules on how to represent errors in GraphQL, but there are
< a href = "https://github.com/facebook/graphql/issues/117#issuecomment-170180628" > some< / a >
< a href = "https://github.com/graphql/graphql-js/issues/560#issuecomment-259508214" > comments< / a >
from one of the authors of GraphQL on how they intended " hard" field errors to
be used, and how to model expected errors.< / p >
< a class = "header" href = "#objects-and-generics" id = "objects-and-generics" > < h1 > Objects and generics< / h1 > < / a >
< p > Yet another point where GraphQL and Rust differs is in how generics work. In
Rust, almost any type could be generic - that is, take type parameters. In
GraphQL, there are only two generic types: lists and non-nullables.< / p >
< p > This poses a restriction on what you can expose in GraphQL from Rust: no generic
structs can be exposed - all type parameters must be bound. For example, you can
not make e.g. < code > Result< T, E> < / code > into a GraphQL type, but you < em > can< / em > make e.g.
< code > Result< User, String> < / code > into a GraphQL type.< / p >
< p > Let's make a slightly more compact but generic implementation of < a href = "non_struct_objects.html" > the last
chapter< / a > :< / p >
< pre > < pre class = "playpen" > < code class = "language-rust" > # #[derive(juniper::GraphQLObject)] struct User { name: String }
# #[derive(juniper::GraphQLObject)] struct ForumPost { title: String }
#[derive(juniper::GraphQLObject)]
struct ValidationError {
field: String,
message: String,
}
# #[allow(dead_code)]
struct MutationResult< T> (Result< T, Vec< ValidationError> > );
juniper::graphql_object!(MutationResult< User> : () as " UserResult" |& self| {
field user() -> Option< & User> {
self.0.as_ref().ok()
}
field error() -> Option< & Vec< ValidationError> > {
self.0.as_ref().err()
}
});
juniper::graphql_object!(MutationResult< ForumPost> : () as " ForumPostResult" |& self| {
field forum_post() -> Option< & ForumPost> {
self.0.as_ref().ok()
}
field error() -> Option< & Vec< ValidationError> > {
self.0.as_ref().err()
}
});
# fn main() {}
< / code > < / pre > < / pre >
< p > Here, we've made a wrapper around < code > Result< / code > and exposed some concrete
instantiations of < code > Result< T, E> < / code > as distinct GraphQL objects. The reason we
needed the wrapper is of Rust's rules for when you can derive a trait - in this
case, both < code > Result< / code > and Juniper's internal GraphQL trait are from third-party
sources.< / p >
< p > Because we're using generics, we also need to specify a name for our
instantiated types. Even if Juniper < em > could< / em > figure out the name,
< code > MutationResult< User> < / code > wouldn't be a valid GraphQL type name.< / p >
< a class = "header" href = "#multiple-operations-per-request" id = "multiple-operations-per-request" > < h1 > Multiple operations per request< / h1 > < / a >
< p > The GraphQL standard generally assumes there will be one server request for each client operation you want to perform (such as a query or mutation). This is conceptually simple but has the potential to be inefficent.< / p >
< p > Some client libraries such as < a href = "https://www.apollographql.com/docs/link/links/batch-http.html" > apollo-link-batch-http< / a > have added the ability to batch operations in a single HTTP request to save network round-trips and potentially increase performance. There are some < a href = "https://blog.apollographql.com/batching-client-graphql-queries-a685f5bcd41b" > tradeoffs< / a > that should be considered before batching requests.< / p >
< p > Juniper's server integration crates support multiple operations in a single HTTP request using JSON arrays. This makes them compatible with client libraries that support batch operations without any special configuration.< / p >
< p > Server integration crates maintained by others are < strong > not required< / strong > to support batch requests. Batch requests aren't part of the official GraphQL specification.< / p >
< p > Assuming an integration supports batch requests, for the following GraphQL query:< / p >
< pre > < code class = "language-graphql" > {
hero {
name
}
}
< / code > < / pre >
< p > The json data to POST to the server for an individual request would be:< / p >
< pre > < code class = "language-json" > {
" query" : " {hero{name}}"
}
< / code > < / pre >
< p > And the response would be of the form:< / p >
< pre > < code class = "language-json" > {
" data" : {
" hero" : {
" name" : " R2-D2"
}
}
}
< / code > < / pre >
< p > If you wanted to run the same query twice in a single HTTP request, the batched json data to POST to the server would be:< / p >
< pre > < code class = "language-json" > [
{
" query" : " {hero{name}}"
},
{
" query" : " {hero{name}}"
}
]
< / code > < / pre >
< p > And the response would be of the form:< / p >
< pre > < code class = "language-json" > [
{
" data" : {
" hero" : {
" name" : " R2-D2"
}
}
},
{
" data" : {
" hero" : {
" name" : " R2-D2"
}
}
}
]
< / code > < / pre >
< / main >
< nav class = "nav-wrapper" aria-label = "Page navigation" >
<!-- Mobile navigation buttons -->
< div style = "clear: both" > < / div >
< / nav >
< / div >
< / div >
< nav class = "nav-wide-wrapper" aria-label = "Page navigation" >
< / nav >
< / div >
< script src = "elasticlunr.min.js" type = "text/javascript" charset = "utf-8" > < / script >
< script src = "mark.min.js" type = "text/javascript" charset = "utf-8" > < / script >
< script src = "searcher.js" type = "text/javascript" charset = "utf-8" > < / script >
< script src = "clipboard.min.js" type = "text/javascript" charset = "utf-8" > < / script >
< script src = "highlight.js" type = "text/javascript" charset = "utf-8" > < / script >
< script src = "book.js" type = "text/javascript" charset = "utf-8" > < / script >
<!-- Custom JS scripts -->
< script type = "text/javascript" >
window.addEventListener('load', function() {
window.setTimeout(window.print, 100);
});
< / script >
< / body >
< / html >