Dgraph is the world’s first (and only at this time) native GraphQL database. While this statement may sound oxymoronic to some, it tells a story of the advancements made by Dgraph Labs and their journey to becoming the #1 graph database. Dgraph just celebrated 16K Github stargazers, almost doubling that of its closest competition.
Dgraph Labs set out on a journey to build a database using GraphQL as the query language but soon came to realize the fact that GraphQL is not a database query language, but rather an API query language. After introducing Dgraph to the world in April 2016, Dgraph Labs spent the next four years extending a fork of GraphQL making their own database query language. This query language is now known as Dgraph Query Language (DQL). At the end of Q1 2020, Dgraph Labs released Dgraph version 20.03 summarized in the blog post Back to GraphQL. Dgraph did not drop DQL but rather created an additional endpoint from the core of Dgraph to officially be the first database to support the official GraphQL specification.
Every other GraphQL implementation outside of Dgraph requires an additional layer. This layer then serves GraphQL and communicates to their database of choice. With Dgraph, there is not a need for another service layer. Dgraph removes the hassle from implementing GraphQL services for clients by managing most business logic use cases at its core. This removes the need for writing GraphQL resolvers and middlewares.
Supporting both DQL and GraphQL, two similar query languages, in the core of Dgraph can seem confusing to newcomers of Dgraph. In this article, we aim to guide you through this uncharted space and put you well on your way to understanding Dgraph in relation to both DQL and GraphQL.
GraphQL is an API. This point needs clarifying before we continue as it will help break down some of the barriers to understanding Dgraph. GraphQL is very commonly thought to be just a query language for APIs, but looking at the bigger picture, no API outside of a GraphQL API can understand GraphQL as an API query language.
The term GraphQL entails more than just a query language and embodies the entire GraphQL service. A GraphQL service is a server-side runtime. This service can be used for executing queries, subscribing to changes in the data, mutating data, and other useful operations. The service itself uses a type system to define the data available through the API. This GraphQL type system, created by the developer, is called a schema. From this schema, we know what data is available through our API and the types of said data. From this, we can begin to define GraphQL queries and mutations and issue them against the GraphQL API.
Up to this point, things seem like any other ordinary GraphQL service. A developer defines a schema and there is a system that uses this schema to resolve data requests back to the client. But here is where Dgraph really starts to show its true colors as a native GraphQL database.
After defining a GraphQL schema and supplying it to Dgraph, Dgraph provides a GraphQL endpoint for running queries and mutations against the data it stores. Many other services mimic what Dgraph is doing, but not in the same manner. With other services, a developer provides a GraphQL schema and the service might build a database instance and a separate GraphQL service from the single schema, but there are still two services that it has generated. With Dgraph, only a single instance of Dgraph is required to serve both the database and the GraphQL API endpoint. This approach allows you to have an all-in-one graph backend, the graph database to store the data and a powerful GraphQL layer to handle querying. Dgraph can be started with multiple instances distributing both the DQL endpoints and the GraphQL endpoint across multiple servers at once. This distributed aspect is what the “D” in Dgraph represents.
If you have already looked into Dgraph’s documentation you may have noticed that there is documentation for defining both a DQL schema and a GraphQL schema. So if Dgraph is a native GraphQL database, then why are there two different schema syntaxes and when should you use one or the other?
Since GraphQL is an API and the schema is a representation of a type system, Dgraph needed additional syntax for a database schema language than what the GraphQL syntax could provide. A DQL schema looks similar to a GraphQL schema, but they are different syntaxes.
GraphQL Schema | DQL |
---|---|
type Person {
name: String!
}
type Car {
vin: String!
owner: Person!
}
|
type Person {
name string
}
type Car {
vin string
owner Person
}
owner uid .
vin string .
name string .
|
With Dgraph, if you provide a GraphQL schema, a DQL schema will be generated, but if you only provide a DQL schema, then a GraphQL schema will not be generated.
So which schema should you use? If you decide you are going to use GraphQL at any point in your application, then first define your GraphQL schema. Dgraph will generate the DQL schema, and provide you the option to later modify the underlying DQL schema so long as it does not conflict with the saved GraphQL schema.
Let’s briefly cover some graph basics. Everything in Dgraph is a predicate. Nodes and their corresponding edges to other nodes or values in a broad sense are all only predicates. For a really quick example, no piece of data exists by itself, rather every piece of data forms a triple consisting of a subject, predicate, and object. A unique identifier (UID) of a node does not exist by itself, rather it exists as it is connected to objects through predicates. The most basic existence of a node would be a UID connected through the predicate dgraph.type>
to its correlating string value of the type: 0x1 <dgraph.type> "Car" .
There is not a “table” for cars, or pivot tables linking cars to owners, rather there are posting lists as objects of predicates. Understanding these basics will help continue your journey with Dgraph and why there is a need for two types of schemas.
A DQL schema is predicate-first focused and supports some aspects not yet supported by the GraphQL syntax such as multi-lingual predicates and facets (data information stored on edges between nodes). A GraphQL schema is type-first focused and only supports spec-compliant elements such as type
s, union
s, and interface
s. DQL does not support unions or interfaces but can still correctly represent GraphQL schemas containing such elements.
If you are already familiar with GraphQL, you may be wondering why inputs were excluded from the previous paragraph. GraphQL API developers usually have to define inputs in their schema as well, but Dgraph will generate inputs in your GraphQL schema based upon your types and directives.
Looking at the above schemas they are not direct representations of each other. The schemas will need indexes applied to enable searching, and the GraphQL schema must have directives applied to it for Dgraph to generate the correct DQL indexes, map inverse relationships, and inform endpoint functions of identifiers to use in the DQL schema.
The last thing to know before moving on is that a DQL schema that is generated from a GraphQL schema will name predicates in a type dotted
notation. Here is a schema side by side comparison:
GraphQL Schema | DQL Schema |
---|---|
type Person {
id: ID
name: String!
cars: [Car] @hasInverse(field: "owner")
}
type Car {
vin: String! @id
owner: Person! @hasInverse(field: "cars")
}
|
type Person {
Person.name string
Person.cars [Car]
}
type Car {
Car.vin string
Car.owner Person
}
Person.name string @index(exact) .
Car.vin string @index(exact) .
Person.cars [uid] .
name string .
|
This is just a small example to get a brief understanding so we can move on. I encourage you to review the docs and join us in the Dgraph community to deepen your understanding of these schema syntaxes.
A query is a way to request (read) data, and a mutation is a way to mutate (create, update, or delete) data. In relation to REST API, this allows you to do all of your CRUD operations through a GraphQL endpoint. GraphQL provides a beautiful syntax that enables queries and mutations to work in fundamentally the same syntax.
GraphQL Mutation | GraphQL Query |
---|---|
mutation { addCar(input:[{ vin: "1GNSKJKC4FR597102" owner: { name: "Nick Fury" } }]) { car { vin owner { id name } } } } |
query { getCar(vin: "1GNSKJKC4FR597102") { vin owner { id name } } } |
Both queries and mutations in GraphQL are sent to a single endpoint. With DQL, queries and mutations are sent to two separate endpoints. That is due to the syntax difference between DQL queries and mutations. Dgraph supports both JSON and RDF formats for incoming mutations.
While a DQL query looks very similar in syntax to a GraphQL query, there are syntax differences between them such as DQL supporting var blocks and allowing queries outside of a seemingly typed GraphQL system. (You can query for fields in DQL even if that field does not exist on the DQL schema and you will not receive any errors.) Any data you query in DQL is returned in a JSON format similar to GraphQL’s response payloads.
DQL Mutation | DQL Query |
---|---|
set { "uid": "_:thisCar" "dgraph.type": "Car", "Car.vin": "1GNSKJKC4FR597102", "Car.owner": { "dgrpah.type": "Person", "Person.name": "Nick Fury", "Person.cars": [ { "uid": "_:thisCar" } ] } } |
{ getCar(func: eq(Car.vin, "1GNSKJKC4FR597102")) { uid Car.vin Car.owner { uid Person.name } } } |
The purpose of this article is not to cover the semantics between DQL and GraphQL but rather provide an overview understanding of when to use which one, so we will move along without much further explanation of the above examples. Feel free to start a discussion in the community concerning any specific questions you may have.
Dgraph provides a GraphQL endpoint to provide access from frontend clients directly to the database in a secure and manageable manner. Dgraph provides developers with a toolset of directives to apply in the GraphQL schema that apply business logic rules such as authorizing access to data and applying constraints that otherwise do not exist in the database itself.
A quick example of this API control was shown above with a Car
having a vin
field that is using the @id
directive to maintain uniqueness across the type. While this uniqueness will be controlled from the GraphQL endpoint without any additional work needed by the developer, it is still possible to mutate data using DQL that breaks this unique constraint and adds a duplicate entry. If you were to follow through the above mutations and queries you would find that the last DQL query would return two cars with two different uid
s that are respectively owned by two people having the same name, but different uid
s.
It may appear contradictory to the databases’ purpose to see this API control being broken, but this actually sheds light on the purpose of the endpoints. There is one more key piece of knowledge to understand before moving forward. Dgraph at its core uses a single query language, DQL. Any incoming GraphQL queries or mutations are rewritten on the fly within the core functions into DQL, which are then understood by the database.
With this knowledge, it is easy to understand that anything that can be done in GraphQL can be done in DQL, but the opposite is not necessarily true. Some queries and mutations are possible in DQL that are simply not possible in GraphQL, mainly due to the restriction of the GraphQL specification itself being a strictly typed query language, and DQL being a loosely typed graph query language that can even work without a predefined schema.
GraphQL provides clients with a strictly controlled query and mutation endpoint that developers can tightly control while DQL provides developers with a more flexible graph query language to control their data in ways outside of the normal use cases of front-end applications.
A real-world use case for using both GraphQL and DQL together would be for a developer to have a more flexible application management console than what options they provide to clients within their application. Consider a use case of an application developer who builds out their portfolio site. They can use a single database to keep track of clients and projects, but maybe the developer wants to hide some more private data such as financial data concerning projects that correlate to proposals and invoices. The developer doesn’t want this financial data made public to anyone viewing his portfolio site, but at the same time, the developer does not want to create a separate database for a portfolio site when all of the data needed is already in a single Dgraph database. The consumer-facing portfolio site would connect via GraphQL only allowing clients access to specific data types and fields, but the developer can have an administration site that he alone has access to and can add in other related fields and data objects at will without being constrained by GraphQL syntax specifications.
DQL can be considered a direct raw graph database query language that’s similar to other database direct languages such as SQL, Cypher, or Gremlin while GraphQL should be thought of as an API endpoint to access the database in a controlled manner.
The following shortlists of topics are not meant to be comprehensive, but rather a list of ideas to help you understand when it may or may not be appropriate to use GraphQL or DQL to connect to a Dgraph database.
Your circumstances and application needs may be unique and not perfectly fit into either GraphQL or DQL use cases. However, Dgraph may still be ideal for your use case with highly connected data. So how do you connect your application to Dgraph? Dgraph provides developers with a wide toolset but does not require you to use Dgraph as your complete backend solution.
If your use case calls for advanced business logic, you can develop a custom backend for your application leveraging either DQL, GraphQL, or even both as needed. You could even use GraphQL and DQL together within custom directives and lambdas in the GraphQL endpoint to expose some more restricted DQL syntax through to the GraphQL endpoint. Dgraph encourages developers to think outside of the box as you build the next generation of applications and services.