DEV Community

Vishnu Sivan
Vishnu Sivan

Posted on

Getting started with GraphQL

In a RESTful world, we should be aware of API versioning, over-fetching, waterfall requests, and so on. GraphQL is an open-sourced query language and a server-side runtime. It is designed to create fast, flexible, and developer-friendly APIs. It uses queries to read data and mutations to develop, update, and delete data.

Getting Started

We have been using REST to build APIs for a long time. In a RESTful world, you might face some challenges like:

  • API versioning
  • A lot of endpoints
  • Over and under fetching of information
  • Waterfall requests etc. GraphQL is the simplest solution for the above challenges. GraphQL has become a new standard for API development. It is a query language for APIs which enables users to specify their requirements and fetches nothing more, nothing less.

GraphQL

GraphQL is a query language and server-side runtime for APIs. As an alternative to REST, it lets developers to create requests that pull data from multiple data sources in a single API call. It is incredibly effective for building modern mobile and web applications. It requires less branching than REST. It ensures that the application loads only the relevant data, even if it’s from multiple sources.

Features

  • Strongly typed schema
    We can specify all data types (such as Boolean, String, Int, Float, ID, Scalar) supported by the API in a schema in GraphQL Schema Definition Language (SDL). It makes GraphQL less error-prone, more validated, and provides auto-completion for supported IDEs.

  • No over fetching or under fetching
    Over-fetching occurs when the response fetches more than what is required. Under-fetching occurs when the response doesn’t fetch adequate data in a single API request and needs additional API requests to get the related or referenced data.

In GraphQL, you can specify the exact fields that you want to fetch. It fetches the required fields in a single request and solves the issues of over-fetching and under-fetching.

  • Saves time and bandwidth
    GraphQL allows us to make multiple resource request in a single query call. It saves a lot of time and bandwidth by reducing the number of requests to the server. It also helps to reduce waterfall network requests.

  • Schema stitching for combining schemas
    Schema stitching is used to combine multiple schemas into a single schema. In a microservices architecture, each microservice can define its own GraphQL schema, and combine into a single unit that is accessed by the client.

  • No versioning
    In REST architecture, maintaining version is a common practice as developers create new versions due to resource change or structural changes in request/response. Whereas in GraphQL, there is no such requirements where you can add or remove fields, but the resource URL remains the same.

  • GraphQL vs REST
    In REST APIs, you typically have a specific endpoint that returns an entire block of data as a JSON response, which needs to be parsed. Whereas in GraphQL, you use schema, queries, and resolvers for requesting a specific data and not just an entire block. It does not require the data to be parsed or send multiple requests to multiple resources.
    GraphQL vs REST

Fundamentals of GraphQL

Let’s discuss some of the fundamentals of GraphQL such as schema, types, queries, mutations and subscriptions.

Schema

A schema defines the type of the GraphQL API such as objects, fields, relationships that a client can access. Each request from the client is validated and executed against the schema. You can use the buildSchema function to build a Schema object in GraphQL.

In the GraphQL, a user with an id, name, and age can represents as below,

type User {
  id: ID!
  name: String!
  age: Int
}
Enter fullscreen mode Exit fullscreen mode

Types

You can define different types inside buildSchema like type Query {...} and type Mutation {...}. type Query {...} holds functions mapped to GraphQL queries which is used to fetch data (equivalent to GET in REST). type Mutation {...} holds functions mapped to mutations which is used to create, update, or delete data (equivalent to POST, UPDATE, and DELETE in REST).

You can return a user and an array of users of type Person, who has an id, name, age as below:

// Initialize a GraphQL schema
var schema = buildSchema(`
  type Query {
    user(id: Int!): Person
    users(age: Int): Person
  },
  type Person {
    id: Int
    name: String
    age: Int
  }
`);
Enter fullscreen mode Exit fullscreen mode

Queries

Queries describe how you’re going to get data. It returns only the data you have specified.

You can get a user of type Person with id and age as below:

type Query {
    user(id: Int!): Person
    users(age: Int): Person
}
Enter fullscreen mode Exit fullscreen mode

Resolvers

Resolver is designed to map the operation to an actual function.

For example, you have a user object inside type Query and map it to a function with the same name inside root as follows,

// Sample users
var users = [
  {
    id: 1,
    name: 'Vishnu',
    age: '27'
  },
  {
    id: 2,
    name: 'Amal',
    age: '26'
  },
];
// Return a single user
var getUser = function(args) {
  // ...
}
// Return a list of users
var retrieveUsers = function(args) { 
  // ...
}
// Root resolver
var root = { 
  user: getUser,  // Resolver function to return user with specific id
  users: retrieveUsers
};
Enter fullscreen mode Exit fullscreen mode

