How to Use GraphQL and Sequelize

Editor's note: This is the first part of the series submitted by Benny Ogidan, one of the winners of the Andela Life Hacks Competition for March 2019. Ben is a software developer at Andela.

Background

It is super important to note that this post is not to convince you to start rewriting all your projects in GraphQL or to launch a Trump-style campaign that GraphQL is better than REST API. One of the many misconceptions around GraphQL especially when I started learning, was that if you wanted to implement GraphQL, you would have to re-write your entire backend. What this article aims to do is inform any readers on the benefits of using GraphQL and seeing it being introduced into a simple project.

So just for context purposes, it is probably nice to clarify that GraphQL was created in 2012 by Facebook. It was apparently developed as an alternative to the existing REST standard for structuring server queries. The spec can be traced to as far back as 2012. Initially, the problems facebook experienced were due to constant over-fetching of resources on their native mobile applications, performance got poorer as application logic got more complex due to the limited storage sizes of mobiles. GraphQL overcomes this problem allows the developer to specify the data that is returned. Facebook realized that data fetched continually changes inorganically, and there is a cost we ensure to maintain a separate endpoint with the exact amount of data for each component we make. And sometimes, we have to make a compromise between having loads of endpoints and having the endpoint which fit best with our applications.

GraphQL, according to Facebook, is a query language designed to build client applications by providing an intuitive and flexible syntax and system for describing their data requirements and interactions. GraphQL helps solve some of the problems attributed to REST API by using a Schema-Type system to allow developers to fetch only the data they require from an endpoint. One of the advantages of GraphQL is that you can use in conjunction with existing REST architecture, so that’s good news for folks who do not have to tear down their existing application to accommodate GraphQL.

All the code can be found here. PRs are welcome too.

Introduction

For more in-depth background information about GraphQL, I have added a few links at the end of the article. For this article, I have chosen to use the Apollo server for the backend implementation. This is the point some people will start asking why they keep hearing references to the Greek God of the sun 🤔. (It is also the colloquial name for a rather serious eye infection - and it is at this point I just realized why my parents have been giving me funny looks after they learned I work with 'Apollo.') So “Apollo Server, is a server-side JavaScript GraphQL implementation for defining a schema and a set of resolvers that implement each part of that *schema”. We will go into details of what schemas and resolvers are but for a more high-level explanation basically, Apollo Server is just an implementation of the GraphQL javascript spec with a bunch of libraries that help make coding a whole lot easier. Apollo Server integrates easily with various Node.js HTTP frameworks like Express, Connect, KOA, Hapi, etc. But that’s for another post

At this point, I do have to mention that as a developer that does not work for Apollo there are other implementations out there, some you might even find easier depending on individual use cases. For JavaScript, I do know and have tried GraphQL, Yoga built by Prisma, which is excellent. I also know they just introduced middlewares for their resolvers, which also works with Apollo, so it is excellent that both companies teamed up together so that tools work across the board. Also, if you, like me, like to understand every dependency going into a project, then you are welcome to go and grab the spec found here. As you can see it is available in a lot of languages, but for this post, I will be sticking to JavaScript.

GraphQL Mode of Operation

Schemas

So GraphQL uses only one endpoint at a time to fetch data. This endpoint can contain as little or as much data as we desire to depend on the logic. The way GraphQL works is based on its schemas which are strongly typed. This means in making your definitions in the schema on the shape of data specified, which essentially is the types of each property of the values returned.

type Student {
id: ID!
firstname: String!
lastname: String!
birthdate: Date!
photoUrl: String
hobbies: [Hobbies!]!
}


The schemas have to be explicit due to the flexibility of the one route which is used to send and receive data. If something is specified in the schema but not returned, the response will contain a null for that field. However, if a specific field is not specified but is needed, the client won’t receive it until the schema is updated to include that field.

Queries and Mutations

Schema Definition Language (SDL) is one of the ways we write we can create GraphQL syntax. Queries and mutations are composed of the required fields we need to retrieve from the database in case of queries and for mutations the values we will need to manipulate.

