What is GraphQL
GraphQL is a specification for writing queries for APIs and a runtime for fulfilling those queries to already existing data. It is built around the HTTP protocol and defines how to send and receive resources from a server.
With GraphQL, you do not need to create multiple endpoints to request data like you would usually do when creating REST endpoints. You are provided with one singular endpoint which returns the required data based on the queries you send to the endpoint.
REST vs GraphQL
Imagine you are building a CMS (a content management system) and you have two tables in your database; user
and posts
. Below, you will see how both REST and GraphQL will handle a situation where you need to get all the posts for each user in your application.
REST
When using REST, you would first need to make a request to a first endpoint (e.g /users
) which queries the user
model to get a list of all the users in the db. After doing this, you can extract the id
of each user before making another request to a second endpoint (e.g /users/posts
) which returns all the posts created by that user. This means that you would have to make n
number of calls to the /users/posts
endpoint, n
being the number of users in your application.
Also note that the response from the /users
endpoint might contain other user information apart from the id
like name, date of birth, location, age, etc which you do not really need in this context. This also applies to the /users/posts
endpoint. This ultimately means that you get to make requests to endpoints more that you need to and that might slow down the application.
GraphQL
With GraphQL, you only need to create a single query which contains the exact data that you need. In this context, you tell it that you need all the users (their names only) and all posts created by each user (title only) by creating a query like the one below
query{
users{
name
posts{
title
}
}
}
After creating the query, you send it to your GraphQL server which parses the query and returns the the appropriate data as defined in the query. Here, you get exactly what you need from a single endpoint, nothing more, nothing less. This greatly improves your application in many ways.
In a nutshell
In a nutshell, GraphQL gives us everything a REST API can do like CRUD functionalities, error handling, etc. It however improves on it by reducing the number of requests made, nesting data based on simple organized queries, which makes the whole server run faster and more optimized.
Express + GraphQL
Let us make a handy template for getting started with GraphQL applications. You'll first need to setup a simple Express application using GraphQL. You then add some functionalities to the application so you can see some of the features GraphQL has to offer by yourselves.
To get started, you create a basic express server by running the command below;
% npm init --yes
This generates a package.json
file which contains information about your application, and other information like the application dependencies and how to run it.
You need to install your application dependencies by running the commands below which adds them to your package.json
file;
% npm i express express-graphql graphql
These dependencies allows you to start up an express server, connect your expresses server to graphql (express-graphql
) and also provides the GraphQL functionalities you need (graphql
).
You'll also install nodemon
to help restart your server when any file changes, and add a dev
script to your package.json
file.
% npm i --save-dev nodemon
package.js
{ "scripts":
{ "dev": "nodemon index.js"}
}
PostgreSQL + Docker
After this is done, you'll set up a postgreSQL database using docker-compose and populate it with some users and posts so you can test your application. To do this, you'll create a docker-compose.yml
file which will contain the configurations for the postgres service you'll be using. You can set that up by adding the folling lines to the docker-compose file
version: '3.8'
services:
postgres:
image: postgres:13-alpine
ports:
- 5432:5432
env_file:
- .env
volumes:
- postgres:/var/lib/postgresql/data
networks:
- graphql-express
volumes:
postgres:
name: graphql-express-docker-db
networks:
graphql-express:
Then go on to you terminal and run docker compose up -d
or sudo docker-compose up -d
(for linux). This spins up the postgres service and makes it available for your application. You also need to install sequelize
, an ORM
for connecting with and quering your database. To do the, install the following
% npm install sequelize pg pg-hstore
Sequelize also provides a cli
which makes it easier to user the ORM by providing cli commands. In this example, you'll make use of sequelize-cli
to set up database connection and schema. Enter the following commands in your terminal
% npm install --save-dev sequelize-cli
...
% npx sequelize-cli init
This will create a number of folders
-
config
, which contains config file, which tells CLI how to connect with database -
models
, contains all models for your project -
migrations
, contains all migration files
After these has been created, you need to create your user and posts models. To do this, you can use the model:generate
command which takes the model name and attributes
% npx sequelize-cli model:generate --name User --attributes name:string
% npx sequelize-cli model:generate --name Post --attributes title:string
This generates the user and posts models which you can edit to add relationships. In the user
model, you can add the following lines to the static associate
function in the user.js
file
static associate({ Post }) {
this.hasMany(Post, { foreignKey: "userId", onDelete: "CASCADE" })
}
and in the post.js
file, you add the belongsTo
association in the Post
model
static associate({ Post }) {
this.hasMany(Post, { foreignKey: "userId", onDelete: "CASCADE" })
}
You can then run migration to persist the schema on the database using the db:migrate
command
npx sequelize-cli db:migrate
After this, you can create a seed file to insert some raw data by default. This will help you test the server later. You can create a seed file to create demo-users.
% npx sequelize-cli seed:generate --name demo-user
...
You can then proceed to add demo users to the seed files. After adding these users you then use the db:seed:all
command to create them.
xxxxxxxxxxxxx-demo-user.js
'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up (queryInterface, Sequelize) {
return queryInterface.bulkInsert('Users', [{
name: 'Naruto Uzumaki',
createdAt: new Date(),
updatedAt: new Date()
}]);
},
async down (queryInterface, Sequelize) {
return queryInterface.bulkDelete('Users', null, {});
}
};
then
% npx sequelize-cli db:seed:all
Express Server
You can then set up a simple server by adding the following to the index.js
file. This spins up the server and add GraphQL
to it. It uses the graphqlHTTP
function which takes two arguments, schema
, which will be defined later and the graphiql
which provides a GUI for interacting with our server.
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const { sequelize, User, Post} = require('./models');
const app = express();
app.use('/graphql', graphqlHTTP({
schema: schema,
graphiql: true
}))
app.listen(5000, () => (
await sequelize.authenticate();
console.log("Running")
))
For this to work though, you need to define a schema which tells GraphQL how your data interacts with each other. This schema takes two arguments; the query
argument which is a way for you to fetch data from the server and the mutation
argument which gives your server other CRUD
functionalities.
const schema = new GraphQLSchema({
query: rootQuery,
mutation: rootMutation
})
To define your rootQuery
, add the following lines to your server file. The rootQuery
is a GraphQLObjectType
which defines the name and description for the object. It servers as a data access layer (DAL) which defines the object fields which itself contains definitions of various query fields you can use to fetch data from your server. It also resolves those fields and returns the appropriate data by making queries to the the database.
// root query scope
const rootQuery = new GraphQLObjectType({
name: "Query",
description: "Root Query",
fields: () => ({
posts: {
type: new GraphQLList(PostType),
description: 'List of posts',
resolve: () => Post.findAll()
},
users: {
type: new GraphQLList(UserType),
description: 'List of users',
resolve: () => User.findAll()
},
post: {
type: PostType,
description: 'A single post',
args: {
id: { type: GraphQLInt }
},
resolve: (parent, args) => Post.findOne({
where: {
id: args.id
}
})
},
user: {
type: UserType,
description: 'A single user',
args: {
id: { type: GraphQLInt }
},
resolve: (parent, args) => User.findOne({
where: {
id: args.id
}
})
}
})
})
You can also define your rootMutation
which is very similar to the the rootQuery
object. However it differs because it is used to mutate or change data on your server.
const rootMutation = new GraphQLObjectType({
name: "Mutation",
description: "Root Mutation",
fields: () => ({
addUser: {
type: UserType,
description: 'Add a user',
args: {
name: { type: GraphQLNonNull(GraphQLString) }
},
resolve: (parent, args) => {
const user = User.create({
name: args.name
})
return user
}
},
addPost: {
type: PostType,
description: 'Add a post',
args: {
title: { type: GraphQLNonNull(GraphQLString) },
userId: { type: GraphQLNonNull(GraphQLInt) }
},
resolve: (parent, args) => {
const post = Post.create({
title: args.title,
userId: args.userId
})
return post
}
}
})
})
Both the rootQuery
and the rootMutation
defines some types like the PostType
and UserType
which represents the definitions for your posts and users respectively. To define these types, which are a bit similar with the rootQuery
and rootMutation
, you should input the following and edit as required
const UserType = new GraphQLObjectType({
name: "User",
description: "A User in our application",
fields: () => ({
id: { type: GraphQLNonNull(GraphQLInt) },
name: { type: GraphQLNonNull(GraphQLString) },
posts: {
type: new GraphQLList(PostType),
resolve: (user) => Post.findAll({
where: {
userId: user.id
}
})
}
})
})
const PostType = new GraphQLObjectType({
name: "Post",
description: "A Post created by a user",
fields: () => ({
id: { type: GraphQLNonNull(GraphQLInt) },
title: { type: GraphQLNonNull(GraphQLString) },
userId: { type: GraphQLNonNull(GraphQLInt) },
user: {
type: UserType,
resolve: (post) => User.findOne({
where: {
id: post.userId
}
})
}
})
})
Testing the server
With your database running and everything above put in place as you require, you can proceed to run npm run dev
in your terminal session to spin up the server. Your expres server should be available on localhost:5000
and you can interact with the GraphQL
server on localhost:5000/graphql
which provides a nice user interface.
With this, you have successfully created a GraphQL server using express and connected to a PostgreSQL database instance that was created using docker compose.
The full code for this article is available here on github.
Cheers.
Top comments (1)
Nice introduction. I also recommend to check out my take on this topic in the context of serverless GraphQL backend. webwinx.com/2023/01/30/go-serverle...