Vanilla GraphQL With NodeJS And PostgreSQL - Setting Up Application

Hi guys, in today's post, let's talk about GraphQL.

So it's been 5 years since the day GraphQL was publicly released and the majority of us may be using it for our daily projects. 5 years is long enough for a new piece of technology and its ecosystem to mature. Today, we can easily integrate GraphQL into our workflow with Apollo Server/GraphQL Yoga, etc on the backend side and Relay/Apollo Client, etc if you're on the frontend team. That's pretty cool. However, those fancy tools (especially when used together with ORMs) also added many functionalities which can sometimes prevent us from grasping what GraphQL actually is and what it is capable of.

That being said, let's do something differently. In this post and the next coming one, I would like to walk you through a journey to fire up GraphQL without its fancy friends but just vanilla graphql package. In fact, in the next post when we introduce using PostgreSQL, we won't be using any ORM or SQL builder either. The whole point of doing this is not about giving up using third parties entirely, but to actually see that it's not so difficult to live without them, and most importantly, we will be able to gain a deeper understanding of how things work and appreciate how much third parties make life easier by abstracting away the heavy works.

So, what are we gonna build? Well, we will create a simple authentication service that consists of a GraphQL server & PostgreSQL database for users to register and log in, as well as have their login status managed via sessions.

This will be a mini-series consisting of five posts like below:

Sounds simple enough? Let's get started!

Preparation

First, clone my ExpressJS starter-code from GitHub. It will give us a nice and compact setup for ExpressJS, Typescript, Eslint, and Prettier. Then we will go inside and run yarn to install all the dependencies. After that run yarn start and make sure you see {"status": "success"} at localhost:8000.

(Most likely, there will be errors the first time you run yarn start. Just hit Ctrl-C to terminate and run it again)

Loading...

Next, let's open up and examine index.ts inside the src folder. Right now we have a bare minimum setup for an ExpressJS app with only one endpoint.

Loading...

Lastly, we need to install a few dependencies to set up a GraphQL server:

Loading...

Schema Definition

With GraphQL installed, now we can define the schema for the GraphQL server. We will need one ObjectType to define the User type, two Queries for getting one single user/multiple users, and two Mutations for register and login functionality. Using buildSchema function from graphql, it's gonna look like this:

Loading...

Next, we need to define a User class that holds the actual data for the User type. We also implement the constructor method to make the initialization easier:

Loading...

Okay, what else do we need? Since we're not using a database today (I will talk about database setup in the next post), let's define a dictionary-like object to serve as an in-memory database. It will have User‘s id as keys and the User object as values. And by the way, this is how we define such objects in Typescript:

Loading...

Alright, cool. Now we can go ahead and set up the GraphQL server. We achieve that by using graphqlHTTP, which acts as a middleware to ExpressJS so that we both have a REST server and a GraphQL server sitting on top of it. Inside index.ts, modify the main function:

Loading...

Now let's fire up the server by running yarn start, head over to localhost:8000/graphql, we can see an interface to play with GraphQL called GraphiQL:

Figure 1: GraphQL playground - GraphiQL
Figure 1: GraphQL playground - GraphiQL

Now, if we try to run a Query, say, the easiest one: getUsers, we will receive null as a result.

Figure 2: getUsers returns null
Figure 2: getUsers returns null

Oops! Let's figure out why in the next session.

Resolver Implementation

How can that be? Well, if you remember, we only declared the Query & Mutation types, we haven't provided an implementation for them yet. All we have to do is to add behaviors to each of getUser, getUsers, register, and login types. And those behaviors are called resolvers in GraphQL.

So, where do we put them, then? Inside the empty object that we passed to rootValue above.

Let's start with getUsers, shall we? We know that we want to return an array of all available users, so this is how we're gonna implement it:

Loading...

Let's switch to the browser and try to run the query one more time:

Figure 3: getUsers shows up data after implementing the resolver
Figure 3: getUsers shows up data after implementing the resolver

We can see the data showing up now. Yay! Let's move on and implement getUser query. This is a little bit more special because it requires an id argument:

