Skip to content

Instantly share code, notes, and snippets.

@digitalronin
Last active August 31, 2020 16:12
Show Gist options
  • Select an option

  • Save digitalronin/7f2e8fae7fb14345d746e3222a7ff69b to your computer and use it in GitHub Desktop.

Select an option

Save digitalronin/7f2e8fae7fb14345d746e3222a7ff69b to your computer and use it in GitHub Desktop.

Build a GraphQL-powered blog wth Dgraph and Slash GraphQL

In this article, I'm going to walk you through building a proof of concept blog powered by Slash GraphQL.

Graph databases are a fascinating way to model the information in a system where the relationships (edges) between pieces of data (nodes) are first-class entities of the system. This is a different approach to the more common relational database (RDBMS) model, where the relationships between records are implied. For example, "this user owns this post because user.id == post.user_id"

Working with graph databases can feel a little unfamiliar if you're used to working with RDBMSs, so in this article I'll try to show you some of the basics. As usual, I'm not trying to produce finished, production-ready code here - just simple examples to show you the underlying concepts.

You can find all the example code for this article here.

Dgraph

For this article, I'm going to use dgraph, a native GraphQL database (as opposed to a graphql layer on top of a relational datastore) written in Go, and designed for large-scale application with high-availability and transaction support. If you're interested in learning more about the project, they have some good introductory videos on their YouTube Channel.

Rather than installing dgraph on a server, or running it locally via docker, I'm going to use their hosted GraphQL backend service Slash GraphQL. The free tier (at time of writing) allows 20,000 operations per month, which is plenty for our purposes.

Let's get started.

Creating a GraphQL backend on Slash GraphQL

In order to use Slash GraphQL, you need to login using your GitHub or Google account, and you'll then see the web interface with options to create and manage your backends, as well as lots of links to tutorials and other documentation, which I recommend exploring.

Dgraph recently released slash-graphql, a command-line tool for managing Slash GraphQL backends, and because I strongly prefer working on the command line, I'm going to use that for this article. But you can do everything via the web interface, if you prefer.

You can install the command line tool by running:

npm install --global slash-graphql

Create a backend

Using slash-graphql to manage Slash GraphQL backends feels a lot like using the heroku command-line tool to manage Heroku applications, so if you're comfortable with that, you should find this quite familiar.

To create a Slash GraphQL backend via the command-line, we first have to login:

slash-graphql login

This will prompt you to confirm that a code on your terminal matches a code shown on the web page that the command will open. Once you have done this, you can issue commands to Slash GraphQL.

Now we can create our graphql backend like this:

slash-graphql create-backend blog

This will output the "endpoint" of your backend, which usually takes around 20 seconds to create. This is the URL we will use to interact with our graphql database. You'll be using this throughout this article, so please make a note of it.

In my case, my graphql endpoint is:

https://anxious-aunt.us-west-2.aws.cloud.dgraph.io/graphql

Please substitute your own endpoint wherever you see this in the code examples.

Define a schema

We need to define a schema for our blog data.

type User {
  email: String! @id @search(by: [hash])
  name: String @search(by: [exact])
  posts: [Post] @hasInverse(field: user)
}

type Post {
  id: ID!
  title: String! @search(by: [fulltext])
  body: String!
  image: String
  user: User!
  comments: [Comment] @hasInverse(field: post)
}

type Comment {
  id: ID!
  body: String!
  user: User!
  post: Post!
}

Let's break that down a little. I'm just going to cover a few highlights here. For details, please have a look at the Slash GraphQL documentation.

Starting with the last entity, we define Comment like this:

type Comment {
  id: ID!
  body: String!
  user: User!
  post: Post!
}

This means we have a node type called Comment, which has several attributes.

  • id: ID! tells dgraph to generate its own uid values for this type

  • body is a required string value

  • post and user have types Post and User, and the ! indicates that these are required fields. i.e. every comment belongs to a Post, and also belongs to a User.

The Post type has a comments attribute which is a list of Comment nodes:

comments: [Comment] @hasInverse(field: post)

Similarly, User has many posts.

Note that we're telling our backend what node types exist, and how they relate to each other, but we don't have to define how to make those relationships work - no foreign keys, no joins, and no many-to-many mapping tables.

We can apply this schema to our backend like this:

slash-graphql update-schema --endpoint https://anxious-aunt.us-west-2.aws.cloud.dgraph.io/graphql schema.graphql

Now that we have our schema, we can add some data.

Adding data with curl

Although the slash-graphql tool has an import-data function, that's designed for restoring a backup rather than adding chunks of data.

The usual way of interacting with a graphql database is via HTTP requests, so that's how we're going to add our data using curl, via this script:

#!/bin/bash

FILE=$1

curl --request POST \
  --header "Content-Type: application/graphql" \
  --header "Authorization: bearer ${API_TOKEN}" \
  --data "$(cat ${FILE})" \
  ${ENDPOINT}

The script uses curl to make an HTTP POST request (--request POST) to our graphql endpoint ${ENDPOINT}.

