GraphQL on Existing Dgraph - Graphql

How to use GraphQL on an existing Dgraph instance.

If you have an existing Dgraph instance and want to also expose GraphQL, you need to add a GraphQL schema that maps to your Dgraph schema. You don’t need to expose your entire Dgraph schema as GraphQL, but do note that adding a GraphQL schema can alter the Dgraph schema.

Dgraph also allows type and edge names that aren’t valid in GraphQL, so, often, you’ll need to expose valid GraphQL names. Dgraph admits special characters and even different languages (see here), while the GraphQL Spec requires that type and field (predicate) names are generated from /[_A-Za-z][_0-9A-Za-z]*/.

Mapping GraphQL to a Dgraph schema

By default, Dgraph generates a new predicate for each field in a GraphQL type. The name of the generated predicate is composed of the type name followed by a dot . and ending with the field name. Therefore, two different types with fields of the same name will turn out to be different Dgraph predicates and can have different indexes. For example, the types:

type Person {
    name: String @search(by: [hash])
    age: Int
}
type Movie {
    name: String @search(by: [term])
}

generate a Dgraph schema like:

type Person {
    Person.name
    Person.age
}
type Movie {
    Movie.name
}
Person.name: string @index(hash) .
Person.age: int .
Movie.name: string @index(term) .

This behavior can be customized with the @dgraph directive.

  • type T @dgraph(type: "DgraphType") controls what Dgraph type is used for a GraphQL type.
  • field: SomeType @dgraph(pred: "DgraphPredicate") controls what Dgraph predicate is mapped to a GraphQL field.

For example, if you have existing types that don’t match GraphQL requirements, you can create a schema like the following.

type Person @dgraph(type: "Human-Person") {
    name: String @search(by: [hash]) @dgraph(pred: "name")
    age: Int
}
type Movie @dgraph(type: "film") {
    name: String @search(by: [term]) @dgraph(pred: "film.name")
}

Which maps to the Dgraph schema:

type Human-Person {
    name
    Person.age
}
type film {
    film.name
}
name string @index(hash) .
Person.age: int .
film.name string @index(term) .

You might also have the situation where you have used name for both movie names and people’s names. In this case you can map fields in two different GraphQL types to the one Dgraph predicate.

type Person {
    name: String @dgraph(pred: "name")
    ...
}
type Movie {
    name: String @dgraph(pred: "name")
    ...
}

Note: the current behavior requires that when two fields are mapped to the same Dgraph predicate both should have the same @search directive. This is likely to change in a future release where the underlying Dgraph indexes will be the union of the @search directives, while the generated GraphQL API will expose only the search given for the particular field. Allowing, for example, dgraph predicate name to have term and hash indexes, but exposing only term search for GraphQL movies and hash search for GraphQL people.

Roadmap

Be careful with mapping to an existing Dgraph instance. Updating the GraphQL schema updates the underlying Dgraph schema. We understand that exposing a GraphQL API on an existing Dgraph instance is a delicate process and we plan on adding multiple checks to ensure the validity of schema changes to avoid issues caused by detectable mistakes.

Future features are likely to include:

  • Generating a first pass GraphQL schema from an existing dgraph schema.
  • A way to show what schema diff will happen when you apply a new GraphQL schema.
  • Better handling of @dgraph with @search

We look forward to you letting us know what features you’d like, so please join us on discuss or GitHub.


This is a companion discussion topic for the original entry at https://dgraph.io/docs/graphql/dgraph/

Is it true that using a custom predicate like this, will require also custom queries?

It seems that the automatic GraphQL queries (such as queryPerson) will not find any fields as they expect all fields in this format: Person.name

Is there anyway to use the simple autogenerated GraphQL queries when explicitly setting the dgraph pred, like above ?

Can you share more details on what steps you followed.
I was able to query the name predicate of Person using queryPerson where name is a custom dgraph predicate.
GraphQL queries don’t take input of fields as Person.name . The mapping from name to Person.name or any other custom predicate (as in this case) is done internally.

This is what i hoped and expected… but here is what happens:

Schema on the left, ratel showing reults top right, insomnia query succeeding but empty bottom right.

Running the custom query that I defined gets results but not the generic autogenerated query…

 "data": {
    "queryAllLocations": [
      {
        "name": "Lisbon"
      },
      {
        "name": "Berlin"
      },
     ....etc....
    ]
  },

Here are the queries from the GetGQLSchema/generatedSchema :

 queryAllLocations: [Location] @custom(dql: \"query {\
  queryAllLocations(func: eq(dgraph.type, \\\"Location\\\")) {\
    id: uid\
    name\
    range\
    created\
    modified\
  }\
}\")
  getLocation(id: ID!): Location
  queryLocation(filter: LocationFilter, order: LocationOrder, first: Int, offset: Int): [Location]

EDIT: Turns out this was an auth problem - if i disable the query auth rule on Location then the standard query returns data… however this was rather unexpected as the query appeared to run fine, and returned an empty array - with no mention of an auth restriction anywhere. Whereas if the auth token is malformed then it returned an error.

EDIT-FOUND-IT:

in the # Dgraph.Authorization declaration at the bottom of my GQL schema in the namespace section I had a trailing space before the closing quote - so the $isAuthenticated variable was not properly extracted from the JWT (oh bummer, such a tiny easy to overlook syntax detail !! )

1 Like

I am glad that it worked.

It returned an empty array because the user with wrong credentials is not expected to see anything in query result. This has been designed so that a user with malicious intent would not get to know any details about the authorization format through error response.