Aliases

Aliases enables us to rename the data that is returned in a query’s results. It does not change the original schema but display it in accordance with the specifications. It is useful when you have more than one top level field in the same query with different arguments.

query getUsers {
  male: users(gender: "MALE") {
    id
    name
    age
  }
  female: users(gender: "FEMALE") {
    id
    name
    age
  }
}
Enter fullscreen mode Exit fullscreen mode

Fragments

Fragments are reusable units which enables to construct sets of fields and include them in queries wherever required.

To create a user fragment, use the following code snippet,

query getUsersWithFragments($userAID: Int!, $userBID: Int!) {
  userA: user(id: $userAID) {
    ...userFields
  },
  userB: user(id: $userBID) {
    ...userFields
  }
}
fragment userFields on Person {
  name
  age
}
Enter fullscreen mode Exit fullscreen mode

In the above code, a fragment called userFields is created to retrieve user information. You will receive the following output while running the above code:

{
  "data": {
    "userA": {
      "name": "Vishnu",
      "age": 27
    },
    "userB": {
      "name": "Amal",
      "age": 26
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Directives

Directives enable you to dynamically change the structure of your queries using variables. Sometimes, you might skip or include some fields without altering the schema in your query. For which, you can use the following directives.

  • @include(if: Boolean)- Includes the field if true.
  • @skip(if: Boolean)- Skips the field if true. For instance, if you want to retrieve users who are fans of Vishnu with their ids but you don’t require their age field, follow the below code snippet.
query getUsers($age: Boolean!, $id: Boolean!) {
  users(age: $age){
    ...userFields
  }
}
fragment userFields on Person {
  id @include(if: $id)  
  name
  age @skip(if: $age)
}
Enter fullscreen mode Exit fullscreen mode

Provide the following content in the variables panel on the web interface:

{
  "shark": "Vishnu",
  "age": true,
  "id": true
}
Enter fullscreen mode Exit fullscreen mode

You will obtain the following output:

{
  "data": {
    "users": [
      {
        "name": "Vishnu",
        "id": 1
      },
      {
        "name": "Amal",
        "id": 2
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

Mutations

Mutations is a GraphQL operation which deals with creation, deletion, and updation of data.

For instance, you require to update a user with id == 1 and change their age and name. Update the user schema with a mutation type as follows,

// Initialize a GraphQL schema
var schema = buildSchema(`
  type Query {
    user(id: Int!): Person
  },
  type Person {
    id: Int
    name: String
    age: Int
  }
  # newly added code
  type Mutation {
    updateUser(id: Int!, name: String!, age: String): Person
  }
`);
Enter fullscreen mode Exit fullscreen mode

Add a updateUser function to update the user details:

// Update the user details
var updateUser = function({id, name, age}) {
  users.map(user => {
    if (user.id === id) {
      user.name = name;
      user.age = age;
      return user;
    }
  });
  return users.filter(user => user.id === id)[0];
}
Enter fullscreen mode Exit fullscreen mode

Update the root resolver with relevant resolver functions as follows:

// Root resolver
var root = { 
  user: getUser,
  users: retrieveUsers,
  updateUser: updateUser 
};
Enter fullscreen mode Exit fullscreen mode

Assuming these are the initial user details:

{
  "data": {
    "user": {
      "name": "Vishnu",
      "age": 27,
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Add the following query to the input panel on the web interface:

mutation updateUser($id: Int!, $name: String!, $age: String) {
  updateUser(id: $id, name:$name, age: $age){
    ...userFields
  }
}
fragment userFields on Person {
  name
  age
}
Enter fullscreen mode Exit fullscreen mode

Provide the following content in the variables panel on the web interface:

{
  "id": 1,
  "name": "Codemaker",
  "age": "27"
}
Enter fullscreen mode Exit fullscreen mode

You will obtain the following output:

{
  "data": {
    "updateUser": {
      "name": "Codemaker",
      "age": 27
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The user with the id == 1 has been updated from Vishnu (age 27) to Codemaker(age 27).

Develop your first GraphQL API

We have discussed the fundamentals of GraphQL so far. It’s time to create our first GraphQL api with node.js and express.

Install dependencies

We require node.js to be installed in the machine to start with GraphQL.

Setting up the environment

  • Create a first-graphql-api directory where we can hold our apis.
mkdir first-graphql-api
cd first-graphql-api
Enter fullscreen mode Exit fullscreen mode
  • Initialize new npm project using the following command,
npm init -y
Enter fullscreen mode Exit fullscreen mode

npm init

  • Create a index.js file which will contain the server code.
  • Install the required packages graphql, express and express-graphql to the project using the following command,
npm install graphql express express-graphql
Enter fullscreen mode Exit fullscreen mode

npm install

  • Open index.js file and add the following code to it,
var express = require('express');
var { graphqlHTTP } = require('express-graphql');
var { buildSchema } = require('graphql');
const port = process.env.PORT || 4000
// Initialize a GraphQL schema
var schema = buildSchema(`
  type Query {
    hello: String
  }
`);
// Root resolver
var root = { 
  hello: () => 'Hello world!'
};
// 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(port, () => {
  console.log(`App running on port ${port}.`)
})
Enter fullscreen mode Exit fullscreen mode
  • Add a start command on scripts in the package.json as follows,
"start": "node index.js"
Enter fullscreen mode Exit fullscreen mode

node index

The final project structure should look like the following,
project structure

  • Start the node server using the following command,
npm start
Enter fullscreen mode Exit fullscreen mode

npm start

  • Open the link localhost:4000/graphql on a web browser. You will see a Welcome to GraphiQL web interface.

GraphiQL

The GraphiQL interface is mainly divided into two. The left panel is used to enter the queries and query variables. The right panel is used to display the executed query results. The play button located on the top left corner is used to execute queries.

Execute your GraphQL Apis

1. GraphiQL web interface

Let’s add a query { hello } in the left panel and click on play button. You will get the result hello world! in the right panel (shown in the above figure).
We have tried the GraphiQL web interface so far. Now, we can get the results via an api call without GraphiQL interface. For which, set graphiql value to false in the index.js file.
graphiql

If you run the server again and hit the graphql api endpoint, you will get the following error,

error

This is because we haven’t yet provided the query string in the api.

2. Using CURL

To send a HTTP POST request to the endpoint with express-graphql, pass the GraphQL query as the query field in the JSON payload.

For example, if the code is running on an Express GraphQL server and the endpoint is http://localhost:4000/graphql, we can add query { hello } query as the payload in the curl command as follows:

curl -X POST -H "Content-Type: application/json" -d "{\"query\": \"{ hello }\"}" http://localhost:3001/graphql
Enter fullscreen mode Exit fullscreen mode

If you execute the above command on a terminal, the output is returned as JSON and is as follows:

{"data":{"hello":"Hello world!"}}
Enter fullscreen mode Exit fullscreen mode

output

If you prefer to use a GUI to test your queries, use clients such as GraphiQL and Insomnia.

3. From web browser console

To send GraphQL queries from the browser.

  • Open the URL http://localhost:4000/graphql on a browser.
  • Open a developer console (Press F12 in Google Chrome).
  • Paste the following code in the console window,
fetch('/graphql', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Accept': 'application/json',
  },
  body: JSON.stringify({query: "{ hello }"})
})
  .then(r => r.json())
  .then(data => console.log('data returned:', data));
Enter fullscreen mode Exit fullscreen mode

output2

4. Postman

To execute GraphQL queries using Postman, type the URL in the input field and provide the query in the body.
output3

5. From an HTML file

To get response in the frontend using fetch api, create a file named fetch_demo.html and add the following code to it.

<!DOCTYPE html>
<html>
    <body>
        <h2>GraphQL fetch demo</h2>
        <p>This example returns the response from the server using GraphQL API</p>        
        <p id="demo"></p>        
        <script>
            function fetchData() {
                fetch('http://localhost:4000/graphql', {
                    method: 'POST',
                    headers: {
                    'Content-Type': 'application/json',
                    'Accept': 'application/json',
                    },
                    body: JSON.stringify({query: "{ hello }"})
                })
                    .then(r => r.json())
                    .then(data => { document.getElementById("demo").innerHTML = "Response: " + JSON.stringify(data); console.log('data returned:', data) });
                return res
            }
            fetchData()
        </script> 
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode
  • You can either put the file in a server or bind the file in the same express server that we are using for creating the GraphQL apis.
  • Add an Api in the index.js file to render the HTML file as follows,
app.get('/graphql-fetch', function(request, response){
    response.sendFile("fetch_demo.html", { root: '.' });
});
Enter fullscreen mode Exit fullscreen mode
  • Hit the graphql-fetch api on your browser to see the following response, output4

Thanks for reading this article.

Thanks Gowri M Bhatt for reviewing the content.

If you enjoyed this article, please click on the heart button ♥ and share to help others find it!

The full source of the demo and this tutorial is available on

codemaker2015/first-graphql-api: Getting started with GraphQL. First GraphQL api with node.js and express (github.com)

Here are some useful links,

Top comments (0)