Table of Contents
Why Bother with GraphQL?
It can’t go unnoticed that GraphQL is being adopted by plenty of reputable companies like Facebook, Shopify, Twitter, and many more.
For quite some time, I hadn’t had the chance to explore it and understand what it brings to the table. Still, probably like many of you, I’ve read some isolated references here and there, gaining intuition about what kind of API design pain points GraphQL is meant to solve.
So I decided to spend some time learning GraphQL, its pros and cons, and get a better feeling of the scenarios when it can improve the general quality of a system and integration workflows.
Now I believe that GraphQL is worth digging into and can be a valuable tool in a system designer’s toolbox.
That being said, this article aims to provide a practical demo project for everyone who wants to get some hands-on, introductory experience with GraphQL.
About the ”Modern GraphQL with Node” Course at Udemy
Once I decided to explore GraphQL, I searched for a suitable online course through some popular learning platforms.
That’s how I ended up at the following Udemy course:
Modern GraphQL with Node – Complete Developers Guide, by Laith Harb
In my opinion, it’s pretty well taught and reveals the core concept in a clean and practical manner.
Following along with the lectures and exercises, you’ll build a few GraphQL mini-projects.
Some of the main covered topics are:
- What is GraphQL and how does it compare to REST
- GraphQL terminology
- How to build a GraphQL API in Node using Apollo Server
- GraphQL design principles
- Adding Authentication to the GraphQL API
- Working with Prisma v3 to interact with a Postgres DB
- Solving the N + 1 problem via Data Loaders
- Building a GraphQL client with React and Apollo Client
If you’re a complete newbie to GraphQL and want to learn it with a clear step-by-step approach, I would strongly recommend taking the course yourself.
Presenting the “Social Media” Demo Project
This article will present the GraphQL API for a sample “Social Media” demo project that I built as part of taking the Udemy course.
The focus here is not on teaching GraphQL from a theoretical standpoint.
Instead, consider this demo project as a shortcut in case you’ve glanced over some of the main topics like queries, mutations, schemas, resolvers, but you want to see them in action.
Still, even without prior knowledge, an experienced developer should be able to understand the responsibilities of the different components, their dependencies and interactions, by looking at the source code and the sample API calls.
The demo project represents a primitive version of a Social Media platform. It’s built with NodeJS, Apollo, and React for the front end. It uses PostgreSQL with Prisma as an ORM technology for data persistence.
In this post, I’ll focus on the back-end and the GraphQL API, leaving the front end for the next part.
Functional Requirements
Let’s first describe the main functional aspects of the system from a high-level perspective. Later on, I’ll present the concrete GraphQL API methods that implement these requirements.
Sign Up and Sign In
Every new visitor should be able to register a new account. Once the account is created and the user is signed in, he can start reviewing his posts feed and adding new posts himself.
Creating Posts
When a user creates a Post, its initial state is private (not published). This is a temp state during which other customers can’t see the post in their feeds. When the user decides, he can publish the post making it available to everyone else.
Posts Feed Page
The feed page is a sort of a home page for every user that lists all the published posts by himself or any other user.
User Profile Page
Each user has a profile that contains their biography. You can visit any user profile page and see his personal info and all the posts he created. However, if you go to your own profile, you’ll have additional options like adding/updating a post, publishing/unpublish the existing ones, etc.
Set Up the Back End Environment
Let’s go through a few steps you need to follow to set up your back-end environment and run the API.
Install NodeJS
I’ve built this project using Node version 16.14
Install Postgres
You can install Postgres locally with docker using a command like this one:
docker run --name postgres -e POSTGRES_PASSWORD=<some_pass> -e POSTGRES_USER=vasilkosturski -p 5432:5432 -d postgres
Download the project from GitHub
Install the packages and dependencies
Go to the server
subdirectory and run npm install
:
Create keys.ts
file
Under the src
dir, create the key.ts
file and export the JSON_SIGNATURE
constant. For demo purposes, you can assign any string you want. This is used when signing the JWT token, so the important part is to make sure it doesn’t change between requests.
export const JSON_SIGNATURE = 'jfhghlsgfjkdfhgkhkfsbjhh'
Create .env
file
Also, you need to create the .env
file and populate your Postgres connection string. It should look something like this:
DATABASE_URL="postgresql://vasilkosturski:<some_pass>@localhost:5432/postgres"
Make sure you set your own username and password.
Create the Postgres database and tables
Spend a moment to review the database schema defined in the schema.prisma file:
Run the following command in the terminal so you get your Postgres database and all the required tables created:
npx prisma db push
You can then take a look at your new database.
For a Postgres IDE, I’m using Postbird.
Run the project
Finally, you run the project using npm run start:dev
Start the Apollo Playground
In this post, we’ll use the Apollo GraphQL Playground to run queries against the GraphQL API. Make sure it’s accessible at localhost:4000
:
GraphQL Schema
One of the strongest sides of GraphQL is the ability to selectively choose only the data you need from your API.
For this, GraphQL requires you to define a schema that models all the relationships between the API entities.
For the demo app, I’ll go through some of the main entities below, but I strongly advise you to explore the complete GraphQL schema and type definitions on your own.
Let’s take an example with the user Profile
type declaration:
type Profile { id: ID! bio: String! isMyProfile: Boolean! user: User! }
The profile has a User property that’s defined as follows:
type User { id: ID! name: String! email: String! posts: [Post!]! }
As you can see, the User, in turn, contains an array of posts where a Post has the following form:
type Post { id: ID! title: String! content: String! createdAt: String! published: Boolean! user: User! }
So, when an API client is requesting a Profile, he might also want to retrieve the user details with all his posts.
You’ll see a bunch of examples in the next section, but just for intuition, a sample query might look like so:
query Profile($userId: ID!) { profile(userId: $userId) { id, bio, user { email, id, posts { title } } } }
Of course, you can change the projection if you like:
query Profile($userId: ID!) { profile(userId: $userId) { bio user { email, posts { title } } } }
To get a better sense of how the nested fields of an API entity are populated, you need to understand the concept of Resolvers.
A quick definition, taken from here:
Resolver is a collection of functions that generate response for a GraphQL query. In simple terms, a resolver acts as a GraphQL query handler. Every resolver function in a GraphQL schema accepts four positional arguments as given below − fieldName:(root, args, context, info) => { result }
For example, we’ve seen that the User
has a nested posts
array. This array gets populated via the following resolver:
export const User = { posts: (parent: UserParentType, __: any, { userInfo, prisma }: Context) => { const isOwnProfile = parent.id === userInfo?.userId; var filter:any = {} filter.authorId = parent.id; if (!isOwnProfile){ filter.published = true; } return prisma.post.findMany({ where: filter, orderBy: [ { createdAt: "desc", }, ], }); }, };
This code snippet also sheds some light on how a user can only see unpublished posts that are created by himself. Feel free to explore the logic behind `userInfo` and how it gets injected as a context to the resolver.
I encourage you to read the next section and experiment with the GraphQL calls, projections, and resolvers by yourself.
GraphQL API Use Cases
In this section, I’ll use the Apollo playground to demonstrate most of the mutations and queries that our API supports.
Go to localhost:4000
to get started.
Sign Up
Check the Signup resolver
The Sign Up request looks like so:
mutation Signup($credentials: CredentialsInput!, $name: String!, $bio: String!) { signup(credentials: $credentials, name: $name, bio: $bio) { token, userErrors{ message } } }
You need to provide values for the input parameters $credentials
, $name
, and $bio
as per the bottom part of the screenshot below.
Here I’ll create a couple of users – James Smith and Allice Brown.
The response contains the JWT token to be passed as an authorization header for the other API calls.
For demo purposes, I’m using the bcryptjs
and jsonwebtoken
libraries to hash the passwords and generate user tokens. Feel free to explore the usage in the auth.ts file.
After these Sign Up calls, database records are created in two tables – User and Profile.
Sign In
Check the Signin resolver
The Sign In request looks like so:
mutation Signin($credentials: CredentialsInput!) { signin(credentials: $credentials) { token, userErrors { message } } }
This is the usual way to obtain a JWT token for an existing user. In the next section, we’ll see how to set it in the Authorization header.
Create Post
Check the PostCreate resolver
Here’s a sample call to create a Post (of course, you can change what fields to project in the response):
mutation PostCreate($post: PostInput!) { postCreate(post: $post) { post { title, content, published, user { email } }, userErrors { message } } }
First, in the Headers section, you need to set the Authorization
header with the token received from the Sign In request:
Then, populate the input variables that will be passed as input arguments e.g. the post title
and content
.
After you execute the request, you can check the post in Postgres:
Publish Post
Check the PostPublish resolver
Publishing a post makes it visible to the other users of the system.
Request:
mutation PostPublish($postId: ID!) { postPublish(postId: $postId) { post { id, title } } }
Apollo Playground:
DB result:
Update Post
Check the PostUpdate resolver
You can update the title
and/or the content
of a post.
Request:
mutation PostUpdate($postId: ID!, $post: PostInput!) { postUpdate(postId: $postId, post: $post) { post { id, title }, userErrors { message } } }
Apollo Playground:
Get Posts
Check the Posts resolver
The query for getting all the posts looks like so:
query Posts { posts { id, content, title, createdAt, published, user { email } } }
Apollo Playground:
One particularly interesting point related to the posts retrieval is the ability to project the user, as shown in the request above. Here’s the resolver that’s responsible for it.
This resolver utilizes a data loader which solves the N + 1 problem, which means avoiding doing separate DB calls for every post to get the related user.
In essence, the data loader would batch the users requests and keep an in-memory cache to load the users from.
Get Profile
Check the Profile resolver
Here’s the request for receiving a user profile along with his posts:
query Profile($userId: ID!) { profile(userId: $userId) { id, bio, isMyProfile, user { email, id, posts { title } } } }
Apollo Playground:
These are the core API calls you’ll see in the project. There are a few more, so feel free to explore them at your convenience.
Summary
In this article, I presented the GraphQL API for a sample Social Media demo project.
I hope this is a good reference point for anyone making the first steps with GraphQL with Node and Apollo server.
See you next time!