Queries correspond to GET requests in REST API, they consist of logic which indicates the shape the data returned, should be in. This gives the user the ability to explicitly control the shape of the data to be used.

{
getAllStudents
{
firstName
lastName
birthDate
photoUrl
}
}

Mutations, unlike the Queries, correspond to the HTTP verbs that deal with changing of the data PUT, POST, PATCH and other HTTP verbs used to make changes to the data stored on the server.

mutation { createStudent(firstName: "Benny", lastName: "Ogidan", hobbies: ["Swimming"])
{
firstName
lastName
}
}

All CRUD operations are handled by queries and mutation with queries reading the data and mutations manipulating it.

REST API vs GraphQL API illustration

On our implementation, GraphQL will act as a layer on top of the ORM which in turn is a layer on top of the database help.

Setting Up

So first we need to set up an express framework server with a dotenv package to help hide any env variables we utilize in this post. I also have added inbabel as it helps to transpile down to ES5. The app we will be building is a simple application to enter students into a database with hobbies.

mkdir student-list
cd student-list
yarn init -y

  • you can use npm init -y, the debate on the why can be in the comment section

yarn add babel and babel dependencies dotenv sequelize pg pg-hstore apollo-server-express sqlite3 express graphqltouch server.js

So we just added a ton of stuff I will go through the highlights of what they do.

  • Babel — helps us program with up to date ES6 spec
  • Sequelize — Sequelize is a promise-based Node.js ORM for Postgres and other databases. It is what will help us set up and connect to the database. Some people do not like ORMs because of the unnecessary overhead and complexity they may bring along with their queries. However, I feel they do a better job of acting like an abstraction for database logic allowing me to switch out from one database to another which is a trend with modern day applications where requirements can change very quickly.
  • pg- short for Postgres, since we are going to be using a field with array type support I decided to pick Postgres
  • apollo-server-express — This is the Express and Connect integration of GraphQL Server
  • sqlite3- I am a fan of using this in memory server for testing.
  • express- This is a very popular node framework

// server.jsconst express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');

const app = express()
const port = 4000// Construct a schema, using GraphQL schema language
const typeDefs = gql`
type Query {
hello: String
}
`;// Provide resolver functions for your schema fields
const resolvers = {
Query: {
hello: () => 'Hello world!',
},
};const server = new ApolloServer({ typeDefs, resolvers });const app = express();
server.applyMiddleware({ app });app.listen({ port }, () =>
console.log(`🚀 Server ready at http://localhost:ocalhost:${port}${server.graphqlPath}`)
);

I am using the gql helper library to help with the GraphQL syntax. The gql library reads the Graphql syntax in using tagged template literals which allows for interpolation.

If you have executed everything accordingly.

node server.js

You should see this prompt

🚀 Server ready at http://localhost:4000/graphql

Go to this link and the page below should be rendered by Graphcool. Graphcool is an “in-browser IDE for exploring GraphQL” built by Prisma. It’s like an in-app implementation of Postman for GraphQL and it is a big help when testing out queries and mutation very quickly. You can test the query we listed and see what it resolves to.

[caption id="attachment_66055" align="alignnone" width="800"]

graphql

Graphcool implementation of our application[/caption]

So the GraphQL server is up, now for sequelize, to achieve this we need a bit more code. For tutorial purposes, I am going to be using sequeliz-cli

npm install -g sequelize-cli

This helps this gives us the ability to scaffold our project, we can use

node_modules/.bin/sequelize init

This helps us create migrations, models, seeders and config folders. To make creating a model we can use. This can be overwritten later with --f flag.

node_modules/.bin/sequelize model:create --name Student --attributes firstName:string,email:string

You should have a student.js file in your models folder along with another suffix with create-student in the migrations folder.

We repeat this process for the hobbies model ;

node_modules/.bin/sequelize model:create --name Hobbies --attributes title:string

