Getting started with GraphQL

illustrations illustrations illustrations illustrations illustrations illustrations illustrations
post-thumb

Published on 21 March 2024 by Andrew Owen (5 minutes)

GraphQL is an API query and manipulation language. Created by Facebook in 2012, it was open-sourced in 2015. In 2018 it moved to the GraphQL Foundation and introduced a schema definition language (SDL). It seems to be replacing REST as the standard way to expose public APIs. With REST, you have to define the inputs and outputs for each endpoint. Whereas with GraphQL, there’s only one endpoint and you define a schema. The user sends only the required data to get what they want back. And unlike SQL, you’re not limited to a single data source.

This year, I’ve had to get rapidly up-to-speed with GraphQL. I thought I’d be starting from nothing, but I’d forgotten that TinaCMS (the headless content management system that I use with this site) uses it. One of the first problems I had to solve was how to generate static documentation. My limited research led me to two possible solutions: SpectaQL, developed from the earlier DociQL, and Magidoc. The latter has built-in search, so that made my choice for me. I use Hugo as my static site generator, so the first thing I had to do was start the local version of TinaCMS from my site’s Git repository:

npx tinacms dev - c "hugo server -D -p 1313"

With the local server running, you can access GraphiQL at http://localhost:1313/admin/#/graphql. GraphiQL is a reference implementation of the GraphQL API playground. If it’s too basic for you, there’s a commercial alternative called Apollo. The TinaCMS implementation gives you three options (selected from the icons on the left):

  • Docs: API docs in a tree structure.
  • History: Previous queries.
  • Queries: Query builder.

After you’ve taken a look at the Docs section in GraphQL, you’ll understand why I wanted to generate a static site. With Magidoc it’s easy. I added this configuration file to my Git repository:

export default {
  introspection: {
    type: 'url',
    url: 'http://localhost:4001/graphql',
  },
  website: {
    template: 'carbon-multi-page',
	  customStyles: ['/static/css/custom.css'],
  },
}

Then to generate the documentation, enter: pnpm add --global @magidoc/cli\@latest && magidoc generate (you’ll need pnpm installed). By default, this creates a static site in a folder called docs. However, you can’t just open the index.html file. You’ll need to launch the server with magidoc preview and then follow the link. You may want to add the docs folder to your .gitignore file. But in production, you can deploy using any web server. The output is based on IBM’s Carbon Design System.

Now you’ve got some static searchable documentation, you can start exploring your schema. GraphQL schema can be written in any programming language that implements the type system. With TinaCMS that means either TypeScript or JavaScript. Here’s a snippet of my Tina config.js file:

    schema: {
      collections: [
        {
          name: "blog",
          format: "md",
          label: "Blog",
          path: "content/blog",
          defaultItem: () => {
            return {
              draft: true,
            }
          },
          fields: [
            {
              name: "draft",
              type: "boolean",
              label: "Draft",
              required: true,
            },
            {
              name: "title",
              type: "string",
              label: "Title",
              isTitle: true,
              required: true,
            },
            {
              name: "date",
              type: "datetime",
              label: "Date",
            },
            {
              name: "description",
              type: "string",
              label: "Description",
            },
            {
              name: "image",
              type: "image",
              label: "Image",
            },
            {
              name: "image_license",
              type: "string",
              label: "Image License",
            },
            {
              name: 'tags',
              type: 'string',
              label: 'Tags',
              list: true,
            },
            {
              name: 'body',
              type: 'rich-text',
              isBody: true,
              label: "Body",
              templates: [
                {
                  name: 'shortcode',
                  label: 'shortcode',

This creates a schema type called Blog with the fields:

  • draft: Boolean!
  • title: String!
  • date: String
  • description: String
  • image: String
  • image_license: String
  • tags: [String]
  • body: JSON
  • id: ID!
  • _sys: SystemInfo!
  • _values: JSON!

Fields can be scalar (Boolean, Float, Integer, String and so on) or complex (containing other data). A trailing exclamation mark ( ! ) means the field is required (non-nullable). Square brackets mean an array. If the type is JSON, then a set of sub-fields is defined. You can also use previously defined types to create relationships. For example, if your blog has multiple contributors you could have a field called author with the type User!.

Queries

Because there’s only one endpoint, you have to tell it what you want. For example, to get the list of collections you’d use:

{
  collections {
    name
  }
}

The collections filed is the root field. Everything else is the payload. Because the payload only contains name the query returns a list of all the collections:

{
  "data": {
    "collections": [
      {"name": "blog"},
      {"name": "recipe"}
    ]
  }
}

You get exactly the amount of information you ask for in the payload. You can also pass arguments. For example, if you wanted to get the title, date and draft status of this article you could use:

{
  blog(relativePath: "getting-started-with-graphql.md") {
    title
    date
    draft
  }
}

However, typically you would use a variable in the query, like this:

query blog($relativePath: String!) {
  blog(relativePath: $relativePath) {
    title
    date
    draft
  }
}

Variables have a prefix ( $ ). The type (String in this example) is defined in the query. The variable is passed in the blog parameters.

Mutations

Query is the equivalent of GET in REST or read in traditional data terminology. For create, update and delete there’s mutation. The syntax is the same as for queries. You might have noticed the ID type earlier. If you specify it when creating a record, the GraphQL server will give it a unique identifier. You can try out this example from the TinaCMS documenation (sticking with the Napoleon Dynamite references):

mutation {
  updatePost(relativePath: "voteForPedro.json", params: {title: "Vote For Napolean Instead", category: "politics", author: "content/authors/napolean.json"}) {
    title
    category
    author {
      ... on Author {
        id
      }
    }
  }
}

Subscriptions

I’ve written previously about event-driven architectures. Subscriptions are a way to get data from the GraphQL server every time an subscribed event takes place. Not all GraphQL servers are configured to support websocket subscriptions, and that’s true of my TinaCMS instance. But if it was, this is what a request to be notified when a new blog article is created would look like:

subscription {
  newBlog {
    title
  }
}

Conclusion

It’s beyond the scope of this article to do more than cover the basics. For more information, the GraphQL documenation is a good starting point. TinaCMS is a good application to start experimenting with its GraphQL API. For the adventurous, Kingdom Orjiewuru wrote the first part of an article on building a simple document manager with GraphQL.