Dgraph creates a GraphQL API from nothing more than GraphQL types. To customize the behavior of your schema, you can implement custom resolvers.
@custom directive@custom directive and thus tell Dgraph
where to apply custom logic.
@custom directive is used to define custom queries, mutations and fields.
In all cases, the result type (of the query, mutation, or field) can be either:
@remote directive.url where the custom logic is called. This can include a path and
parameters that depend on query/mutation arguments or other fields.method to use in the call. For example, when calling a REST
endpoint with GET, POST, etc.body definition that can be used to construct a HTTP body from arguments
or fields.forwardHeaders to take from the incoming request and add to the
outgoing HTTP call. Used, for example, if the incoming request contains an
auth token that must be passed to the custom logic.secretHeaders to take from the Dgraph.Secret defined in the
schema file and add to the outgoing HTTP call. Used, for example, for a server
side API key and other static value that must be passed to the custom logic.graphql query/mutation to call if the custom logic is a GraphQL server
and whether to introspect or not (skipIntrospection) the remote GraphQL
endpoint.mode which is used for resolving fields by calling an external GraphQL
query/mutation. It can either be BATCH or SINGLE.introspectionHeaders to take from the Dgraph.Secret
object defined in the schema file. They’re added to the
introspection requests sent to the endpoint.@remote types. For custom fields the type can be object
types or scalar types.
The method can be any of the HTTP methods: GET, POST, PUT, PATCH, or
DELETE, and forwardHeaders is a list of headers that should be passed from
the incoming request to the outgoing HTTP custom request. Let’s look at each of
the other http arguments in detail.
secretHeaders
and introspectionHeaders while defining the custom directive for a
field/query.
Github-Api-Token would be sent as a header with
value long-token for the introspection request. For the actual /graphql
request, the Authorization header would be sent with the value long-token.
Authorization:Github-Api-Token syntax tells us to use the value for
Github-Api-Token from Dgraph.Secret and forward it to the custom API with
the header key as Authorization.https://my.api.com/person/auth123/posts?limit=10.
When using custom logic on fields, the URL can draw from other fields in the
type. For example:
username or
authorID in the preceding examples, must be marked as non-nullable (have !
in their type); whereas, those used in parameters, such as numToFetch, can
be nullable._ aren’t yet supported.url allows
specifying a url pattern to use in resolving the custom request, Dgraph allows a
body pattern that’s used to build HTTP request bodies.
For example, this body can be structured JSON that relates a mutation’s
arguments to the JSON structure required by the remote endpoint.
newMovie(title: "...", desc: "...", dir: "dir123", imdb: "tt0120316") is
transformed into a POST request to http://myapi.com/movies with a JSON body
of:
url and body templates can be used together in a single custom definition.
For both url and body templates, any non-null arguments or fields must be
present to evaluate the custom logic. And the following rules are applied when
building the request from the template for nullable arguments or fields.
null is
inserted, while in a url nothing is added. For example, if the desc argument
above is null then { ..., storyLine: null, ...} is constructed for the body.
Whereas, in a URL pattern like https://a.b.c/endpoint?arg=$gqlArg, if
gqlArg is present, but null, the generated URL is
https://a.b.c/endpoint?arg=.storyLine if the desc
argument is missing, and in https://a.b.c/endpoint?arg=$gqlArg the result
would be https://a.b.c/endpoint if gqlArg weren’t present in the request
arguments.graphql argument to specify which
query/mutation on the remote server to call. The syntax includes if the call is
a query or mutation, the arguments, and what query/mutation to use on the remote
endpoint.
For example, you can pass arguments to queries onward as arguments to remote
GraphQL endpoints:
graphql argument. From the results of
introspection, it tries to match up arguments and input and object types to
ensure that the calls to and expected responses from the remote GraphQL make
sense.
If that introspection isn’t possible, set skipIntrospection: true in the
custom definition and Dgraph won’t perform GraphQL schema introspection for this
custom definition.
@remote directive isn’t stored in Dgraph. This
allows your Dgraph GraphQL instance to serve an API that includes both data
stored locally and data stored or generated elsewhere. You can also use custom
fields, for example, to join data from disparate datasets.
Remote types can only be returned by custom resolvers and Dgraph won’t generate
any search or CRUD operations for remote types.
The schema definition used to define your Dgraph GraphQL API must include
definitions of all the types used. If a custom logic call returns a type not
stored in Dgraph, then that type must be added to the Dgraph schema with the
@remote directive.
For example, your API might use custom logic to integrate with GitHub, using
either https://api.github.com or the GitHub GraphQL API
https://api.github.com/graphql and calling the user query. Either way, your
GraphQL schema needs to include the type you expect back from that remote call.
That could be linking a User as stored in your Dgraph instance with the
Repository data from GitHub. With @remote types, that’s as simple as adding
the type and custom call to your schema.
@remote directive, in a GraphQL schema you can also
use the @remoteResponse directive. You can define the @remoteResponse
directive on the fields of a @remote type to map the JSON key response of a
custom query to a GraphQL field.
For example, in the given GraphQL schema there’s a defined custom DQL query,
whose JSON response contains the results of the groupby clause in the
@groupby key. By using the @remoteResponse directive you’ll map the
groupby field in GroupUserMapQ type to the @groupby key in the JSON
response:
@custom DQL query:
getCustomPost query into a HTTP request to
https://my.api.com/post/$id and expects a single JSON object with fields id,
title, datePublished and author as result. Any additional fields are
ignored, while if non-nullable fields (like id and title) are missing,
GraphQL error propagation is triggered.
For getPosts, Dgraph expects the HTTP call to
https://my.api.com/person/$authorID/posts?limit=$numToFetch to return a JSON
array of JSON objects, with each object matching the Post type as described
above.
If the custom resolvers are GraphQL calls, like:
post to return a valid GraphQL result
like { "data": { "post": {...} } } and will use the JSON object that is the
value of post as the data resolved by the request.
Similarly, Dgraph expects postByAuthor to return data like
{ "data": { "postByAuthor": [ {...}, ... ] } } and will use the array value of
postByAuthor to build its array of posts result.
errors array and sent back to the user in the JSON
response.
When a field returns an error while resolving a custom HTTP endpoint, the
field’s value becomes null and the error is added to the errors JSON array.
The rest of the fields are still resolved as required by the request.
For example, a query from a custom HTTP endpoint will return an error in the
following format:
User type
defined as:
username and then using the result
to resolve the custom field posts (which relies on username). For a request
like:
username so it can run the custom
field posts, even though username isn’t part of the original query. So
Dgraph retrieves enough data to satisfy the custom request, even if that
involves data that isn’t asked for in the query.
There are currently a few limitations on custom fields:
ID or @id field@custom directive in the same manner as for custom
queries and mutations), and@custom and @remote - in the current version you
can’t have @custom inside an @remote type once we add this, you’ll be able
to extend remote types with custom fields, andTwitterUser and set up a custom
query:
https://api.twitter.com/1.1/users/show.json?screen_name=dgraphlabs, attach
header Authorization from the incoming GraphQL request to the outgoing HTTP,
and make the call and return a GraphQL result.
The result JSON of the actual HTTP call will contain the whole object from the
REST endpoint (you can see how much is in the Twitter user object
here).
But, the GraphQL query only asked for some of that, so Dgraph filters out any
returned values that weren’t asked for in the GraphQL query and builds a valid
GraphQL response to the query and returns GraphQL.
Accept-Encoding to gzip, etc.
addPost mutation from those types, but we want to do
something extra. We don’t want the author field to come in with the mutation,
that should get filled in from the JWT of the logged in user. Also, the
datePublished shouldn’t be in the input; it should be set as the current time
at point of mutation. Maybe we also have some community guidelines about what
might constitute an offensive title or text in a post. Maybe users can only
post if they have enough community credit.
We’ll need custom code to do all that, so we can write a custom function that
takes in only the title and text of the new post. Internally, it can check that
the title and text satisfy the guidelines and that this user has enough credit
to make a post. If those checks pass, it then builds a full post object by
adding the current time as the datePublished and adding the author from the
JWT information it gets from the forward header. It can then call the addPost
mutation constructed by Dgraph to add the post into Dgraph and returns the
resulting post as its GraphQL output.
So as well as the types above, we need a custom mutation:
@remote Directive@auth Directive