Most of us may be very familiar with creating REST APIs. GraphQL is a query language, created by Facebook with the purpose of building client applications based on intuitive and flexible syntax, for describing their requirements and interactions. GraphQL was designed to solve one of the biggest drawbacks of REST-like APIs. A GraphQL service is created by defining types and fields on those types, then providing functions for each field on each type.

Once a GraphQL service is running (typically at a URL on a web service), it can be sent GraphQL queries to validate and execute. A received query is first checked to ensure it only refers to the types and fields defined, then runs the provided functions to produce a result.

In this tutorial, we are going to implement a GraphQL server using Express and use it to learn very important GraphQL features.

GraphQL Features

  • Hierarchical ; queries look exactly like the data they return.

  • Client-specified queries – the client has the liberty to dictate what to fetch from the server,

  • Strongly typed – you can validate a query syntactically and within the GraphQL type system before execution. This also helps leverage powerful tools that improve the development experience, such as GraphiQL.

  • Introspective – you can query the type system using the GraphQL syntax itself. This is great for parsing incoming data into strongly-typed interfaces, and not having to deal with parsing and manually transforming JSON into objects.

Why use GraphQL

One of the primary challenges with traditional REST calls is the inability of the client to request a customized (limited or expanded) set of data. In most cases, once the client requests information from the server, it either gets all or none of the fields.

Another difficulty is working and maintain multiple endpoints. As a platform grows, consequently the number will increase. Therefore, clients often need to ask for data from different endpoints. GraphQL APIs are organized in terms of types and fields, not endpoints. You can access the full capabilities of your data from a single endpoint.

When building a GraphQL server, it is only necessary to have one URL for all data fetching and mutating. Thus, a client can request a set of data by sending a query string, describing what they want, to a server.

Setup

We will begin with a simple file structure and a simple sample code snippet. To follow this tutorial step by step, create a GraphQL folder and inside the folder initialize an npm project using npm init -y. Then create a file server.js which will be the main file. For complete code, clone the repository using the instructions in the attached github repository.

We will install the packages required which will be discussed at each step.

npm i graphql express express-graphql -S   #Install required packages

We will setup a simple server using Express and express-graphql, a HTTP server middleware. Do not worry about the meaning of the code as we will explore it bit by bit in the next steps while adding more content.

var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');

// Initialize a GraphQL schema
var schema = buildSchema(`
  type Query {
    hello: String
  }
`);

// Root resolver
var root = { 
  hello: () => 'Hello !'
};

// Create an express server and a GraphQL endpoint
var app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,  // Must be provided
  rootValue: root,
  graphiql: true,  // Enable GraphiQL when server endpoint is accessed in browser
}));
app.listen(4000, () => console.log('Now browse to localhost:4000/graphql'));

Concepts

So far we have explored some features and advantages of GraphQL. In the this section we will delve into different teminologies and implementations of some technical features in GraphQL. We will be using a simple Express server to paractice our features.

Schema

In GraphQL, the Schema manages queries and mutations, defining what is allowed to be executed in the GraphQL server. A schema defines a GraphQL API’s type system. It describes the complete set of possible data (objects, fields, relationships, everything) that a client can access. Calls from the client are validated and executed against the schema. A client can find information about the schema via introspection. A schema resides on the GraphQL API server.

GraphQL Interface Definition Language (IDL) or Schema Definition Language (SDL) is the most concise way to specify a GraphQL Schema. The most basic components of a GraphQL schema are object types, which just represent a kind of object you can fetch from your service, and what fields it has. In the GraphQL schema language, we might represent it like this:

type User {
  id: ID!
  name: String!
  age: Int
}

In the above snippet we are using buildSchema function which builds a Schema object from GraphQL schema language.

var schema = buildSchema(`
  type Query {
    hello: String
  }
`);

Constructing Types

Inside buildSchema, we can define different types. You might notice in most cases type Query {...} and type Mutation {...}. type Query {...} is an object holding the functions that will be mapped to GraphQL queries; used to fetch data(equivalent to GET in REST) while type Mutation {...} holds functions that will be mapped to mutaions; used to create, update or delete data(equivalent to POST, UPDATE and DELETE in REST).

We will make our schema a bit complex by adding some reasonable types. For instance, we want to return a user and an array of users of type Person who has an id, name ,age and gender properties.

var schema = buildSchema(`
  type Query {
    user(id: Int!): Person
    users(gender: String): [Person]
  },
  type Person {
    id: Int
    name: String
    age: Int
    gender: String  
  }
`);

You can notice some interesting syntax above, [Person] means return an array of type Person while the exclamation in user(id: Int!) means that the id must be provided. users query takes an optional gender variable.

Resolver

A resolver is responsible for mapping the operation to actual function. Inside type Query, we have an operation called users. We map this operation to a function with the same name inside root. We will also create some dummy users for this functionality.

...
var users = [  // Dummy data
  {
    id: 1,
    name: 'Brian',
    age: '21',
    gender: 'M'
  },
  {
    id:2,
    name: 'Kim',
    age: '22',
    gender: 'M'
  },
  {
    id:3,
    name: 'Joseph',
    age: '23',
    gender: 'M'
  },
  {
    id:3,
    name: 'Faith',
    age: '23',
    gender: 'F'
  },
  {
    id:5,
    name: 'Joy',
    age: '25',
    gender: 'F'
  }
];

