Posts Introduction to GraphQL
Post
Cancel

Introduction to GraphQL

What is GraphQL and how do I use it?

An incomplete introduction to GraphQL.


In this blog post, I will cover some basic techniques to create a GraphQL server using the Apollo Server Library and the Hasura GraphQL engine. I will then go on to provide an example of how to connect a client application to the GraphQL server. I should also note that this tutorial does not cover all of the features of GraphQL but only a few of them

GraphQL Server

Apollo GraphQL Server

When designing an Apollo GraphQL server 3 key areas need to be considered;

  • The Schema — provides structure to the API and defines what the types, queries, mutations, and subscriptions look like and how they can be used
  • The Resolvers — are a way to descript what each query, mutation, and subscription will do.
  • The Server — is the final part that wraps the schema and resolvers together into a functional API that can be consumed by a GraphQL client

An example of how this all links together can be found here

The Schema

In GraphQL a schema defines fields that can be queried, mutated, or subscribed to and what the type of those fields are. GraphQL provides a Domain Specific Language (DSL) known as the GraphQL Schema Definition Language (SDL) which is used to create these schemas.

What makes a schema

When defining a GraphQL schema there are a few key parts that must be present. These include the Root type (some GraphQL server libraries may automatically define this) which is used to group the Query, Mutation, and Subscription types. A Query type should be defined if there are any queries that can be made in the API, A Mutation type should be defined if there are any mutations that can be made and a Subscription type should be defined if a user can subscribe to server events (This feature uses WebSockets so that the server can notify the client of an event)

As well as the predefined Query, Mutation and Subscription types GraphQL allows you to define custom types. This is achieved using the type keyword for custom objects and the scalar keyword for custom scalars (such as Apollo file uploading facility or the _uuid in Hasura).

For example, it is possible to create a Book type that contains a custom BookId scalar

Something to note however is that types are only output to be able to bundle arguments together the input type needs to be used.

Unlike some typesafe languages which assume a value is not null by default, GraphQL does the opposite and will assume a value is nullable unless declared otherwise with the exclamation mark (!) operator

Example

An example schema is defined below for a simple book database where a user can query for all books or a specific book by title. They are also able to insert a new book into the database.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Book {
    title: String
    author: String
}

type Query {
    books: [Book]!

    book(title: String!): Book
}

type Mutation {
    insert_book(title:String! author:String!): Boolean!
}

For more information about how to create GraphQL schemas visit the GraphQL website here

The Resolvers

Resolvers are used to connect the GraphQL schema to an action in the server.

In the example of an Apollo GraphQL server resolvers link a schema type to its respective response, each response is a function that can take up to four arguments.

parent/sourceThe result of the parent resolver (the previous resolver in the chain)
argsan object of input arguments from the GraphQL schema
contexta shared object between all resolvers for a particular operation. This can be used to share state
infoInformation about the current operation’s execution state

Each of these functions will return a result that matches the format defined in the return type of the GraphQL schema.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const resolvers: Resolvers = {
    Query: {
        books: (source, args, context) => {
            return context.data.books();
        },
        book: (source, {title}, context) => {
            return context.data.book(title);
        },
    },
    Mutation: {
        insert_book: (source, {title, author}, context) => {
            return context.data.insertBook(title, author);
        }
    },
}

The Server

Finally, a GraphQL server is declared where the schema (typeDefs) resolvers and context are brought together in a single object and the server is then launched. In this example, the server will expose the GraphiQL IDE to http://localhost:3000 which is also the same endpoint address for the GraphQL API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const server = new ApolloServer({
    typeDefs,
    resolvers,
    context: (async (context) => ({
        data: new MockDatabase(),
        ...context,
    })) as DatabaseResolverContextFunction
});

server.listen({
    port: 4000,
}).then(({url}) => {
    console.log(`🚀  Server ready at ${url}`);
}).catch((reason) => {
    console.error('Failed to launch server');
    console.error(reason);
});

Hasura GraphQL Engine