We are going to define relationships between both models as we want each student to be attributed to a hobby. However, in Part 2 of this post, we will be implementing an array thereby also changing the relationship.

We need to update the migration on the Hobbies model so we can have a reference for the Student model when a lookup is done to match the associated hobbies. I have included a studentIdcolumn to the Hobbiesmodel. So, we want a one-to-many relationship between our models i.e. One Student can have many Hobbies.

// migrations/XXXXXXXXXXXXXX-create-hobbies.jsid: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},StudentId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'Student', // name of Target model
key: 'id', // key in Target model that we're referencing
},
},title: {
type: Sequelize.STRING
},createdAt: {
allowNull: false,
type: Sequelize.DATE
},updatedAt: {
allowNull: false,
type: Sequelize.DATE}

Now in the Student model, we add these lines to reference the Hobbies model

// student.jsStudent.associate = function(models) { Student.hasMany(models.Hobbies)};

For the Hobbies model add-in to similarly reference the Student model

// hobbies.jsHobbies.associate = function(models) { Hobbies.belongsTo(models.Student, { foreignKey: 'studentId' })};

Note: This was something I discovered later, defining this kind of relationship automatically capitalized the foreign key which is why I have capitalized it in the model above. This is the article that helped me understand this

You can now migrate, this process helps you prepare the models and associations ready for data retrieval and manipulation. It is essential you edit the config.json file with values that exist as it will fail. I have edited mine as you can see below. Also, I have set up an elephantSql account and I am using a Postgres instance which is the reason I am using a url parameter.

// config/config.json"development": {"password": *******,
"url": "postgres://******************",
"dialect": "postgres"},

The models/index.jswill automatically look for the variables passed into the config.json. However, since what I have done is essentially a hack, by using an external Postgres service (Elephant SQL) for development I will have to edit the generatedmodels/index.js file in kind.

// models/index.jsif (config.use_env_variable) {
sequelize = new Sequelize(process.env[config.use_env_variable], config);} else {
sequelize = new Sequelize(config.url);}

We can now refactor our server-side code to implement our newly created models.

Firstly, we need to open a new file called schema.js to implement out GraphQL logic

const { gql } = require('apollo-server-express')const typeDefs = gql`type Student {
id: Int!
firstname: String!
email: String!
hobbies: [Hobbies!]!
}type Hobbies {
id: Int!
title: String!
student: Student!
}type Query {
getStudent(id: Int!): Student
getAllStudents: [Student!]!
getHobbies(id: Int!): Hobbies}type Mutation {createStudent(name: String!, email: String!, password: String!): Student!
createHobbies( studentId: Int!, title: String!): Hobbies!}`module.exports = typeDefs

So I know that looks like a lot but trust me it isn’t. We are interpolating the GraphQL syntax with the gql package. Then we define the schema to mirror our database schema, we define types for Student and Hobbies which corresponds to our already defined models. On the Student type, we have defined an extra field called Hobbies which we use to retrieve a current student’s hobbies. Same for the Hobbies type which returns the corresponding student. These will be dealt with in the resolver. Notice how we have only both types share the Query and Mutation, this is because only one Query and Mutation type can be defined. However, there are ways these can be extended but we won’t go into those details until part 2.

Next, we defined three queries; one for fetching a single student, the other for returning all students and the last one getting a single hobby. You can add some for getAllHobbies as well if you are feeling brave. The mutation for createStudent and createHobbies should be pretty self-explanatory.

The bang operator! specified in the Query means I would like the return the whole type specified. e.g. Student! means return a Student type. If the type is not available then return null.

Resolvers

This is where the logic of our application goes, the resolvers handle data logic and how our data is returned. This is akin to the controllers in the MVC pattern but with less validation.

// resolvers.jsconst resolvers = { Query: {
async getStudent (root, { id }, { models }) {
return models.Student.findByPk(id)
},
async getAllStudents (root, args, { models }){
return models.Student.findAll()
},
async getHobbies (root, { id }, { models }) {
return models.Hobbies.findByPk(id)
}},Mutation: { async createStudent (root, { firstName, email }, { models }) {
return models.Student.create({
firstName,
email
})},async createHobbies (root, { studentId, title }, { models }) {
return models.Hobbies.create({ studentId, title })
}
},
}