Loading...

Now, if you look at the function, you might wonder why we have to pass id inside an object. That is because a resolver expects a parameter named args, which will contain all the arguments that got passed to its function. So what we're doing here is to simply destructure that parameter and get the id argument from it. Okay, let's test it out:

Figure 4: getUser returns result if there is any matching user
Figure 4: getUser returns result if there is any matching user
Figure 5: getUser when there is no matching user
Figure 5: getUser when there is no matching user

If there is a user that matches the id, then we give that user back, otherwise, we simply return null. The tests above confirmed the logic we wanted. Cool!

So we have done with the Query types, let's move on to the Mutation types. This time, instead of getting the data from memory, we will insert/update the data into memory. The logic shouldn't be that different though. Do you want some challenge? Pause here and try implementing them yourselves!

We will start off by implementing the resolver for login type. If username doesn't exist or if password doesn't match, we will return null, otherwise, we will update the lastLogin attribute and return the User object. The code looks something like this:

Loading...

The result when login is successful is as follows. You can check for the case when login failed by yourselves, you should see null get returned.

Figure 6: login successful
Figure 6: login successful

Lastly, let's tackle the register‘s resolver. What we're gonna do is to check if that username has been taken first. In case it hasn't, we will add a new User into our in-memory database. Alright, let's code:

Loading...

It's time to check our new code for register‘s resolver:

Figure 7: register successful
Figure 7: register successful

If we execute it one more time, i.e. register with an existing username, we will receive null back. Please confirm that to be sure 😉.

Password Hashing

Now, there's one more thing that we can do to improve the register's resolver: we don't want to save naked passwords like that. As usual, let's add a step to hash the password before inserting into the database.

How to hash the password is up to you. You can either manually hash using hashing functions or even better, rely on password hashing packages such as Bcrypt or Argon2. I typically like the latter better, since they provide functions for both hashing and verifying passwords. There are ongoing debates on whether Bcrypt or Argon2 works better than the other, I couldn't care less because they both work well for me.

For this particular project, I'm gonna choose Argon2 so let's go ahead and install it:

Loading...

The first thing we need to update is the seed data. But wait a minute, don't we have a register function now? We can go ahead and comment out the chunk of code that we used to seed initial data to the database. There's another reason for not using those lines, more on that in a second.

Loading...

Alright, great. We can update our register logic now. To use Argon2 to hash passwords is simple, just call its hash function on the raw password.

Loading...

Compilation error, wasn't it? Since hash returns a Promise, let's await it and turn register's resolver into an async function. That's also the reason why we should not update the seed data since we have to wrap that code into an async function too. That's too much!

Our new register function looks like this:

Loading...

Let's test this out to make sure it still works:

Figure 8: register with hashed password
Figure 8: register with hashed password

We can confirm that register resolver still works, and the password was hashed before saved to the database. Great!

If we try to log in now, we couldn't anymore because the password was hashed. So, inside login resolver, we need to update the logic to check if the input password is correct. Again, we will use a function called verify that Argon2 provides us with. Pretty neat, isn't it? Here's what our new login looks like:

Loading...

Again, let's make sure everything works as expected:

Figure 9: login with hashed password
Figure 9: login with hashed password
Figure 10: getUsers still works
Figure 10: getUsers still works
Figure 11: as well as getUser!
Figure 11: as well as getUser!

Okay, cool! Everything is working properly. So, eventually, our index.ts will look like below:

Loading...

And we're done! Congratulations! In case you encountered any problems and want to compare to my code, please clone my GitHub repo and check out the branch for this post:

Loading...

Conclusions

Alright, guys. Thank you for reading this long, we have reached the end. Today we have successfully set up a GraphQL server, defined a schema and implemented resolvers so that we can get users' information, register a new user, and log an existing user in. We're on our way to create an authentication functionality for our future projects!

In the next post, we will discuss how to migrate from in-memory object-type database to using PostgreSQL. We will also explore how to properly use session to manage login status to finish off the backend of our application. Until then, take good care of yourself and I'm gonna see you soon.