DEV Community

Jamiebones
Jamiebones

Posted on

Getting Started With GraphQL and Apollo-Server

GraphQL is a query language used to query and retrieve data. It is an alternative to REST and it gives the ability to describe our data as types since it is a strongly-typed language. We describe a schema and query for the exact data or portion of data we want.

Describe your data (Schema):

 type Student {
   studentId: ID!
   name: String
   sex: Male
   courses: [Courses]
 }
Enter fullscreen mode Exit fullscreen mode

Ask for what you want:

 {
   student(name: "Ada"){
      name
      sex
   }
 }
Enter fullscreen mode Exit fullscreen mode

Retrieve predictable result:

{
  student: {
    name: "Ada",
    sex: "Female"
   }
}
Enter fullscreen mode Exit fullscreen mode

Schema

We define a schema to represent the shape of our data and queries. A schema can be liken to a blueprint which describes the different types, queries and mutation allowed by our GraphQL server.The GraphQL engine validates any incoming request against our defined schema.

  //example schema 
  type User {
    name: String!
    userId: ID!
    sex: String!
  }
Enter fullscreen mode Exit fullscreen mode

In the above example we have a User type that consist of the name, userId and sex. fields. These fields resolves to a GraphQL built-in scalar type of String, ID and String respectively.

Fields that resolve to a single object and cannot have sub-selection in the query is refer to as a scalar type.

  query {
      getUsers{
         name
         userId
         sex
      }
   }
Enter fullscreen mode Exit fullscreen mode

The above query field getUsers resolves to a concrete data of scalar type of String, String and String for the name, userId and sex fields. These fields do not have a further sub-selection hence the name as scalar. The scalar type represent the leaves of the query.

  query {
    allBooks{
      title #scalar type of string
      authors{ #non scalar it has a further sub-selection
        name
      }
   }
  }
Enter fullscreen mode Exit fullscreen mode

The above example is a sample query with a field of allBooks which resolves to a scalar type of string for the title ( no sub-selection) and an authors fields which has a further selection of name of the author.

Some examples of GraphQL scalar types are:

  • String: A UTFโ€8 character sequence.
  • Int: A signed 32โ€bit integer.
  • Float: A signed double-precision floating-point value.
  • Boolean: true or false.
  • ID : This is a unique identifier like a database key

Query and Mutation types

A GraphQL server has a Query and a Mutation type. These are special types that define the entry point of every GraphQL query.

  type Query {
     #all the fields of the query object are listed
    allStudents: [User]
    student(studentId: String): User
  }

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

The above describe a GraphQL User type and queries that can be performed on the server. The field allStudents in the Query object resolves and returns a list of User object while the student returns a single User object. How does the GraphQL system know how to get the data for these fields?

Resolvers

A resolver is a user defined function that resolves the fields of a type with data. Looking at the Query object; it consists of two fields which are allStudents and student. We will need to define two resolvers function with the same name to pull out the data from our database or store.

//resolvers
  Query{
    allStudents:() => {
     //this function should return a list of users
    },

    student:() => {
      //this should return a single user
    }
  }
Enter fullscreen mode Exit fullscreen mode

Data can be retrieved from any data source inside a resolver function as GraphQL does not care about the database or source you are using. You only need to point it to the resolver function which it calls to retrieve the data.

Apollo Server

Lets see an implementation of GraphQL using Apollo Server.

We can build a production ready GraphQL server using Apollo Server.

Create a new NodeJS project

  • make a new directory by typing:
  mkdir graphql_tutorial
Enter fullscreen mode Exit fullscreen mode
  • navigate into the new directory by typing:
 cd graphql_tutorial
Enter fullscreen mode Exit fullscreen mode
  • Navigate to your project directory and type the command
 npm init --yes
Enter fullscreen mode Exit fullscreen mode

The above assumes that NodeJS is installed on your machine.

  • create a new file named index.js
 touch index.js
Enter fullscreen mode Exit fullscreen mode
  • create another file named data.js This will serve as the data store to query against.
//data.js file content
 const StudentData = [
  {
    studentId: "1",
    name: "John Parson",
    sex: "Male",
    booksCollection: ["1", "2"]
  },
  {
    studentId: "2",
    name: "Pokemon Geshia",
    sex: "Female",
    booksCollection: ["1"]
  },
  {
    studentId: "3",
    name: "Samson Loken",
    sex: "Male",
    booksCollection: []
  },
];

const BooksData = [
  {
    bookId: "1",
    title: "The Law Of The Land",
    authors: [
      { authorId: "1", authorName: "John Mason" },
      { authorId: "2", authorName: "Favour Damen" },
    ],
  },

  {
    bookId: "2",
    title: "The Next day is Close",
    authors: [{ authorId: "1", authorName: "John Mason" }],
  },
];

const AuthorData = [
  {
    authorId: "1",
    name: "John Mason",
    sex: "Male",
    address: "East West Lane",
    books: ["1", "2"]

  },

  {
    authorId: "2",
    name: "Favour Damen",
    sex: "Female",
    address: "Best Avenue Drive Zone",
    books: ["1"]
  },
];
module.exports = { StudentData, AuthorData, BooksData}
Enter fullscreen mode Exit fullscreen mode

Copy the above code block and dump inside the data.js file.

  • install dependencies
npm install apollo-server-express apollo-server-core express graphql
Enter fullscreen mode Exit fullscreen mode

Run the above command on the terminal to install the packages needed to create the GraphQL server.

  • install nodemon to auto run our code
 npm install --save-dev nodemon
Enter fullscreen mode Exit fullscreen mode
  • open the package.json file and under the scripts section add a start script that will run the server.
  {
  "name": "graphql_tutorial",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "apollo-server-core": "^3.9.0",
    "apollo-server-express": "^3.9.0",
    "express": "^4.18.1",
    "graphql": "^16.5.0"
  },
  "devDependencies": {
    "nodemon": "^2.0.18"
  }
}
Enter fullscreen mode Exit fullscreen mode
  • create two files inside the project namely schema.js and resolver.js
  touch schema.js
  touch resolver.js
Enter fullscreen mode Exit fullscreen mode
  • open the index.js file and put the content below inside.
const { ApolloServer } = require('apollo-server-express');
const { ApolloServerPluginDrainHttpServer } = require('apollo-server-core');
const express =  require('express');
const http = require('http');
const resolver = require("./resolvers");
const schema = require("./schema");

async function startApolloServer(typeDefs, resolvers) {
  const app = express();
  const httpServer = http.createServer(app);
  const server = new ApolloServer({
    typeDefs,
    resolvers,
    csrfPrevention: true,
    cache: 'bounded',
    plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
  });

  await server.start();
  server.applyMiddleware({ app });
  await new Promise(resolve => httpServer.listen({ port: 5000 }, resolve));
  console.log(`๐Ÿš€ Server ready at http://localhost:5000${server.graphqlPath}`);
}
startApolloServer(schema, resolver);
Enter fullscreen mode Exit fullscreen mode

At the top we required Apollo Server and other boiler plate code needed to run the server. Next, we defined an async function called startApolloServer which receives as an argument the typeDefs and resolvers (schema and resolvers). We are going to define the schema and resolver for our Graph and pass it as an argument to the startApolloServer function. The server is available on http://localhost:5000/graphql

Creating the Schema

We are going to write our schema inside of the schema.js we created earlier. Our schema will consist of a User type and some queries and mutation.

const { gql } =  require("apollo-server-express");

  module.exports = gql` 
   type Query {
      allUsers: [User]
      user(studentId: String):User
   }
   type User {
    studentId: ID!
    name: String!
    sex: String!
 }
`
Enter fullscreen mode Exit fullscreen mode

The above schema describes a User type and two queries field of allUsers and user. At the top of the file we required gql from the apollo-server-express. The schema is written inside the template string of gql. The content of the file is exported for use using module.exports.

We will need to create a resolver function for the fields of allUser and user.

Open the file resolver.js which was created earlier and let's update it with our resolver function.

const { StudentData, BooksData, AuthorData } = require("./data");


module.exports = {
  Query: {
    allUsers: (parent, args, context, info) => {
      return StudentData;
    },
    user: (parent, args, context, info)) => {
       const user = StudentData.find(student => 
       student.studentId == args.studentId);
     return user;
    }

  },
Enter fullscreen mode Exit fullscreen mode

A resolver accepts four positional arguments which are :

  • parent:
  • args:
  • context:
  • info You can read more about them in the documentation Here

The above resolves retrieves data from our static data file data.js. The allUsers resolvers do not take any arguments and return all the list of students. If we are pulling data from a database, this is where we would have performed the queries to retrieve the data. The return value must conform to the schema described.

Lets start and execute the query we just wrote:
Run the server:
At the terminal run:

 npm run start
Enter fullscreen mode Exit fullscreen mode

This will start up the server and you will see a message on the terminal : ๐Ÿš€ Server ready at http://localhost:5000/graphql

Open your browser and navigate to http://localhost:5000/graphql. You will be presented with a playground where you can test your queries and mutation.

Image description

Click on the explorer tab to open a new window for you to write out your query.

query{
  allUsers {
    sex
    studentId
    name
  }
}
Enter fullscreen mode Exit fullscreen mode

Paste the above query inside the tab and click on the Run button. The above query is executed against your server and the result is displayed.

{
  "data": {
    "allUsers": [
      {
        "sex": "Male",
        "studentId": "1",
        "name": "John Parson"
      },
     <--- rest of the result -->
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

Apollo server will return a data object with the result of the query. The result of the allUsers query is an array with the list of the user object (sex,studentId and name). If you look at the file data.js you will noticed that the StudentData also consist of a booksCollection field which is an array of strings. You had notice that the returned data does not contain this field. This is because the booksCollection field is not defined inside our schema.

Let's write a query to get a particular user. The user query requires we pass in a studentId which will be used to fetch the user data.

 query($studentId: String){
  user(studentId: $studentId) {
     name
     studentId
  }
}
Enter fullscreen mode Exit fullscreen mode

At the top we defined a studentId variable that is of type string and we passed the value of the variable to the user field.

Image description

The variables are entered below the query tab in the circled location of the image above. Run the query and put in a studentId that existed in the data file. The query runs and we get our result.

Let's introduce some new types into our schema. We want to add a Book and an Author type to the schema. We will want to be able to query for the books owned by a student. In the StudentData array in the data.js file, we have a booksCollection which is a list of books by ID owned by a user.

Open the schema.js file and add the following code to it.

type User {
   #previous fields above
    booksCollection: [Book] #added the field booksCollection
 }

 type Book {
    bookId: ID!
    title: String!
    authors: [Author] 
 }
 type Author {
    authorId: ID!
    name: String
    sex: String
    address: String
 }
Enter fullscreen mode Exit fullscreen mode

We have added a Book type and an Author type to the schema as well as extend the User type by adding a booksCollection field. Looking at the new types added, we can notice that the User type has a field called booksCollection which resolves into a list of Book.

The Book type has a field called authors that resolves into a list of Author type. We have to tell our GraphQL how to retrieve the values for these listed fields. That is where our resolvers come in, we will need to add functions that the GraphQL server calls when it encounters these fields so as to resolve them.

Open the file resolver.js and let's add the resolvers for these field.

{
//resolver.js
//rest of the resolvers file

 Book: {
    authors: (book) => {
      const authors = AuthorData.filter((author) => {
        return author.books.includes(book.bookId);
      });
      return authors;
    },
 },
User: {
    booksCollection: (parent) => {
        const student = StudentData.find(s => s.studentId == parent.studentId);
        const booksArray = student?.booksCollection;
        const collection = [];
        booksArray?.map((bookId) => {
            const book = BooksData.find(e => e.bookId == 
   bookId);
            collection.push(book);
        });
        return collection;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

In the Book type we have a field called authors that we will need to write a function for so as to retrieve the authors of a book. Remember that a resolver function takes 4 positional arguments of which the first is refer to as the parent of the field, we are resolving. In the case of the authors resolver the parent is a book field that we want to resolve its list of authors.

The authors resolver returns a list of authors from the AuthorData array where the book.bookId is included in the author.books property of the AuthorData array. It simply means, return all the list of authors that wrote the particular book.

Same above also applies to the booksCollection field of the User type which is resolved by finding the user by the studentId of the parent. The parent is the User collection. (we could as well name parent as user ). Once the user is found, the books owned by the user is retrieved by mapping over the student.booksCollection array. For each item, in the array, we retrieve the corresponding book and save it into another array called collection. Once we are done with the loop, we return the collection array.

Let's run another query to retrieve a particular user and their collection of books.

  query($studentId: String){
  user(studentId: $studentId) {
     name
     studentId
     sex
     booksCollection {
       title
       authors {
         address
         sex
         authorId
         name
       }
     }
  }
}
Enter fullscreen mode Exit fullscreen mode

Put a valid value for the studentId. The GraphQL server runs the query and makes use of the defined resolver above and returns a user with the complete list of all the books owned.

We have seen how to retrieve data using the query object of GraphQL. Now, let us see how to mutate or change data in our data store.

Mutation

To change data in our server, we will add the Mutation type. This type has fields which when resolved performs some changes to the data store.

Open the schema.js file and lets add the Mutation type.

#rest of the code above
type Mutation {
    saveUser(studentId: ID!, name: String!, sex: String!, 
    collection: [String]): UserDataSaved
}

interface MutationResponse  {
    code: String!
    success: Boolean!
    message: String!
 }

 type UserDataSaved implements MutationResponse {
    code: String!
    success: Boolean!
    message: String!
    user: User
 }
#rest of the code below
Enter fullscreen mode Exit fullscreen mode

Add the above code after the Query type. Looking closely at the added code, you will notice that we added an interface defined which we call MutationResponse. An interface in GraphQL is similar to interfaces in other programming language. It is a contract that an implementing type must execute, meaning any type that implement this particular interface must define a code, success and message field.

We defined a UserDataSaved type that implements the MutationResponse. To fulfil the interface contract we added the fields of the interface to the type and also added the user field which is the new user added.

When making a mutation, it is recommended to return the new object that was added or changed in the response.

The Mutation type has a field called saveUser which accepts four variables and it resolves with the UserDataSaved type. Let us write the resolver function for the saveUser

Open the resolver.js file and create a Mutation object:

   Mutation: {
    saveUser: (_, { studentId, name, sex, collection }) => {
      const newuser = {
        studentId,
        name,
        sex,
        collection
      };
      StudentData.push(newuser);
      return {
        code: "200",
        success: true,
        message: "New user record saved",
        user: newuser
      }
    },
  },
Enter fullscreen mode Exit fullscreen mode

The saveUser resolver accepts as argument, the studentID,name, sex and collection as arguments. Remember that the second positional arguments of the resolver function is the args object that is passed to the resolver. We destructured the args object to retrieve the studentId, name, sex and collection. We created a newUser variable using these values and we push it into the StudentData array. we return an object which matches our schema as response to the mutation.

Let add a new user via the Apollo playground.

 mutation($studentId: ID!, $name: String!, $sex: String!, $collection: [String]){
  saveUser(studentId: $studentId, name: $name, sex: $sex, collection: $collection) {
    code
    message
    success
    user {
      name
      studentId
      booksCollection {
        authors {
          address
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

After running the query, we get a response back showing that the user has been added to our student list. Note that the added user is to persisted to file. We can query for our newly added user.

We have just covered the basics of GraphQl, in the next post we will explore further and learn more about GraphQL. The code for this tutorial can be found Here

Thanks for reading!

Top comments (1)

Collapse
 
andrewbaisden profile image
Andrew Baisden

Great GraphQL article.