easy-dgraph: Create DGraph GraphQL on the Fly!

Jonathan Gamble - May 31 '21 - - Dev Community

UPDATE 1/3/22

I have created a second package j-dgraph, that has all of the querying built-in like I spoke of below. It uses easy-dgraph and urql in the backend. Check it out! Similar to firebase, supabase, and prisma apis!

UPDATE 6/3/21

I completed simplified the interface! Keep reading!


Have you every had these problems in Cloud DGraph?

  • Forgot to update your queries.ts file when you added or changed a field
  • Forgot the rules of graphql since add has different sort of input than update etc...
  • Got tired of writing overly complicated queries when what you want is simple
  • Sick of remembering gql rules, when json works well in Visual Studio

Well, inspired by Firestore and Supabase.io, I wanted all the benefits of graphql (instead of sql or nosql), but all the facility of use of these competitors.

So, I wrote a typescript package that can be imported into any JS or TS Framework (Angular, React, Svelte, Vue, etc...)

Framework Note: I have only tested this in Angular, but I purposely left out automatic urql or apollo to make it simple and customizable. Everyone has their own preferences.

Here is my example angular app: angular-fire-dgraph using the package.

Package Note: This package was built upon json-to-graphql-query, so view that package for more rules. To just use that package, simply run:

new Dgraph().toGQL(q);
Enter fullscreen mode Exit fullscreen mode

This will return the graphql object based on the json object q. You can set json-to-graphql-query options with:

new Dgraph().options({ pretty: true }).toGQL(q);
Enter fullscreen mode Exit fullscreen mode

Or to pretty print any of your code, simply use:

new Dgraph().pretty().toGQL(q);
Enter fullscreen mode Exit fullscreen mode

How it works...

Keep in mind, I did not add complicated methods, as I wanted the essence of graphql still there for understanding.

Install the package:

npm i easy-dgraph
Enter fullscreen mode Exit fullscreen mode

Import the library:

import { Dgraph } from 'easy-dgraph';
Enter fullscreen mode Exit fullscreen mode

Queries


Note: anything starting with __ represents options, not values

const gql = new Dgraph('task').query({
  id: 1,
  title: 1,
  completed: 1,
  user: {
    email: 1
  }
}).build();
Enter fullscreen mode Exit fullscreen mode

So, initiate a new instance with the type you want to query. The keys are the values that you want to return. All query functions take keys for the values to return.

You can have the value equal to anything you want, even true, 0, 1, or false. As long as there is a value and it is valid json.

Type

You can set the type with

new Dgraph('task')
Enter fullscreen mode Exit fullscreen mode

or

new Dgraph().type('task')
Enter fullscreen mode Exit fullscreen mode

Available Query Functions:

These queries match the available GraphQL functions.

  • aggregate()
  • get()
  • query()
  • add()
  • upsert()
  • update()
  • delete()
  • customMutation()
  • customQuery()

Other Functions

  • cascade() - input the cascade variables, or no input
  • filter() - the filter object, or just the id(s) you want to filter for mutations
  • set() - the variables you want to add in json format
  • first() - use __first for nested
  • order() - use __order for nested
  • offset() - use __offset for nested

Internal Functions Available

  • operation()
  • pretty()
  • options()
  • toGQL()
  • type()

Other Variables

Just like graphql, you can filter, order, first, and offset...

__filter: { id: '0x2323s' },
__order: { desc: createdAt },
__first: 5,
__offset: 10
Enter fullscreen mode Exit fullscreen mode

Because filter and order are pasted directly into graphql and not parsed, anything viable in graphql should be viable in this package. That includes all complex statements.

For nested types, simply use the __ rules. Valid Types:

  • __cascade
  • __filter
  • __order
  • __offset
  • __first

Or create your own with:

  • __args - for arguments
  • __directives - for directives

See json-to-graphql-query for more details.

VALID Statement

  id: 1,
  title: 1,
  completed: 1,
  user: {
    __filter: {
      allofterms: "sit"
    },
    email: 1
  }
}
Enter fullscreen mode Exit fullscreen mode

@cascade example