The resolvers have four parameters (root, args, context, info). root or parent contains the actual data, args the arguments passed in the query.

According to the Apollo documentation:

  • root:The root contains the result returned from the resolver on the parent field, or, in the case of a top-level Query field, the rootValuepassed from the server configuration. This argument enables the nested nature of GraphQL queries.
  • args:An object with the arguments passed into the field in the query. These are normally passed from the client.
  • context:This is an object shared by all resolvers in a particular query, and is used to contain per-request state, including authentication information, dataloader instances, and anything else that should be taken into account when resolving the query. Here is how I make the models available for my resolver object.
  • info: This argument should only be used in advanced cases, but it contains information about the execution state of the query, including the field name, path to the field from the root, and more

We also need to resolve our student field in Hobbies type and hobbies in Students. These fields need to be resolved by the resolved as they are only present as ids on each table.

// resolvers.jsStudent: {
async hobbies(hobbies) {
return hobbies.getHobbies()
}
},Hobbies: {
async student(student) {
return student.getStudent()
}
},

With the relationships, we have defined earlier getStudents() and getHobbies() are made available by sequelize.

We will have to do a refactor of the server.jsfile to re-implement our new resolvers and queries.

// server.jsconst express = require('express');const { ApolloServer } = require('apollo-server-express');const typeDefs = require('./typeDefs')
const resolvers = require('./resolvers')
const models = require('./models')const server = new ApolloServer({ typeDefs, resolvers, context: { models } });const app = express();
server.applyMiddleware({ app });
models.sequelize.authenticate();models.sequelize.sync();app.listen({ port: 4000 }, () => console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`));

As you can see. I have now imported the resolvers and typeDefs where there are taken in as arguments for our instance of the Apollo Server. The models are imported and made available via context in all the resolvers. I have also added sequelize.sync and sequelize.authenticate which syncs all available models and tests the connection to the database respectively.

We can also define a start script

"start": "node server.js"[caption id="attachment_66056" align="alignnone" width="800"]

graphcool implementation of our application

New screen with the updated queries and Mutations[/caption] 

To test our mutations

mutation{
createStudent(firstName:"Benny", email:"[email protected]"){
id
firstName
email
}
}Result{
"data": {
"createStudent": {
"id": 1,
"firstName": "Benny",
"email": "[email protected]"
}
}
}

Queries

getHobbies(id: 1){
id
student{
firstName
}
}
}Result{
"data": {
"getHobbies": {
"id": 1,
"student": {
"firstName": "Benny"
}
}
}
}

Thanks for staying till the end I hope I have been able to enrich your minds. In part 2 of this post, I will be refactoring a lot of the code but will attempt to add on more complicated functionality. Like adding validations and stopping deeply nested queries.

Useful Links

Related posts

The latest articles from Andela.

Visit our blog

How to transition your company to a remote environment

Organizations need to embrace a remote-first mindset, whether they have a hybrid work model or not. Remote-first companies can see a boost in more employee morale and productivity. Here’s how to successfully shift to a remote-first environment.

How Andela's all-female learning program in Lagos is still transforming careers ten years on

In 2014, we launched an all-female cohort in Lagos, Nigeria, to train women in software engineering and development. We spoke to three of the trailblazing women from cohort 4 to find out how the program still inspires their technology careers 10 years later.

IWD: How women at Andela are inspiring inclusion across the tech industry

The theme of this year’s International Women’s Day is #inspireinclusion. We asked some of our female Andelans to offer their expertise and unique perspectives on inclusion, their personal successes, and how they intend to encourage more women to join the tech industry over the next 10 years.

We have a 96%+
talent match success rate.

The Andela Talent Operating Platform provides transparency to talent profiles and assessment before hiring. AI-driven algorithms match the right talent for the job.