var getUser = function(args) { ... }  // Return a single user
var retrieveUsers = function(args) { ... } // Return  a list of users
...
var root = { 
  user: getUser,   // Resolver function to return user with specific id
  users: retrieveUsers
};

To make the code more readable, we will create separate functions instead of piling everything in the root resolver. Both functions take an optional args parameter which carries variables from the client query. Let’s provide an implementaion for the resolvers and test their functionality.

...
var getUser = function(args) { // return a single user based on id
  var userID = args.id;
  return users.filter(user => {
    return user.id == userID;
  })[0];
}

var retrieveUsers = function(args) { // Return a list of users. Takes an optional gender parameter
  if(args.gender) {
    var gender = args.gender;
    return users.filter(user => user.gender === gender);
  } else {
    return users;
  }
}
...

Query

query getSingleUser {
    user {
        name
        age
        gender
    }
}

Output

In the diagram above, we are using a an operation name getSingleUser to get a single user with their name, gender and age. We could optionally specify that we need their name only if we did not need the age and gender.

When something goes wrong either in your network logs or your GraphQL server, it is easier to identify a query in your codebase by name instead of trying to decipher the contents.

This query does not provide the required id and GraphQL gives us a very descriptive error message. We will now make a correct query, notice the use of variables/arguments.

Query

query getSingleUser($userID: Int!) {
    user(id: $userID) {
        name
        age
        gender
    }
}

Variables

{ 
    "userID":1
}

Output

Aliases

Imagine a situation where we need to retrieve two different users. How do we identify each user? In GraphQL, you can’t directly query for the same field with different arguments. Let’s demonstrate this.

Query

query getUsersWithAliasesError($userAID: Int!, $userBID: Int!) {
    user(id: $userAID) {
        name
        age
        gender
    },
    user(id: $userBID) {
        name
        age
        gender
    }
}

Variables

{ 
    "userAID":1,
    "userBID":2
}

Output

The error is very descriptive and even suggests use of aliases. Let’s correct the implementation.

query getUsersWithAliases($userAID: Int!, $userBID: Int!) {
    userA: user(id: $userAID) {
        name
        age
        gender
    },
    userB: user(id: $userBID) {
        name
        age
        gender
    }
}

Variables

{ 
    "userAID":1,
    "userBID":2
}

Output

Now we can correctly identify each user with their fields.

Fragments

The query above is not that bad, but it has one problem; we are repeating the same fields for both userA and userB. We could find something that will make out queries DRY. GraphQL includes reusable units called fragments that let you construct sets of fields, and then include them in queries where you need to.

Query

query getUsersWithFragments($userAID: Int!, $userBID: Int!) {
    userA: user(id: $userAID) {
        ...userFields
    },
    userB: user(id: $userBID) {
        ...userFields
    }
}

fragment userFields on Person {
  name
  age
  gender
}

Variables

{ 
    "userAID":1,
    "userBID":2
}

Output

We have created a fragment called userFields that can only be applied on type Person and used it to retrieve users.

Directives

Directives enable us to dynamically change the structure and shape of our queries using variables. At some point we might want to skip or include some fields without altering the schema. The two available directives are:

  • @include(if: Boolean) Only include this field in the result if the argument is true.
  • @skip(if: Boolean) Skip this field if the argument is true.

Let us say we want to retrieve users of gender ‘F’ but skip their age and include their id fields. We use variables to pass in the gender and use directives for the skipping and inclusion functionalities.

Query


query getUsers($gender: String, $age: Boolean!, $id: Boolean!) {
  users(gender: $gender){
    ...userFields
  }
}

fragment userFields on Person {
  name
  age @skip(if: $age)
  id @include(if: $id)
}

Variables

{
  "gender": "F",
  "age": true,
  "id": true
}

Output

Mutations

So far we have been dealing with queries; operations to retrieve data. Mutations are the second main operation in GraphQL which deal with creating, deleting and updating of data. Let’s focus on some examples of how to carry out mutations. For instance, we want to update a user with id==1 and change their age their name and age and return the new user details.

We will update our schema to include a mutation type and also update our root resolver with relevant resolver functions.

// Add mutations
var schema = buildSchema(`
  ...
  type Mutation {
    updateUser(id: Int!, name: String!, age: String): Person
  }
`);
...
var updateUser = function({id, name, age}) {  // Update a user and return new user details
  users.map(user => {
    if(user.id === id) {
      user.name = name;
      user.age = age;
      return user;
    }
  });
  return users.filter(user=> user.id === id) [0];
}
...

var root = { 
  user: getUser,
  users: retrieveUsers,
  updateUser: updateUser  // Include mutation function in root resolver
};

Assuming this are the initial user details.

After a mutation to update the user, we get the new user details.

Query


mutation updateUser($id: Int!, $name: String!, $age: String) {
  updateUser(id: $id, name:$name, age: $age){
    ...userFields
  }
}

fragment userFields on Person {
  name
  age
  gender
}

Variables

{
  "id": 1,
  "name": "Keavin",
  "age": "27"
}

Output

Conclusion

So far we have covered from very basic concepts of GraphQL to some fairly complex examples. Most of these examples clearly reveal the differences between GraphQL and REST for users who have interacted with REST. To learn more about GraphQL, this series of articles
provide a good background for its usage with React and other third party packages from Apollo.

Resources



Source link

LEAVE A REPLY

Please enter your comment!
Please enter your name here