__cascade: true
Enter fullscreen mode Exit fullscreen mode

or

__cascade: {
  fields: ['me', you']
}
Enter fullscreen mode Exit fullscreen mode

Generate

After you input your variables, you can either .build() or .buildSubscription() for subscriptions...

Mutations


Mutations are easy as well... same variables in the function({ variable: 1 }) , and use filter() and set() to mutate.

Options

  • filter() is the same thing as __filter. Use the latter for nested objects.
  • set() is for adding data.

Add

const gql = new Dgraph('task').set({ title: 'new task', completed: true }).add({
  id: true,
  title: true,
  completed: true,
  user: {
    email: true
  }
}).build();
Enter fullscreen mode Exit fullscreen mode

For upserts, you can use upsert() instead of add.

Update

const id = '12345';

const gql = new Dgraph('task').filter(id).set({ completed: true }).update({
  id: 1,
  user: {
    email: 1
  }
});
Enter fullscreen mode Exit fullscreen mode

Remember you can return whatever values you want using true or 1, whatever your preference is. Here, completed: true is the value true since it is in the set() function.

Delete

const gql = new Dgraph('task').filter('0x1234').delete();
Enter fullscreen mode Exit fullscreen mode

If you don't want to return anything, leave delete() parameters empty, although it does default the return value of msg per the docs.

Custom Queries / Mutations

Custom mutations and queries are also possible. Simply use:

  • customMutation(query)
  • customQuery(query)

Multiple Queries and Mutations

You can also do multiple queries and mutations. Simply chain the object with a new type() function like so:

new Dgraph('task').query({ title: 1}).type('task', 'jonathan')
.filter('123').query({ title: 1 }).buildSubscription();
Enter fullscreen mode Exit fullscreen mode

Notice you can input a second variable in type() for the alias of the same query.

Which will produce the following:

subscription {
    queryTask {
        title
    }
    jonathan: queryTask(filter: {id: "123"}) {
        title
    }
}
Enter fullscreen mode Exit fullscreen mode

If you need to force specifiy the operation, use operation('mutation'), for example.

Other Options

All the options should be there. @skip and @include are not needed, since these are generated on the fly. All complex statements are just added automatically from your json code, as long as they are supported in dgraph graphql.

Every single type of mutation, subscription, or query should be generatable. Let me know if you see an option I did not notice.


Using the gql object

The gql object is just that, the code in graphql. Because it is written on the fly, you do not need the var input part of graphql. So, simply input the code into your module of choice (urql, apollo, fetch), although urql is highly recommended due to its speed and cache enhancements.

const results = await this.urql.mutation(gql);
Enter fullscreen mode Exit fullscreen mode

Bugs

I imagine there are plenty of bugs, as I just wrote this this weekend. Post an issue on the github repo.

Usage

Post usage questions here, so it can be available to anyone to see.

Example

See my angular-fire-dgraph for an example module.

Source Code

You can see the source code is pretty simple, but powerful.

Even Easier Top Level

You can write a module on top of this module that allows you to do even easier calls with less code like this:

this.dgraph.type('task').query({
  id: true,
  title: true,
  completed: true,
  user: {
    email: true
  }
}).buildSubscription().subscribe((r: any) => {
    this.tasks = r;
});
Enter fullscreen mode Exit fullscreen mode

This produces the code, and runs it. See my repo here for an example on how to make this happen. You need to extend the original module like so:

// extend the original dgraph module
export class dgraph extends Dgraph {

  constructor(private urql: UrqlModule) {
    super();
  }

  async build() {
    if (this._operation === 'mutation') {
      return await this.urql.mutation(super.build());
    }
    return await this.urql.query(super.build());
  }

  buildSubscription() {
    this.operation('subscription');
    return this.urql.subscription(super.build());
  }

}
Enter fullscreen mode Exit fullscreen mode

Contributing

I would love any ideas for new or simpler features. Send me a pull request, PM me here.

Future Note

This original post on discuss.dgraph.io will be locked after 30 or so days from me to update, so check the forum for updates.

I was not able to list every example here, but you can see it can generate complex statements!

Thanks,

J

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .