A Beginner's Guide to Building Powerful APIs with GraphQL

A Beginner's Guide to Building Powerful APIs with GraphQL

An API(Application Programmable Interfaces) is a way that enables computer programs to communicate easily with each other, APIs are used for performing a lot of functions which include Data Exchange, Automation and Integrating your application with other systems.

GraphQL Is a language that can be used for Data Query and manipulation in APIs. GraphQL has made a lot of work easier for developers like improved API performance, and flexibility, in the sense that it allows the client to request only the data it needs and also it is strongly typed making it easier to catch errors.

In this lesson, you are going to learn how to query and manipulate data using GraphQL, I guess you are ready😉

Prerequisites

This service will be built with Node.js and Express.js, Even though you don't need to know Node.js having an understanding of it is an advantage.

  • Code Editor, I am using VsCode

  • A database, I'll be using MongoDB

  • Understanding of Javascript

Getting Started

Setting up codebase

In the directory you want to build the project you can type this in your terminal to create the folder

mkdir graphql-lesson

Then Open the folder with VsCode with this command below

cd graphql-lesson
code .

We want to create a package.json file you can do that quickly by running this command in your terminal

npm init -y

Since we'll be using ES6 throughout this lesson we'll need to add type:module in the package.json file.

We need to create all files and folders we'll be using to build this project, You can easily do that by running this command in your terminal.

mkdir model
touch index.js resolvers.js typeDefs.js model/user.js connect.js

And your folder should look like this :

Installing Dependencies

We need to install some dependencies, you can easily do this by running the command below in your terminal

npm install dotenv bcryptjs express express-graphql graphql mongoose nodemon @graphql-tools/schema

In the scripts section in package.json we are going to add this code :

"scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js"
}

We want our server to automatically restart when we make changes, which is the main reason why i added "dev" with the value set to "nodemon index.js".

Creating server

We want to create our server with express.js, But before doing that we want to make sure we connect files that include database connection, Type definitions and resolvers.

Connecting to database

We'll be connecting to our database where users details will be stored, you can easily do this by copying this code below into the connect.js file :

import express from "express";
import mongoose from "mongoose";
import "dotenv/config";
const app = express();

mongoose.set("strictQuery", false);

mongoose
  .connect(process.env.MONGO_URI_LOCAL, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  })
  .then(() => console.log("DB Connected!"))
  .catch((err) => {
    console.log("not able to connect to database"+ err);
  });

export default app;

Connecting Type Definition file

Type definitions in GraphQL define the structure of your data and the operations that can be performed on that data.

Since we will be needing type definitions in GraphQL, we want to set up an example code in the typeDefs.js file, you can easily do that by copying the code below :

import { buildSchema } from 'graphql';

export const typeDefs = buildSchema(`
  type Query {
    message: String
  }
`
);

Connecting Resolvers file

Resolvers in GraphQL are functions that resolve the data for a single field in a query or mutation.

Since we will be needing resolvers, we want to set up an example code in the resolvers.js file, you can easily do that by copying the code below :

export const resolvers = {
    Query : {
        message : () => 'Hello!'
    }
}

Creating a server with express.js

Now we want to create our server with express.js, You can do that easily by copying this code below into the index.js file :

import express from 'express';
import { graphqlHTTP } from 'express-graphql';
import "./connect.js";
import { typeDefs } from './typeDefs.js';
import { resolvers } from './resolvers.js';
import { makeExecutableSchema } from '@graphql-tools/schema';
const app = express();

const schema = makeExecutableSchema({
  typeDefs,
  resolvers,
});

app.use('/graphql', graphqlHTTP({
    schema: schema,
    graphiql: true,
}))

app.listen(8000, () =>{
    console.log(`Running a GraphQL API server at localhost:8000/graphql`)
})

You can start your server with the npm run dev command, your graphQL playground should be accessible at http://localhost:8000/graphql.

Adding secret variables to .env file

We need to add Secret variables to the dotenv(.env) file, firstly you can create the file with this command touch .env , Then get your MongoDB Local URI from Mongo Atlas connection string section, Your Local URI should be similar to this below :

MONGO_URI_LOCAL=mongodb://127.0.0.1:27017/your_database_name

Creating user model

The Next thing we want to do now is create a user model, this is the blueprint of how the user data will be stored, You can easily copy this code below

import mongoose from "mongoose";
const Schema = mongoose.Schema;

const UserSchema = new Schema({
    email: {
        type: String
    },
    name: {
        type: String
    },
    about: {
        type: String
    },
    password: {
        type: String
    }
})

export default mongoose.model('User', UserSchema);

Creating a new user with Graphql

We want to create a new user, These are the essential requirements for creating a new user :

  1. Input Type: This is a type that allows you to pass arguments to a field, It is simply the blueprint of the input details that will be collected from the user.

  2. Object Type: As I explained above it defines the structure of your data.

  3. Mutation Type: This is used to define GraphQL operations, It contains Mutations that can take input arguments and then send a return type.

  4. Resolver: This is a function that is used to resolve the data for a single field

Now Let's start according to the essentials above

Creating Input type

As explained above, The Input object type is a blueprint of input details that will be collected from the user. We want to collect the name, email, password and about from the user and also name the input type UserInput, you can do that easily by defining a new type with the "input" keyword and then all the fields you want to collect and their types. This will be our UserInput :

input UserInput {
      name: String!
      email: String!
      password: String!
      about: String
    }

You should insert it into typeDefs variable in typeDefs.js file

Creating user object Type

An object type is a data type that represents an object, it consists of fields that define the properties of an object, It defines the structure of the data that can be returned in a graphQL API. We want our User type to be able to return the id, name, email and about of a user. Our graphQL API will be able to return all these but what a user receives is dependent on the field they insert in the query. don't fret I'll show you what I mean by that soon.