The body of the request is the contents of the file ${FILE} (--data $(cat "${FILE})"), and we pass the name of the file as an argument to the script FILE=$1.

We set the Content-Type header to application/graphql (it is also possible to send commands in JSON).

We also need to provide an API token to the backend, hence the Authorization header value of bearer ${API_TOKEN}.

Getting an API token

We need to use the Slash GraphQL web interface to generate an API token for our application.

In the interface, go to: Dashboard > Settings > Security > Add API Key

Give your token a name, e.g. "blog middleware", and be sure to keep a copy of the value which is displayed. You won't be able to see it again.

Now we can set the environment variables we need in our post.sh script:

export ENDPOINT=https://anxious-aunt.us-west-2.aws.cloud.dgraph.io/graphql
export API_TOKEN=[your API token value]

Now we can add our data.

Adding the data

Here is the graphql we're going to send (a mutation is how you alter data):

mutation {
  addPost(input: [
    {
      user: {
        name: "Burk Dronsfield",
        email: "bdronsfield0@apple.com"
      },
      title: "Scallops - 10/20",
      image: "https://picsum.photos/id/441/600/300",
      body: "Vestibulum ac est lacinia nisi venenatis tristique. Fusce congue, diam id ornare imperdiet, sapien urna pretium nisl, ut volutpat sapien arcu sed augue. Aliquam erat volutpat.\n\nIn congue. Etiam justo. Etiam pretium iaculis justo.",
      comments: [
        {
          body: "First post!",
          user: {
            email: "tgodleman1@chronoengine.com",
            name: "Tamar Godleman"
          }
        }
      ]
    }
  ])
  {
    post {
      id
      title
    }
  }
}

None of those users are real people, it's sample data from mockaroo.

Notice how we're adding two users, a blog post, and a comment, all with a single request to our backend. This is where we can start to see the power of graphql databases.

./post.sh addData.graphql | jq

The | jq is optional, but the response to our post will be a JSON document, and piping it through jq makes it easier to read.

Querying the database

We can query the database in exactly the same way, via an HTTP POST. Here's a query to retrieve users and the titles of any posts they've written:

query {
  queryUser {
    name
    posts {
      title
    }
  }
}

We run this query in exactly the same way as the mutation:

./post.sh queryUser.graphql | jq

The top of the output you get should be something like this:

{
  "data": {
    "queryUser": [
      {
        "name": "Tamar Godleman",
        "posts": []
      },
      {
        "name": "Burk Dronsfield",
        "posts": [
          {
            "title": "Scallops - 10/20"
          }
        ]
      }
    ]
  },

Web middleware

Now that we can manipulate data in our graphql database, let's put together a small middleware layer to handle HTTP GET/POST requests from a web browser, and make graphql requests to our backend.

There are lots of different architectures you could use here. For instance, you could have a single-page app. using something like React to make graphql requests directly from the browser. I'm using this architecture because I want to keep this article simple, and I think it's an easy design to understand.

Here's how it's going to work:

 +--------------+       +-------------+         +---------+
 |              |       |             |         |         |
 |              |------>|             |-------->|         |
 | Web browser  |  HTTP | Web server  | GraphQL | Slash   |
 |              |<------|             |<--------| GraphQL |
 |              |       |             |         |      {s}|
 +--------------+       +-------------+         +---------+

Run the web server

This github repository contains a very simple express.js webserver. To run it, just clone the repository and launch the server:

git clone https://github.com/digitalronin/graphql-blog-express
cd graphql-blog-express
export ENDPOINT=https://anxious-aunt.us-west-2.aws.cloud.dgraph.io/graphql
export API_TOKEN=[your API token value]
npm install
node index.js

At this point, you can visit https://localhost:5000 to see the web interface.

Or, if you want to run it on Heroku (and you have the heroku command-line tool installed and logged in):

git clone https://github.com/digitalronin/graphql-blog-express
cd graphql-blog-express
heroku create
heroku config:set ENDPOINT=https://anxious-aunt.us-west-2.aws.cloud.dgraph.io/graphql
heroku config:set API_TOKEN=[your API token value]
git push heroku master
heroku open

This should launch a web browser showing you the interface.

You should see a very ugly page showing a list of the blog posts (only one, so far), and you should be able to click on the titles to view each post.

Let's see how the code works. At the bottom of the index.js file, we've got this:

async function graphqlQuery(graphql) {
  const result = await fetch(ENDPOINT, {
    method: "POST",
    headers: {
      "Content-Type": "application/graphql",
      Authorization: `bearer ${API_TOKEN}`
    },
    body: graphql
  });
  const obj = await result.json();
  console.log("obj:", obj);
  return obj;
}

This is the heart of our middleware application - a function that takes a string of graphql, posts it to the endpoint and returns a data object from the JSON response.

I've deliberately kept this code to the simplest form that works. There are a lot of cleverer ways to interact with a graphql backend, and I'd encourage you to read some of the Slash GraphQL documentation to find out more.

The list of blog posts comes from this function:

async function listPosts(req, res) {
  const obj = await graphqlQuery(`
    query {
      queryPost {
        id
        title
      }
    }
  `);
  res.render("pages/posts", { posts: obj.data.queryPost });
}

And this one displays the individual posts:

async function showPost(id, req, res) {
  const obj = await graphqlQuery(`
    query {
      queryPost(filter: {
        id: ["${id}"]
      }) {
        title
        image
        body
        user {
          name
        }
        comments {
          body
          user {
            name
          }
        }
      }
    }
  `);
  res.render("pages/post", { post: obj.data.queryPost[0] });
}

The addPost and addComment functions have a very similar structure, but send mutations rather than queries to the backend, and there's a very basic interface using HTML forms to wire those up.

I've added name and email fields to every form, but of course in a real application you'd have signup and authentication steps, and read the user credentials from a cookie.

The web application is very basic - just enough to demonstrate the concepts. In particular, please never use unsanitised user input in a real application, because it's a security nightmare.

Conclusion

We've talked briefly about the difference between a graph database and a traditional RDBMS, and we've gone through the process of setting up a hosted graphql backend with Slash GraphQL, creating a schema and using mutations and queries to manipulate your data. Finally, we put together a very basic web interface as a proof of concept for a graphql-powered blog.

I've barely scratched the surface of what you can do with a graphql database in this article, but I hope it inspires you to dive in and learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment