Skip to content

Schema Conventions

Michael Compton edited this page Feb 23, 2019 · 6 revisions

GraphSchema uses some simple conventions to work out what to do by default.

This description is based on the example here, which contains most of what GraphSchema can accept.

In general, you write a schema with the types you want, and the mutations and queries you want the endpoint to expose. GraphSchema then interprets that to work out how to handle any particular query.

Types

  • Valid GraphQL types, enums, references, lists and nullable/not-null should be ok.
  • GraphQL scalar types int, float, string and boolean are accepted
  • GraphSchema also accepts a DateTime scalar type (maps to dgraph datetime type)
  • A type can have at most one ID field, which can't appear on the corresponding input type. (at the moment this maps to Dgraph UID, but soon GraphSchema will allow ID's to be input and GraphSchema will ensure uniqueness: e.g. user names)

E.G.

type Post {
  id: ID!
  title: String
  text: String
  author: Author!
  postedAt: DateTime!
  postType: PostType!
}

...

enum PostType {
  Question
  Answer
  ...
}

Inputs

A type T can have a corresponding input type TInput that has a subset of the type's fields.

E.G

input PostInput {
  title: String
  text: String
  author: Author
}

or

input PostInput {
  title: String
  text: String
  author: Author
  postedAt: DateTime!
  postType: PostType!
}

The ID field can't appear in the input type

Mutations

Any type T can have a mutation called addT. The mutation must have a single argument of type TInput.

E.G

type Mutation {
  ...
  AddPost(inputData: PostInput!): Post
}

Running the mutation will add a new record of type T and return it.

Limitations :

  • currently links to existing data must be supplied as a uid in the input: e.g the input for a post should have { "input": { ..., "author": { "uid": "0x2" } }}. This will change to the actual field name soon.

Queries

Three query types are currently supported:

Get by Id

A type T can have a query getT that must take an input called id of ID type and return a T!

E.G.

type Query {
  ...  
  GetPost(id: ID!): Post!
  ...
}

This will search the store by both ID and expected type to find the matching data.

Query by function

A type T can have a query queryT that may take an argument func of type String and returns a list of T

E.G.

type Query {
  ...
  QueryAuthor(func: String): [Author!]!
  ...
}

A query like queryAuthor { ... } that doesn't include the argument will find every author.

A query like queryAuthor(func: "some function") { ... }, or queryAuthor(func: $varName) for a variable, must take input of a string that's a legal Dgraph query functions.

E.G.

  • queryAuthor(func: "lt(birthday, \"1980-01-01\")") { ... }
  • queryPost(func: "anyofterms(title, \"GraphQL\")") { ... }

The Dgraph rules about indexes apply.

Query translation

As well as the queries translated by convention above, GraphSchema also allows to set a translation for a query.

E.G.

type Query {
  ...
  MostLikedPost(query: String!): Post
  ...
}

and annotation

# [GraphSchema/Dgraph] MostLikedPost -> GraphSchema.Translation.MostLikedPost

tells GraphSchema to use the file GraphSchema.Translation.MostLikedPost to help translate the query.

The translation file is part of a Dgraph query (minus the external braces), that must have a

mostLikedPost as var ...

as its final result. GraphSchema will wrap that in the variables definitions passed into the query as well as a query result that extracts the result. E.G

query mostliked($query: String!) {             <-- GraphQL variable definitions
  ...
  mostLikedPost as...                          <-- translation file inserted 

  mostLikedPost((func: uid(mostLikedPost))...  <-- translation of GraphQL inserted here
}

because of this translation the variable names in the translation file and the GraphQL query must match (e.g. both $query in this case).

Filters

If the schema contains the directives

directive @filter(filter: String!) on FIELD
directive @paging(paging: String!) on FIELD
directive @order(order: String!) on FIELD

Then those can be used to filter and page.

E.G.

  queryPost(func: $inp) @paging(paging: "first: 1") { ... }

To get the first matching post.

or

query getAuthor {
  getAuthor(id: "0x2") {
    id,
    name,
    posts @filter(filter: "anyofterms(title, \"API\")") { title }
  }
}

To insert a search filter into a query.

Annotations

As well as picking up any conventions listed above, GraphSchema uses annotations at the end of a schema file to add further information.

The end of a schema file can have a section

### GraphSchema
# [GraphSchema] author INV posts
# [GraphSchema/Dgraph] Post . text @index(fulltext)
...

Use # [GraphSchema/Dgraph] Post . text @index(fulltext) to place a Dgraph index on fields you wish to search over.

Annotation # [GraphSchema] author INV posts specifies that edges author and post are inverses of each other. In this case, GraphSchema will automatically fill in the posts: [Post!]! field on author from the AddPost mutaions (so posts shouldn't be in the input type).

Annotation # [GraphSchema] ApiKeys enforces API keys for all access to the API - see Security

Secrets

If an input type is annotated as

# [GraphSchema] AuthorInput . password -> Secret

GraphSchema creates the corresponding type (Author) with a Dgraph password type edge. GraphSchema will expect a password in the input type which is passed through to Dgraph, encrypted and stored. The query type is then permitted to check the password by extending the get type

type Query {
  GetAuthor(id: ID!, secret: String): Author
  ...
}

A get with the correct Id and password will return the corresponding record. An incorrect pair returns null.

Because secrets are stored encrypted, once stored they can't be retrieved, only checked against with a get.

(Once the ID type is extended to things other than Dgraph uid (e.g. usernames) this allows, for example, easy login checking.)

To Come

  • Modifications
  • Delete semantics