A common issue with graphql servers is how the resolvers query their datasource.
+his issue results in a large number of unneccessary database queries or http requests.
+Say you were wanting to list a bunch of cults people were in
+
query {
+ persons {
+ id
+ name
+ cult {
+ id
+ name
+ }
+ }
+}
+
+
What would be executed by a SQL database would be:
+
SELECT id, name, cult_id FROM persons;
+SELECT id, name FROM cults WHERE id = 1;
+SELECT id, name FROM cults WHERE id = 1;
+SELECT id, name FROM cults WHERE id = 1;
+SELECT id, name FROM cults WHERE id = 1;
+SELECT id, name FROM cults WHERE id = 2;
+SELECT id, name FROM cults WHERE id = 2;
+SELECT id, name FROM cults WHERE id = 2;
+# ...
+
+
Once the list of users has been returned, a separate query is run to find the cult of each user.
+You can see how this could quickly become a problem.
+
A common solution to this is to introduce a dataloader.
+This can be done with Juniper using the crate cksac/dataloader-rs, which has two types of dataloaders; cached and non-cached. This example will explore the non-cached option.
Once created, a dataloader has the functions .load() and .load_many().
+When called these return a Future.
+In the above example cult_loader.load(id: i32) returns Future<Cult>. If we had used cult_loader.load_may(Vec<i32>) it would have returned Future<Vec<Cult>>.
Dataloaders should be created per-request to avoid risk of bugs where one user is able to load cached/batched data from another user/ outside of its authenticated scope.
+Creating dataloaders within individual resolvers will prevent batching from occurring and will nullify the benefits of the dataloader.
GraphQL defines a special built-in top-level field called __schema. Querying
for this field allows one to introspect the schema
at runtime to see what queries and mutations the GraphQL server supports.
@@ -151,7 +163,7 @@ could execute the following query against Juniper:
}
}
-
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 schema.json. A complete representation of the schema can be
@@ -173,7 +185,7 @@ impl juniper::Context for Context {}
struct Query;
-#[juniper::object(
+#[juniper::graphql_object(
Context = Context,
)]
impl Query {
@@ -243,6 +255,14 @@ fn main() {
+
+
+
+
+
+
diff --git a/master/advanced/multiple_ops_per_request.html b/master/advanced/multiple_ops_per_request.html
index 951c0734..4fbd3f1e 100644
--- a/master/advanced/multiple_ops_per_request.html
+++ b/master/advanced/multiple_ops_per_request.html
@@ -1,9 +1,11 @@
-
+
Multiple operations per request - Juniper - GraphQL Server for Rust
+
+
@@ -30,9 +32,12 @@
-
+
-
+
@@ -72,7 +80,10 @@
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.
Some client libraries such as apollo-link-batch-http have added the ability to batch operations in a single HTTP request to save network round-trips and potentially increase performance. There are some tradeoffs that should be considered before batching requests.
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.
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 have to be mapped into GraphQL
interfaces.
Using Result-like enums can be a useful way of reporting e.g. validation
errors from a mutation:
-
# #[derive(juniper::GraphQLObject)] struct User { name: String }
-
+
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.
@@ -146,19 +158,19 @@ not make e.g. Result<T, E> into a GraphQL type, but you c
Result<User, String> into a GraphQL type.
Let's make a slightly more compact but generic implementation of the last
chapter:
Juniper supports the full GraphQL query language according to the
specification, including interfaces, unions, schema
introspection, and validations.
@@ -161,8 +173,8 @@ It does not, however, support the schema language.
non-null types by default. A field of type Vec<Episode> will be converted into
[Episode!]!. The corresponding Rust type for e.g. [Episode] would be
Option<Vec<Option<Episode>>>.
-
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.
Juniper supports the full GraphQL query language according to the
specification, including interfaces, unions, schema
introspection, and validations.
@@ -161,8 +175,8 @@ It does not, however, support the schema language.
non-null types by default. A field of type Vec<Episode> will be converted into
[Episode!]!. The corresponding Rust type for e.g. [Episode] would be
Option<Vec<Option<Episode>>>.
-
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.
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 Option<T>, Vec<T>, Box<T>,
@@ -198,13 +213,13 @@ types to a GraphQL schema. The most important one is the
resolvers, which you will use for the Query and Mutation roots.
use juniper::{FieldResult};
-# struct DatabasePool;
-# impl DatabasePool {
-# fn get_connection(&self) -> FieldResult<DatabasePool> { Ok(DatabasePool) }
-# fn find_human(&self, _id: &str) -> FieldResult<Human> { Err("")? }
-# fn insert_human(&self, _human: &NewHuman) -> FieldResult<Human> { Err("")? }
-# }
-
+struct DatabasePool;
+impl DatabasePool {
+ fn get_connection(&self) -> FieldResult<DatabasePool> { Ok(DatabasePool) }
+ fn find_human(&self, _id: &str) -> FieldResult<Human> { Err("")? }
+ fn insert_human(&self, _human: &NewHuman) -> FieldResult<Human> { Err("")? }
+}
+
#[derive(juniper::GraphQLEnum)]
enum Episode {
NewHope,
@@ -246,7 +261,7 @@ impl juniper::Context for Context {}
struct Query;
-#[juniper::object(
+#[juniper::graphql_object(
// Here we specify the context type for the object.
// We need to do this in every type that
// needs access to the context.
@@ -277,7 +292,7 @@ impl Query {
struct Mutation;
-#[juniper::object(
+#[juniper::graphql_object(
Context = Context,
)]
impl Mutation {
@@ -293,18 +308,18 @@ impl Mutation {
// Request queries can be executed against a RootNode.
type Schema = juniper::RootNode<'static, Query, Mutation>;
-# fn main() {
-# let _ = Schema::new(Query, Mutation{});
-# }
-
You can invoke juniper::execute directly to run a GraphQL query:
-
# // Only needed due to 2018 edition because the macro is not accessible.
-# #[macro_use] extern crate juniper;
-use juniper::{FieldResult, Variables, EmptyMutation};
+
// Only needed due to 2018 edition because the macro is not accessible.
+#[macro_use] extern crate juniper;
+use juniper::{FieldResult, Variables, EmptyMutation};
#[derive(juniper::GraphQLEnum, Clone, Copy)]
@@ -321,7 +336,7 @@ impl juniper::Context for Ctx {}
struct Query;
-#[juniper::object(
+#[juniper::graphql_object(
Context = Ctx,
)]
impl Query {
@@ -357,36 +372,36 @@ fn main() {
);
}
While any type in Rust can be exposed as a GraphQL object, the most common one
is a struct.
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 Complex fields
+attribute. The other way is described in the Complex fields
chapter.
#[derive(juniper::GraphQLObject)]
struct Person {
@@ -394,8 +409,8 @@ struct Person {
age: i32,
}
-# fn main() {}
-
+fn main() {}
+
This will create a GraphQL object type called Person, with two fields: name
of type String!, and age of type Int!. Because of Rust's type system,
everything is exported as non-null by default. If you need a nullable field, you
@@ -414,8 +429,8 @@ struct Person {
age: i32,
}
-# fn main() {}
-
+fn main() {}
+
Objects and fields without doc comments can instead set a description
via the graphql attribute. The following example is equivalent to the above:
Descriptions set via the graphql attribute take precedence over Rust
doc comments. This enables internal Rust documentation and external GraphQL
documentation to differ:
Because Person is a valid GraphQL type, you can have a Vec<Person> in a
struct and it'll be automatically converted into a list of non-nullable Person
objects.
By default, struct fields are converted from Rust's standard snake_case naming
convention into GraphQL's camelCase convention:
#[derive(juniper::GraphQLObject)]
@@ -487,8 +502,8 @@ struct Person {
last_name: String, // Exposed as lastName
}
-# fn main() {}
-
+fn main() {}
+
You can override the name by using the graphql attribute on individual struct
fields:
#[derive(juniper::GraphQLObject)]
@@ -499,9 +514,9 @@ struct Person {
website_url: Option<String>, // Now exposed as websiteURL in the schema
}
-# fn main() {}
-
The name, description, and deprecation arguments can of course be
combined. Some restrictions from the GraphQL spec still applies though; you can
only deprecate object fields and enum values.
By default all fields in a GraphQLObject are included in the generated GraphQL type. To prevent including a specific field, annotate the field with #[graphql(skip)]:
#[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
+ #[allow(dead_code)]
+ password_hash: String, // This cannot be queried or modified from GraphQL
}
-# fn main() {}
-
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 object procedural macro. This macro lets you define GraphQL object
@@ -543,7 +558,7 @@ struct Person {
age: i32,
}
-#[juniper::object]
+#[juniper::graphql_object]
impl Person {
fn name(&self) -> &str {
self.name.as_str()
@@ -562,8 +577,8 @@ impl Person {
}
}
-# fn main() { }
-
+fn main() { }
+
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:
#[derive(juniper::GraphQLObject)]
@@ -576,7 +591,7 @@ struct House {
inhabitants: Vec<Person>,
}
-#[juniper::object]
+#[juniper::graphql_object]
impl House {
// Creates the field inhabitantWithName(name), returning a nullable person
fn inhabitant_with_name(&self, name: String) -> Option<&Person> {
@@ -584,12 +599,12 @@ impl House {
}
}
-# fn main() {}
-
+fn main() {}
+
To access global data such as database connections or authentication
information, a context is used. To learn more about this, see the next
-chapter: Using contexts.
Like with the derive attribute, field names will be converted from snake_case
to camelCase. If you need to override the conversion, you can simply rename
the field. Also, the type name can be changed with an alias:
@@ -598,7 +613,7 @@ struct Person {
}
/// Doc comments are used as descriptions for GraphQL.
-#[juniper::object(
+#[juniper::graphql_object(
// With this attribtue you can change the public GraphQL name of the type.
name = "PersonObject",
// You can also specify a description here, which will overwrite
@@ -637,17 +652,17 @@ impl Person {
}
}
-# fn main() { }
-
-
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 context factory. How this is defined is
specific to the framework integration you're using, so check out the
-documentation for either the Iron or Rocket
+documentation for either the Iron or Rocket
integration.
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 HashMap:
-
# #![allow(dead_code)]
-# use std::collections::HashMap;
-
+
We would like a friends field on User that returns a list of User objects.
In order to write such a field though, the database must be queried.
To solve this, we mark the Database as a valid context type and assign it to
-the user object.
-
To gain access to the context, we need to specify an argument with the same
+the user object.
+
To gain access to the context, we need to specify an argument with the same
type as the specified Context for the type:
-
# use std::collections::HashMap;
-extern crate juniper;
+
use std::collections::HashMap;
+extern crate juniper;
// This struct represents our context.
struct Database {
@@ -728,7 +743,7 @@ struct User {
// Assign Database as the context type for User
-#[juniper::object(
+#[juniper::graphql_object(
Context = Database,
)]
impl User {
@@ -754,13 +769,13 @@ impl User {
}
}
-# fn main() { }
-
+fn main() { }
+
You only get an immutable reference to the context, so if you want to affect
change to the execution, you'll need to use interior
mutability
using e.g. RwLock or RefCell.
Rust
provides
two ways of dealing with errors: Result<T, E> for recoverable errors and
@@ -770,8 +785,8 @@ there.
For recoverable errors, Juniper works well with the built-in Result type, you
can use the ? operator or the try! macro and things will generally just work
as you expect them to:
FieldResult<T> is an alias for Result<T, FieldError>, which is the error
type all fields must return. By using the ? operator or try! macro, any type
that implements the Display trait - which are most of the error types out
@@ -856,11 +871,11 @@ following would be returned:
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:
Juniper converts all enum variants to uppercase, so the corresponding string
values for these variants are NEWHOPE, EMPIRE, and JEDI, respectively. If
you want to override this, you can use the graphql attribute, similar to how
-it works when defining objects:
Just like when defining objects, the type itself can be renamed and documented,
while individual enum variants can be renamed, documented, and deprecated:
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
@@ -968,13 +983,13 @@ little bit of boilerplate code, but on the other hand gives you full control
over which type is backing your interface.
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:
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:
#[derive(juniper::GraphQLObject)]
struct Human {
id: String,
home_planet: String,
@@ -1004,7 +1019,7 @@ impl Character for Droid {
fn as_droid(&self) -> Option<&Droid> { Some(&self) }
}
-juniper::graphql_interface!(<'a> &'a Character: () as "Character" where Scalar = <S> |&self| {
+juniper::graphql_interface!(<'a> &'a dyn Character: () as "Character" where Scalar = <S> |&self| {
field id() -> &str { self.id() }
instance_resolvers: |_| {
@@ -1015,19 +1030,19 @@ juniper::graphql_interface!(<'a> &'a Character: () as "Character&
}
});
-# fn main() {}
-
+fn main() {}
+
The instance_resolvers declaration lists all the implementors of the given
interface and how to resolve them.
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.
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:
-
# use std::collections::HashMap;
-#[derive(juniper::GraphQLObject)]
+
use std::collections::HashMap;
+#[derive(juniper::GraphQLObject)]
#[graphql(Context = Database)]
struct Human {
id: String,
@@ -1060,7 +1075,7 @@ 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| {
+juniper::graphql_interface!(<'a> &'a dyn Character: Database as "Character" where Scalar = <S> |&self| {
field id() -> &str { self.id() }
instance_resolvers: |&context| {
@@ -1069,14 +1084,14 @@ juniper::graphql_interface!(<'a> &'a Character: Database as "Char
}
});
-# fn main() {}
-
+fn main() {}
+
This removes the need of downcast methods, but still requires some repetition.
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.
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:
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.
Since any value going over the wire is eventually transformed into JSON, you're
-also limited in the data types you can use.
-
There are two ways to define custom scalars.
+also limited in the data types you can use.
+
There are two ways to define custom scalars.
For simple scalars that just wrap a primitive type, you can use the newtype pattern with
-a custom derive.
+a custom derive.
For more advanced use cases with custom validation, you can use
the graphql_scalar! macro.
For more complex situations where you also need custom parsing or validation,
you can use the graphql_scalar! macro.
Typically, you represent your custom scalars as strings.
The example below implements a custom scalar for a custom Date type.
-
Note: juniper already has built-in support for the chrono::DateTime type
-via chrono feature, which is enabled by default and should be used for this
+
Note: juniper already has built-in support for the chrono::DateTime type
+via chrono feature, which is enabled by default and should be used for this
purpose.
The example below is used just for illustration.
Note: the example assumes that the Date type implements
std::fmt::Display and std::str::FromStr.
-
# mod date {
-# pub struct Date;
-# impl std::str::FromStr for Date{
-# type Err = String; fn from_str(_value: &str) -> Result<Self, Self::Err> { unimplemented!() }
-# }
-# // And we define how to represent date as a string.
-# impl std::fmt::Display for Date {
-# fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result {
-# unimplemented!()
-# }
-# }
-# }
-
+
mod date {
+ pub struct Date;
+ impl std::str::FromStr for Date{
+ type Err = String; fn from_str(_value: &str) -> Result<Self, Self::Err> { unimplemented!() }
+ }
+ // And we define how to represent date as a string.
+ impl std::fmt::Display for Date {
+ fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ unimplemented!()
+ }
+ }
+}
+
use juniper::{Value, ParseScalarResult, ParseScalarValue};
use date::Date;
@@ -1303,8 +1319,9 @@ juniper::graphql_scalar!(Date where Scalar = <S> {
// Define how to parse a primitive type into your custom scalar.
from_input_value(v: &InputValue) -> Option<Date> {
- v.as_scalar_value::<String>()
- .and_then(|s| s.parse().ok())
+ v.as_scalar_value()
+ .and_then(|v| v.as_str())
+ .and_then(|s| s.parse().ok())
}
// Define how to parse a string value.
@@ -1313,19 +1330,19 @@ juniper::graphql_scalar!(Date where Scalar = <S> {
}
});
-# fn main() {}
-
#[derive(juniper::GraphQLObject)]
struct Human {
id: String,
@@ -1352,21 +1369,22 @@ 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(),
+#[juniper::graphql_union]
+impl<'a> GraphQLUnion for &'a dyn Character {
+ fn resolve(&self) {
+ match self {
+ Human => self.as_human(),
+ Droid => self.as_droid(),
+ }
}
-});
+}
-# fn main() {}
-
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.
@@ -1480,62 +1512,62 @@ and mutations of the schema, respectively.
other object in Juniper. The mutation object, however, is optional since schemas
can be read-only.
In Juniper, the RootNode type represents a schema. You usually don't have to
-create this object yourself: see the framework integrations for Iron
-and Rocket how schemas are created together with the handlers
+create this object yourself: see the framework integrations for Iron
+and Rocket how schemas are created together with the handlers
themselves.
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.
Warp 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 Hyper and works on
Rust's stable channel.
Rocket 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.
Iron 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
@@ -1588,7 +1620,7 @@ juniper_iron = "0.2.0"
Included in the source is a small
example
which sets up a basic GraphQL and GraphiQL handler.
If you want to access e.g. the source IP address of the request from a field
-resolver, you need to pass this data using Juniper's context feature.
-
# extern crate juniper;
-# extern crate juniper_iron;
-# extern crate iron;
-# use iron::prelude::*;
-use std::net::SocketAddr;
+resolver, you need to pass this data using Juniper's context feature.
+
Hyper is a is a fast HTTP implementation that many other Rust web frameworks
+fn main() {
+ let _graphql_endpoint = juniper_iron::GraphQLHandler::new(
+ context_factory,
+ Root,
+ juniper::EmptyMutation::<Context>::new(),
+ );
+}
+
Hyper 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.
Hyper is not a higher-level web framework and accordingly
@@ -1692,7 +1724,7 @@ juniper = "0.10"
juniper_hyper = "0.1.0"
Included in the source is a small example which sets up a basic GraphQL and GraphiQL handler.
GraphQL defines a special built-in top-level field called __schema. Querying
for this field allows one to introspect the schema
at runtime to see what queries and mutations the GraphQL server supports.
@@ -1723,7 +1756,7 @@ could execute the following query against Juniper:
}
}
-
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 schema.json. A complete representation of the schema can be
@@ -1745,7 +1778,7 @@ impl juniper::Context for Context {}
struct Query;
-#[juniper::object(
+#[juniper::graphql_object(
Context = Context,
)]
impl Query {
@@ -1772,28 +1805,28 @@ fn main() {
assert!(json_result.is_ok());
}
-
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 have to be mapped into GraphQL
interfaces.
Using Result-like enums can be a useful way of reporting e.g. validation
errors from a mutation:
-
# #[derive(juniper::GraphQLObject)] struct User { name: String }
-
+
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.
While this is an example of how you could use something other than a struct to
@@ -1822,7 +1855,7 @@ no hard rules on how to represent errors in GraphQL, but there are
comments
from one of the authors of GraphQL on how they intended "hard" field errors to
be used, and how to model expected errors.
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.
@@ -1830,21 +1863,21 @@ GraphQL, there are only two generic types: lists and non-nullables.
structs can be exposed - all type parameters must be bound. For example, you can
not make e.g. Result<T, E> into a GraphQL type, but you can make e.g.
Result<User, String> into a GraphQL type.
-
Let's make a slightly more compact but generic implementation of the last
+
Here, we've made a wrapper around Result and exposed some concrete
instantiations of Result<T, E> as distinct GraphQL objects. The reason we
needed the wrapper is of Rust's rules for when you can derive a trait - in this
@@ -1880,7 +1913,7 @@ sources.
Because we're using generics, we also need to specify a name for our
instantiated types. Even if Juniper could figure out the name,
MutationResult<User> wouldn't be a valid GraphQL type name.
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.
Some client libraries such as apollo-link-batch-http have added the ability to batch operations in a single HTTP request to save network round-trips and potentially increase performance. There are some tradeoffs that should be considered before batching requests.
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.
@@ -1934,6 +1967,166 @@ instantiated types. Even if Juniper could figure out the name,
}
]
+
A common issue with graphql servers is how the resolvers query their datasource.
+his issue results in a large number of unneccessary database queries or http requests.
+Say you were wanting to list a bunch of cults people were in
+
query {
+ persons {
+ id
+ name
+ cult {
+ id
+ name
+ }
+ }
+}
+
+
What would be executed by a SQL database would be:
+
SELECT id, name, cult_id FROM persons;
+SELECT id, name FROM cults WHERE id = 1;
+SELECT id, name FROM cults WHERE id = 1;
+SELECT id, name FROM cults WHERE id = 1;
+SELECT id, name FROM cults WHERE id = 1;
+SELECT id, name FROM cults WHERE id = 2;
+SELECT id, name FROM cults WHERE id = 2;
+SELECT id, name FROM cults WHERE id = 2;
+# ...
+
+
Once the list of users has been returned, a separate query is run to find the cult of each user.
+You can see how this could quickly become a problem.
+
A common solution to this is to introduce a dataloader.
+This can be done with Juniper using the crate cksac/dataloader-rs, which has two types of dataloaders; cached and non-cached. This example will explore the non-cached option.
Once created, a dataloader has the functions .load() and .load_many().
+When called these return a Future.
+In the above example cult_loader.load(id: i32) returns Future<Cult>. If we had used cult_loader.load_may(Vec<i32>) it would have returned Future<Vec<Cult>>.
Dataloaders should be created per-request to avoid risk of bugs where one user is able to load cached/batched data from another user/ outside of its authenticated scope.
+Creating dataloaders within individual resolvers will prevent batching from occurring and will nullify the benefits of the dataloader.
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 Option<T>, Vec<T>, Box<T>,
@@ -154,13 +166,13 @@ types to a GraphQL schema. The most important one is the
resolvers, which you will use for the Query and Mutation roots.
use juniper::{FieldResult};
-# struct DatabasePool;
-# impl DatabasePool {
-# fn get_connection(&self) -> FieldResult<DatabasePool> { Ok(DatabasePool) }
-# fn find_human(&self, _id: &str) -> FieldResult<Human> { Err("")? }
-# fn insert_human(&self, _human: &NewHuman) -> FieldResult<Human> { Err("")? }
-# }
-
+struct DatabasePool;
+impl DatabasePool {
+ fn get_connection(&self) -> FieldResult<DatabasePool> { Ok(DatabasePool) }
+ fn find_human(&self, _id: &str) -> FieldResult<Human> { Err("")? }
+ fn insert_human(&self, _human: &NewHuman) -> FieldResult<Human> { Err("")? }
+}
+
#[derive(juniper::GraphQLEnum)]
enum Episode {
NewHope,
@@ -202,7 +214,7 @@ impl juniper::Context for Context {}
struct Query;
-#[juniper::object(
+#[juniper::graphql_object(
// Here we specify the context type for the object.
// We need to do this in every type that
// needs access to the context.
@@ -233,7 +245,7 @@ impl Query {
struct Mutation;
-#[juniper::object(
+#[juniper::graphql_object(
Context = Context,
)]
impl Mutation {
@@ -249,18 +261,18 @@ impl Mutation {
// Request queries can be executed against a RootNode.
type Schema = juniper::RootNode<'static, Query, Mutation>;
-# fn main() {
-# let _ = Schema::new(Query, Mutation{});
-# }
-
You can invoke juniper::execute directly to run a GraphQL query:
-
# // Only needed due to 2018 edition because the macro is not accessible.
-# #[macro_use] extern crate juniper;
-use juniper::{FieldResult, Variables, EmptyMutation};
+
// Only needed due to 2018 edition because the macro is not accessible.
+#[macro_use] extern crate juniper;
+use juniper::{FieldResult, Variables, EmptyMutation};
#[derive(juniper::GraphQLEnum, Clone, Copy)]
@@ -277,7 +289,7 @@ impl juniper::Context for Ctx {}
struct Query;
-#[juniper::object(
+#[juniper::graphql_object(
Context = Ctx,
)]
impl Query {
@@ -356,6 +368,14 @@ fn main() {
+
+
+
+
+
+
diff --git a/master/schema/schemas_and_mutations.html b/master/schema/schemas_and_mutations.html
index dc8b75b7..c5298a45 100644
--- a/master/schema/schemas_and_mutations.html
+++ b/master/schema/schemas_and_mutations.html
@@ -1,9 +1,11 @@
-
+
Schemas and mutations - Juniper - GraphQL Server for Rust
+
+
@@ -30,9 +32,12 @@
-
+
-
+
@@ -72,7 +80,10 @@
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.
@@ -150,40 +162,40 @@ themselves.
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.
Mutations are also just GraphQL objects. Each mutation is a single field that
usually performs some mutating side-effect, such as updating a database.
-
# use juniper::FieldResult;
-# #[derive(juniper::GraphQLObject)] struct User { name: String }
-struct Mutations;
+
use juniper::FieldResult;
+#[derive(juniper::GraphQLObject)] struct User { name: String }
+struct Mutations;
-#[juniper::object]
+#[juniper::graphql_object]
impl Mutations {
fn signUpUser(name: String, email: String) -> FieldResult<User> {
// Validate inputs and save user in database...
-# unimplemented!()
- }
+unimplemented!()
+ }
}
-# fn main() { }
-
+fn main() { }
+
@@ -227,6 +239,14 @@ impl Mutations {
+
+
+
+
+
+
diff --git a/master/searchindex.js b/master/searchindex.js
index 80b693f6..0aebf880 100644
--- a/master/searchindex.js
+++ b/master/searchindex.js
@@ -1 +1 @@
-window.search = {"doc_urls":["index.html#juniper","index.html#features","index.html#integrations","index.html#data-types","index.html#web-frameworks","index.html#api-stability","quickstart.html#quickstart","quickstart.html#installation","quickstart.html#schema-example","quickstart.html#executor","types/index.html#type-system","types/objects/defining_objects.html#defining-objects","types/objects/defining_objects.html#relationships","types/objects/defining_objects.html#renaming-fields","types/objects/defining_objects.html#deprecating-fields","types/objects/defining_objects.html#skipping-fields","types/objects/complex_fields.html#complex-fields","types/objects/complex_fields.html#description-renaming-and-deprecation","types/objects/complex_fields.html#customizing-arguments","types/objects/complex_fields.html#more-features","types/objects/using_contexts.html#using-contexts","types/objects/error_handling.html#error-handling","types/objects/error_handling.html#structured-errors","types/other-index.html#other-types","types/enums.html#enums","types/enums.html#documentation-and-deprecation","types/interfaces.html#interfaces","types/interfaces.html#traits","types/interfaces.html#downcasting-via-accessor-methods","types/interfaces.html#using-an-extra-database-lookup","types/interfaces.html#placeholder-objects","types/interfaces.html#enums","types/input_objects.html#input-objects","types/input_objects.html#documentation-and-renaming","types/scalars.html#scalars","types/scalars.html#built-in-scalars","types/scalars.html#newtype-pattern","types/scalars.html#custom-scalars","types/unions.html#unions","types/unions.html#traits","types/unions.html#downcasting-via-accessor-methods","types/unions.html#using-an-extra-database-lookup","types/unions.html#placeholder-objects","types/unions.html#enums","schema/schemas_and_mutations.html#schemas","schema/schemas_and_mutations.html#the-query-root","schema/schemas_and_mutations.html#mutations","servers/index.html#adding-a-server","servers/official.html#official-server-integrations","servers/warp.html#integrating-with-warp","servers/rocket.html#integrating-with-rocket","servers/iron.html#integrating-with-iron","servers/iron.html#basic-integration","servers/iron.html#accessing-data-from-the-request","servers/hyper.html#integrating-with-hyper","servers/third-party.html#third-party-integrations","advanced/index.html#advanced-topics","advanced/introspection.html#introspection","advanced/introspection.html#schema-introspection-output-as-json","advanced/non_struct_objects.html#non-struct-objects","advanced/objects_and_generics.html#objects-and-generics","advanced/multiple_ops_per_request.html#multiple-operations-per-request"],"index":{"documentStore":{"docInfo":{"0":{"body":79,"breadcrumbs":1,"title":1},"1":{"body":38,"breadcrumbs":1,"title":1},"10":{"body":42,"breadcrumbs":2,"title":2},"11":{"body":213,"breadcrumbs":2,"title":2},"12":{"body":76,"breadcrumbs":1,"title":1},"13":{"body":51,"breadcrumbs":2,"title":2},"14":{"body":42,"breadcrumbs":2,"title":2},"15":{"body":30,"breadcrumbs":2,"title":2},"16":{"body":132,"breadcrumbs":4,"title":2},"17":{"body":89,"breadcrumbs":5,"title":3},"18":{"body":63,"breadcrumbs":4,"title":2},"19":{"body":29,"breadcrumbs":4,"title":2},"2":{"body":0,"breadcrumbs":1,"title":1},"20":{"body":194,"breadcrumbs":4,"title":2},"21":{"body":216,"breadcrumbs":4,"title":2},"22":{"body":70,"breadcrumbs":4,"title":2},"23":{"body":20,"breadcrumbs":1,"title":1},"24":{"body":59,"breadcrumbs":2,"title":1},"25":{"body":36,"breadcrumbs":3,"title":2},"26":{"body":49,"breadcrumbs":2,"title":1},"27":{"body":24,"breadcrumbs":2,"title":1},"28":{"body":111,"breadcrumbs":5,"title":4},"29":{"body":99,"breadcrumbs":5,"title":4},"3":{"body":19,"breadcrumbs":2,"title":2},"30":{"body":72,"breadcrumbs":3,"title":2},"31":{"body":76,"breadcrumbs":2,"title":1},"32":{"body":50,"breadcrumbs":3,"title":2},"33":{"body":46,"breadcrumbs":3,"title":2},"34":{"body":59,"breadcrumbs":2,"title":1},"35":{"body":44,"breadcrumbs":3,"title":2},"36":{"body":67,"breadcrumbs":3,"title":2},"37":{"body":124,"breadcrumbs":3,"title":2},"38":{"body":39,"breadcrumbs":2,"title":1},"39":{"body":0,"breadcrumbs":2,"title":1},"4":{"body":4,"breadcrumbs":2,"title":2},"40":{"body":73,"breadcrumbs":5,"title":4},"41":{"body":69,"breadcrumbs":5,"title":4},"42":{"body":48,"breadcrumbs":3,"title":2},"43":{"body":43,"breadcrumbs":2,"title":1},"44":{"body":74,"breadcrumbs":1,"title":1},"45":{"body":36,"breadcrumbs":2,"title":2},"46":{"body":41,"breadcrumbs":1,"title":1},"47":{"body":35,"breadcrumbs":2,"title":2},"48":{"body":14,"breadcrumbs":5,"title":3},"49":{"body":50,"breadcrumbs":7,"title":2},"5":{"body":7,"breadcrumbs":2,"title":2},"50":{"body":49,"breadcrumbs":7,"title":2},"51":{"body":46,"breadcrumbs":7,"title":2},"52":{"body":95,"breadcrumbs":7,"title":2},"53":{"body":66,"breadcrumbs":8,"title":3},"54":{"body":77,"breadcrumbs":7,"title":2},"55":{"body":14,"breadcrumbs":5,"title":3},"56":{"body":16,"breadcrumbs":2,"title":2},"57":{"body":43,"breadcrumbs":3,"title":1},"58":{"body":99,"breadcrumbs":6,"title":4},"59":{"body":125,"breadcrumbs":5,"title":3},"6":{"body":6,"breadcrumbs":1,"title":1},"60":{"body":152,"breadcrumbs":4,"title":2},"61":{"body":138,"breadcrumbs":6,"title":4},"7":{"body":5,"breadcrumbs":1,"title":1},"8":{"body":283,"breadcrumbs":2,"title":2},"9":{"body":91,"breadcrumbs":1,"title":1}},"docs":{"0":{"body":"Juniper is a GraphQL server library for Rust. Build type-safe and fast API servers with minimal boilerplate and configuration. GraphQL is a data query language developed by Facebook intended to serve mobile and web application frontends. Juniper 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. 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 Hyper , Iron , Rocket , and Warp frameworks, including embedded Graphiql for easy debugging. Cargo crate API Reference","breadcrumbs":"Juniper","id":"0","title":"Juniper"},"1":{"body":"Juniper supports the full GraphQL query language according to the specification , including interfaces, unions, schema introspection, and validations. It does not, however, support the schema language. As an exception to other GraphQL libraries for other languages, Juniper builds non-null types by default. A field of type Vec will be converted into [Episode!]! . The corresponding Rust type for e.g. [Episode] would be Option>> .","breadcrumbs":"Features","id":"1","title":"Features"},"10":{"body":"Most of the work in working with juniper consists of mapping the GraphQL type system to the Rust types your application uses. Juniper provides some convenient abstractions that try to make this process as painless as possible. Find out more in the individual chapters below. Defining objects Complex fields Using contexts Error handling Other types Enums Interfaces Input objects Scalars Unions","breadcrumbs":"Type System","id":"10","title":"Type System"},"11":{"body":"While any type in Rust can be exposed as a GraphQL object, the most common one is a struct. 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 Complex fields chapter. #[derive(juniper::GraphQLObject)]\nstruct Person { name: String, age: i32,\n} # fn main() {} This will create a GraphQL object type called Person , with two fields: name of type String! , and age of type Int! . Because of Rust's type system, everything is exported as non-null by default. If you need a nullable field, you can use Option . 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: !FILENAME GraphQL descriptions via Rust doc comments #[derive(juniper::GraphQLObject)]\n/// Information about a person\nstruct Person { /// The person's full name, including both first and last names name: String, /// The person's age in years, rounded down age: i32,\n} # fn main() {} Objects and fields without doc comments can instead set a description via the graphql attribute. The following example is equivalent to the above: !FILENAME GraphQL descriptions via attribute #[derive(juniper::GraphQLObject)]\n#[graphql(description=\"Information about a person\")]\nstruct 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,\n} # fn main() {} Descriptions set via the graphql attribute take precedence over Rust doc comments. This enables internal Rust documentation and external GraphQL documentation to differ: #[derive(juniper::GraphQLObject)]\n#[graphql(description=\"This description shows up in GraphQL\")]\n/// This description shows up in RustDoc\nstruct 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,\n} # fn main() {}","breadcrumbs":"Defining objects","id":"11","title":"Defining objects"},"12":{"body":"You can only use the custom derive attribute under these circumstances: The annotated type is a struct , Every struct field is either A primitive type ( i32 , f64 , bool , String , juniper::ID ), or A valid custom GraphQL type, e.g. another struct marked with this attribute, or A container/reference containing any of the above, e.g. Vec , Box , Option Let's see what that means for building relationships between objects: #[derive(juniper::GraphQLObject)]\nstruct Person { name: String, age: i32,\n} #[derive(juniper::GraphQLObject)]\nstruct House { address: Option, // Converted into String (nullable) inhabitants: Vec, // Converted into [Person!]!\n} # fn main() {} Because Person is a valid GraphQL type, you can have a Vec in a struct and it'll be automatically converted into a list of non-nullable Person objects.","breadcrumbs":"Relationships","id":"12","title":"Relationships"},"13":{"body":"By default, struct fields are converted from Rust's standard snake_case naming convention into GraphQL's camelCase convention: #[derive(juniper::GraphQLObject)]\nstruct Person { first_name: String, // Would be exposed as firstName in the GraphQL schema last_name: String, // Exposed as lastName\n} # fn main() {} You can override the name by using the graphql attribute on individual struct fields: #[derive(juniper::GraphQLObject)]\nstruct Person { name: String, age: i32, #[graphql(name=\"websiteURL\")] website_url: Option, // Now exposed as websiteURL in the schema\n} # fn main() {}","breadcrumbs":"Renaming fields","id":"13","title":"Renaming fields"},"14":{"body":"To deprecate a field, you specify a deprecation reason using the graphql attribute: #[derive(juniper::GraphQLObject)]\nstruct Person { name: String, age: i32, #[graphql(deprecated = \"Please use the name field instead\")] first_name: String,\n} # fn main() {} The name , description , and deprecation arguments can of course be combined. Some restrictions from the GraphQL spec still applies though; you can only deprecate object fields and enum values.","breadcrumbs":"Deprecating fields","id":"14","title":"Deprecating fields"},"15":{"body":"By default all fields in a GraphQLObject are included in the generated GraphQL type. To prevent including a specific field, annotate the field with #[graphql(skip)] : #[derive(juniper::GraphQLObject)]\nstruct Person { name: String, age: i32, #[graphql(skip)] # #[allow(dead_code)] password_hash: String, // This cannot be queried or modified from GraphQL\n} # fn main() {}","breadcrumbs":"Skipping fields","id":"15","title":"Skipping fields"},"16":{"body":"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 object procedural macro. This macro lets you define GraphQL object fields in a Rust impl block for a type. Continuing with the example from the last chapter, this is how you would define Person using the macro: struct Person { name: String, age: i32,\n} #[juniper::object]\nimpl Person { fn name(&self) -> &str { self.name.as_str() } fn age(&self) -> i32 { self.age }\n} // Note that this syntax generates an implementation of the GraphQLType trait,\n// the base impl of your struct can still be written like usual:\nimpl Person { pub fn hidden_from_graphql(&self) { // [...] }\n} # fn main() { } 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: #[derive(juniper::GraphQLObject)]\nstruct Person { name: String, age: i32,\n} struct House { inhabitants: Vec,\n} #[juniper::object]\nimpl House { // Creates the field inhabitantWithName(name), returning a nullable person fn inhabitant_with_name(&self, name: String) -> Option<&Person> { self.inhabitants.iter().find(|p| p.name == name) }\n} # fn main() {} To access global data such as database connections or authentication information, a context is used. To learn more about this, see the next chapter: Using contexts .","breadcrumbs":"Defining objects » Complex fields","id":"16","title":"Complex fields"},"17":{"body":"Like with the derive attribute, field names will be converted from snake_case to camelCase . If you need to override the conversion, you can simply rename the field. Also, the type name can be changed with an alias: struct Person {\n} /// Doc comments are used as descriptions for GraphQL.\n#[juniper::object( // With this attribtue you can change the public GraphQL name of the type. name = \"PersonObject\", // You can also specify a description here, which will overwrite // a doc comment description. description = \"...\",\n)]\nimpl Person { /// A doc comment on the field will also be used for GraphQL. #[graphql( // Or provide a description here. description = \"...\", )] fn doc_comment(&self) -> &str { \"\" } // Fields can also be renamed if required. #[graphql( name = \"myCustomFieldName\", )] fn renamed_field() -> bool { true } // Deprecations also work as you'd expect. // Both the standard Rust syntax and a custom attribute is accepted. #[deprecated(note = \"...\")] fn deprecated_standard() -> bool { false } #[graphql(deprecated = \"...\")] fn deprecated_graphql() -> bool { true }\n} # fn main() { }","breadcrumbs":"Defining objects » Description, renaming, and deprecation","id":"17","title":"Description, renaming, and deprecation"},"18":{"body":"Method field arguments can also be customized. They can have custom descriptions and default values. Note : The syntax for this is currently a little awkward. This will become better once the Rust RFC 2565 is implemented. struct Person {} #[juniper::object]\nimpl Person { #[graphql( arguments( arg1( // Set a default value which will be injected if not present. // The default can be any valid Rust expression, including a function call, etc. default = true, // Set a description. description = \"The first argument...\" ), arg2( default = 0, ) ) )] fn field1(&self, arg1: bool, arg2: i32) -> String { format!(\"{} {}\", arg1, arg2) }\n} # fn main() { }","breadcrumbs":"Defining objects » Customizing arguments","id":"18","title":"Customizing arguments"},"19":{"body":"GraphQL fields expose more features than Rust's standard method syntax gives us: Per-field description and deprecation messages Per-argument default values Per-argument descriptions These, and more features, are described more thorougly in the reference documentation .","breadcrumbs":"Defining objects » More features","id":"19","title":"More features"},"2":{"body":"","breadcrumbs":"Integrations","id":"2","title":"Integrations"},"20":{"body":"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 context factory . How this is defined is specific to the framework integration you're using, so check out the documentation for either the Iron or Rocket integration. 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 HashMap : # #![allow(dead_code)]\n# use std::collections::HashMap; struct Database { users: HashMap,\n} struct User { id: i32, name: String, friend_ids: Vec,\n} # fn main() { } We would like a friends field on User that returns a list of User objects. In order to write such a field though, the database must be queried. To solve this, we mark the Database as a valid context type and assign it to the user object. To gain access to the context, we need to specify an argument with the same type as the specified Context for the type: # use std::collections::HashMap;\nextern crate juniper; // This struct represents our context.\nstruct Database { users: HashMap,\n} // Mark the Database as a valid context type for Juniper\nimpl juniper::Context for Database {} struct User { id: i32, name: String, friend_ids: Vec,\n} // Assign Database as the context type for User\n#[juniper::object( Context = Database,\n)]\nimpl User { // 3. Inject the context by specifying an argument // with the context type. // Note: // - the type must be a reference // - the name of the argument SHOULD be context fn friends(&self, context: &Database) -> Vec<&User> { // 5. Use the database to lookup users self.friend_ids.iter() .map(|id| context.users.get(id).expect(\"Could not find user with ID\")) .collect() } fn name(&self) -> &str { self.name.as_str() } fn id(&self) -> i32 { self.id }\n} # fn main() { } You only get an immutable reference to the context, so if you want to affect change to the execution, you'll need to use interior mutability using e.g. RwLock or RefCell .","breadcrumbs":"Defining objects » Using contexts","id":"20","title":"Using contexts"},"21":{"body":"Rust provides two ways of dealing with errors: Result for recoverable errors and panic! for unrecoverable errors. Juniper does not do anything about panicking; it will bubble up to the surrounding framework and hopefully be dealt with there. For recoverable errors, Juniper works well with the built-in Result type, you can use the ? operator or the try! macro and things will generally just work as you expect them to: # extern crate juniper;\nuse std::{ str, path::PathBuf, fs::{File}, io::{Read},\n};\nuse juniper::FieldResult; struct Example { filename: PathBuf,\n} #[juniper::object]\nimpl Example { fn contents() -> FieldResult { let mut file = File::open(&self.filename)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; Ok(contents) } fn foo() -> FieldResult