The Hasura GraphQL Engine is an alternative batteries included approach to creating a GraphQL server compared to the Apollo GraphQL Server. It makes use of a PostgreSQL database for storage and a web interface/rest API to manage and interact with the server. Hasura allows for other GraphQL servers, microservices and other serverless code to be merged into a single GraphQL endpoint making it easier to manage and access from the clients’ machine.

Running the server

Hasura can be launched using the following docker-compose script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
version: "3.7"

services:
  postgres:
    image: postgres:12
    ports:
      - "5432:5432"
    restart: always
    environment:
      POSTGRES_PASSWORD: ChangeThisDatabasePassword
    volumes:
      - type: volume
        source: database
        target: /var/lib/postgresql/data
        volume:
          nocopy: true

  hasura:
    image: hasura/graphql-engine:v1.2.0-beta.4
    restart: always
    depends_on:
      - postgres
    ports:
      - "8090:8080"
    environment:
      HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:ChangeThisDatabasePassword@postgres:5432/postgres
      HASURA_GRAPHQL_ENABLE_CONSOLE: "false"
      HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log
      HASURA_GRAPHQL_ADMIN_SECRET: "ChangeThisSecretKey"
      HASURA_GRAPHQL_UNAUTHORIZED_ROLE: "public"

volumes:
  database:
    name: "GraphQLIntroPostgreSQL"

The two volumes that are bound to the Hasura container (ln 32 & ln 37) are for the Hasura metadata and migrations API which can be used to automate the process of creating the container and the PostgreSQL database

An example of this can be found here

Connecting to the server

When accessing Hasura for the first time in a web browser (which in this example is available at http://localhost:8090) you will need to enter the secret key (ChangeThisSecretKey) to be granted admin priveledges where you can start defining the structure of the PostgreSQL tables, the permissions on each row and column of a table and any custom actions, triggers or remote schemas that should be joined.

GraphQL Client

When connecting to GraphQL using the GraphQL client there is a common part and then there may be a plugin for the front end framework being used to display content to the user. In this example it is VueJS

An example of this can be found here

Apollo GraphQL Client

In this example, a GraphQL client will be constructed without using the Apollo Boost library so that additional configuration options, custom caching and more detailed links can be created. For more information about setting up custom Apollo GraphQL clients click here

1
2
3
4
5
6
7
8
9
10
11
12
13
const httpLink = createHttpLink({
    // You should use an absolute URL here
    uri: 'http://localhost:8090/v1/graphql',
});

// Cache implementation
const cache = new InMemoryCache();

// Create the apollo client
const apolloClient = new ApolloClient({
    link: httpLink,
    cache,
});

VueJS Web Application

Once the client has been constructed it can be attached to the frontend library of choice. The Apollo Client library has built-in support for ReactJS and multiple third-party integrations to connect to other popular frameworks. In this example, we will be using the Vue Apollo library by Guillaume CHAU @Akryum. This library attaches the GraphQL client to the VueJS instance and allows for the use of Components as well as custom component options. It also exposes the $apollo object to the Components which is a reference to the Apollo Client object

1
2
3
4
5
6
7
8
const apolloProvider = new VueApollo({
    defaultClient: apolloClient,
});

export {
    apolloClient,
    apolloProvider,
};

To then use the GraphQL provider the following can be done.

For a mutation:

1
2
3
4
5
6
7
8
9
10
11
12
const result = await this.$apollo.mutate<MutationResult, MutationVariables>({
  mutation: gql`mutation($author:String! $title: String!) {
    insert_book(object: {author: $author title: $title}) {
      author
      title
    }
  }`,
  variables: {
    title: this.title,
    author: this.author,
  }
});

For a query

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component({
  apollo: {
    book: {
      query: gql`query($title:String!) {book(title:$title){title author}}`,
      variables() {
        return {
          title: this.$route.params.title,
        }
      }
    },
  }
})
export default class Details extends Vue {
}

Links

GraphQL

Apollo GraphQL

Hasura GraphQL Engine

Example Project

This post is licensed under CC BY 4.0 by the author.