You can create the user type easily by copying this code below :

type User {
    _id: ID!
    name: String!
    email: String!
    about: String
  }

And then you should insert it into typeDefs variable in typeDefs.js file.

Creating a createUser resolver

As explained above a resolver resolves the data for a single field in a query or mutation.

We want to make sure the resolver collects the name, email, password, confirm_password and about from a user then encrypts the password using bcryptjs if it matches confirm password.

Now the first requirement is to specify the operation which is Mutation, you can easily do that by inserting a Mutation object into the resolvers variable in resolvers.js file and then inserting the resolver function in it :

Mutation : {
    createUser : async (_, { input }) => {
            const { name, email, password, confirm_password, about } = input;
            if( password == confirm_password ){
                const encrypted_password = await bcrypt.hash(password, 12);
                const new_user = new User({ name, email, password: encrypted_password, about });
                await new_user.save();
                return new_user;
            }else{
                throw new Error('Password does not match');
            }
        }
}

Creating Mutation type and Mutation operation for createUser

What we want to do now is create a mutation type and then insert the createUser operation which will contain the input argument and then return the user.

You can do that easily by copying the code below and inserting it in the typeDefs variable in typeDefs.js file :

type Mutation {
    createUser(input: UserInput): User
  }

Getting user details with GraphQL

We want a user to be able to send details of a user when an Id is provided, these are the steps needed to accomplish this :

  1. We need to create a query resolver which will be named getUserById, it will collect the Id of a user and then return the user object if found in the database.

  2. We will then create a query operation that is going to take Id as an argument and then return the user type

Creating a resolver for getUserById

As mentioned above this resolver will collect the user id and then return the user if found, you can easily do that by inserting the code below into the query object in the resolvers variable that is in resolvers.js file :

getUserById : async (_, { id }) => {
            const user = await User.findById(id);
            if(user){
                return user
            }else{
                throw new Error('User does not exist');
            }
        }

Creating a query operation for getUserById

Now we need to create a query operation that will accept id as an argument and then return the user type, you can do that easily by copying the code below into the query type in the typeDefs variable which is in the typeDefs.js file :

getUserById(id: ID!): User

Updating User details with GraphQL

What we want to do now is to be able to update a user's details when an id is provided and the field they want to update. To be able to achieve this we are going to follow these steps

  1. We need to create a resolver that will collect user id and input and then update the data, this resolver will be named updateUser.

  2. We need to create a mutation operation that collects the Id and input as arguments and then returns the user type.

Creating a resolver for updateUser

As explained above the updateUser resolver will be taking the id of the user and the field that needs to be updated, you can easily create this resolver by copying the code below :

updateUser: async (_, { id, input }) => {
            const updatedUser = await User.findByIdAndUpdate(id, input, { new: true });
            return updatedUser;
          }

And then insert it into the Mutation object in the resolvers variable which is in resolvers.js file.

Creating a mutation operation for updateUser

What we want to do now is create the updateUser mutation operation, this operation will collect the id, and the field the user wants to update and then return the user type, you can do this easily by copying the code below :

updateUser(id: ID!, input: UserInput!): User!

And then insert it into the mutation type in the typeDefs variable which is in typeDef.js file.

Deleting User with GraphQL

We want to be able to delete a user with GraphQL, these are the steps you'll be following in achieving this.

  1. Create a resolver that will collect the user's Id and then delete the user details, this resolver will be named deleteUser

  2. Create a mutation operation that will accept the User's Id as an argument and then return the user type

Creating a resolver for deleteUser

This resolver function will take the user's Id and then delete the data from the database, This can be done easily by copying the code below :

deleteUser: async (_, { id }) => {
        const deletedUser = await User.findByIdAndDelete(id);
        if (!deletedUser) {
            throw new Error(`User with ID ${id} not found`);
        }
        return deletedUser;
        }

An then inserting it into the Mutation object which is in the resolvers variable in resolvers.js file.

Creating a mutation operation for deleteUser

The next thing we want to do is create the deleteUser mutation operation, it will accept Id as an argument and then return the user type, this can be done easily by copying the code below :

deleteUser(id: ID!): User

And then insert it into the mutation type in the typeDefs variable which is in typeDef.js file.

Testing APIs

Now we want to test the CRUD operation, we'll be using the GraphQL playground which can be accessed through http://localhost:8000/graphql in your browser.

Note: Make sure the server is running.

Create User API

To create a User we will specify the operation we are going to be using which is the mutation operation, and specify the mutation operation name which is createUser, then the argument will contain the input key and all the fields we'll be collecting from the user, It should look like this :

Then to execute query, you'll click on the execute query button that looks like a play button, we should get this as our result :

It means a new user was created successfully.

Get User Details API

To get User details, we will specify the operation we are going to be using which is the query operation, and specify the mutation operation name which is getUserById, then the argument will contain the id and then the detail or details we want to get from the return type. it should look like this :

After executing the query the result should look like this :

Update User Details API

To update user details, we will specify the operation we are going to be using which is the mutation operation, and specify the mutation operation name which is updateUser, then the argument will contain the id and finally the detail we want to get from the return type. it should look like this :

After Executing the query the result should look like this :

Delete User API

To delete a user, we will specify the operation we'll be using which is the mutation operation, and specify the mutation operation name which is deleteUser, then the argument will contain the id and finally the detail we want to get from the return type. it should look like this :

After executing the query the result should look like this :

Conclusion

In conclusion, GraphQL provides a very powerful and efficient way to create CRUD APIs which allows developers to retrieve only the data they need, It helps reduce network round trips, and simplifies API versioning.

The full code is available Here

You've come this far thanks for reading, you can follow me on Twitter, GitHub or LinkedIn for more backend development tips.