Creating a GraphQL Server in Node.js
Tags: javascript, node, graphql
Published: 2022-07-19 20:38:31

*This article was originally written by [Kevin Cunningham](https://www.honeybadger.io/blog/graphql-server-in-nodejs/#authorDetails) on the [Honeybadger Developer Blog](https://www.honeybadger.io/blog/graphql-server-in-nodejs/).*

You've no doubt heard of GraphQL, the graph-based query language from Facebook. Since its release in 2015, increasing numbers of data providers have been offering a GraphQL endpoint. This endpoint is generally offered alongside a traditional REST based API.

I have come to prefer a GraphQL endpoint on the frontend. I like being able to query for the specific data I want, avoiding issues of over- or under-fetching. I like the self-documenting nature of GraphQL, as its type-based schema describes exactly what is expected and is returned. I have wrestled with REST APIs too many times only to realize that the documentation is outdated or wrong.

On the backend, though, I've continued to provide REST endpoints. Traditional HTTP verbs and routing are familiar, and I can get something functional out the door very quickly.

The question I wanted to answer in this article is, what does it take to get a GraphQL API up and running?

## Context

To help give this article some context, I created a fictional surf shop. I've been out on my kayak a lot this summer, and that's what this particular shop sells. The code that accompanies this article can be found [here](https://github.com/doingandlearning/creating-a-fastify-graphql-server).  

My surf shop uses a MongoDB database and has a Fastify server ready to go. You can find the starter code for this shop here, along with a seeding script, if you want to follow along. You will need [Node](https://nodejs.org/en/download/) and [MongoDB](https://docs.mongodb.com/manual/installation/) installed, which is beyond the scope of this article, but click on the names to go to the installation pages.

To make this a realistic scenario, I want to allow my current clients who are consuming the REST API to be unaffected as I add a GraphQL endpoint.

Let's get started!

## GraphQL Schema

There are two libraries we need to add to our project to get up and running with GraphQL. The first is, unsurprisingly, `graphql`, and the second is `mercurius`. Mercurius is the Fastify adapter for GraphQL. Let's install them:

```bash
yarn add graphql mercurius
```

GraphQL is schema-based, which means that our API will always be documented and type safe. This is a significant benefit for our consumers and helps us as we think about the relationships between the data.

Our store has two types, the `Craft` and the `Owner`. Navigating to the Mongoose models, you can see what fields are available on each. Let's look at the `Owner` model.

The Mongoose model looks like this:

```js
const ownerSchema = new mongoose.Schema({
  firstName: String,
  lastName: String,
  email: String,
});
```

We're going to create a schema directory, which is an index.js file, and then create our GraphQL schema. This `OwnerType` in this schema will look very similar to the Mongoose one.

```js
const OwnerType = `type OwnerType {
  id: ID!
  firstName: String
  lastName: String
  email: String
}`;
```

Template strings are used to define our types, starting with the keyword `type` and the name of our type. Unlike a JavaScript object, there are no commas after each line of our type definition. Instead, each line features the field name and its type separated by a colon. I've used the `ID` and `String` types in my definition. You'll notice that ID is followed with an exclamation point, `!`, which labels this as a compulsory, non-nullable field. All other fields are optional.

I'm going to add this type to the `Query` type of my schema now.

```js
const schema = `
type Query {
  Owners: [OwnerType]
  Owner(id: ID!): OwnerType
}

${OwnerType}
`;
```

You'll see that `Owners` is typed as returning an array of the `OwnerType`, indicated by the square brackets.

`Owner` requires the querying consumer to pass an id field. This is denoted by the value in parentheses, `(id: ID!)`, showing both the name of the field and the type to which it must confirm.

Lastly, we'll export this schema from this file and import it into our main `index.js` file.

```js
module.exports = { schema };
```

and

```js
const { schema } = require("./schema");
```

While we are importing the schema, we can import the mercurius plugin and register it with Fastify.

```js
const mercurius = require("mercurius");

fastify.register(mercurius, {
  schema,
  graphiql: true,
});
```

In the options plugin, we will pass the schema and one other property - we'll set `graphiql` equal to true.

## GraphiQL

GraphiQL is a browser-based interface designed to explore and work with your GraphQL endpoint. Now that it is set equal to true, we can run our server and navigate to `http://localhost:3000/graphiql` to find this page.

![Screenshot of GraphiQL](https://www.honeybadger.io/images/blog/posts/graphql-server-in-nodejs/graphiql.png)

With this tool, we can do the following:

1. Write and validate our queries.
2. Add query variables and request headers to help with testing.
3. Get the results back from our API.
4. Explore the documentation generated by our schema.

Exploring the schema now shows a root type of `query: Query`. It was to this type that we added our `Owner` and `Owners`. Clicking on this shows the following:

![Screenshot of types in GraphiQL](https://www.honeybadger.io/images/blog/posts/graphql-server-in-nodejs/types.png)

and clicking on either of them shows the corresponding type:

![Screenshot of OwnerType](https://www.honeybadger.io/images/blog/posts/graphql-server-in-nodejs/ownertype.png)

I'm going to go ahead and set-up the rest of the type definitions. You can check the [source code](https://github.com/doingandlearning/creating-a-fastify-graphql-server/tree/main/finished-code) to see how I've added the `Craft` type and added a `crafts` field to the `Owner` type.

Once I've done that, my Query type now looks like this:

![Screenshot of Owners and Crafts](https://www.honeybadger.io/images/blog/posts/graphql-server-in-nodejs/ownersandcrafts.png)

The field relationships have all been set up, but we are unable to get any data from them yet. To do that, we'll need to explore two concepts: queries and resolvers.

## GraphQL Queries

At its heart, GraphQL is a query language; it's even in the name! But, so far, we haven't executed any queries. The GraphiQL tool has autocomplete, so we can start constructing our queries now. The following query should return the name of all the `Crafts`.

```graphql
query {
  Crafts {
    name
  }
}
```

When we execute, though, we get a `null` response.

```json
{
  "data": {
    "Crafts": null
  }
}
```

That's because we haven't set up any resolvers. A resolver is a function that GraphQL runs to find the data it needs to resolve a query.

For this project, I am going to define the resolvers in the `schema/index.js` file, alongside the schema. I already have controllers for both of the data types used by my REST API routes. I'm going to use these controllers, with some adaptation, to serve my GraphQL endpoint.

First, I'll import the controllers:

```js
const craftController = require("../controllers/craftController");
const ownerController = require("../controllers/ownerController");
```

Then, I'll create a resolvers object:

```js
const resolvers = {}
```

This object should have a key for each root type for which we want to provide resolvers. For our use, we have a single root type, namely `Query`. The value for this key should be a function executed to get the required data. This is how it would look for our Crafts field:

```js
const resolvers = {
  Query: {
    async Crafts() {
      return await craftController.getCrafts();
    },
  },
};

```

We then export the resolvers function, import it into our main `index.js`, and pass it to our plugin options object, along with the schema.

```js
// in /src/schema/index.js
module.exports = { schema, resolvers };

// in /src/index.js
const { schema, resolvers } = require("./schema");

fastify.register(mercurius, {
  schema,
  resolvers,
  graphiql: true,
});


```

Now, when we run the previous query, we should get all the names of the crafts in our database.

![GraphiQL showing craft names](https://www.honeybadger.io/images/blog/posts/graphql-server-in-nodejs/craftnames.png)

Awesome! However, what if we want to query for a specific craft? This requires a little more work. First, let's construct the query in our GraphiQL editor.

![Screenshot showing labeled GraphiQL editor](https://www.honeybadger.io/images/blog/posts/graphql-server-in-nodejs/numberedscreenshot.png)

The query setup looks very similar, with a few differences:

1. I need to pass in a query variable. After the keyword `query`, we indicate the name and type of the variable that is going to be passed. The variable should begin with a dollar sign (`$`).
2. Here, I am using the variable `$id` as the value for the field to query on my Craft field.
3. The value of the query variable is being passed as JSON.
4. Lastly, I get my response back.

At the moment, I don't have any data returned. Let's fix that!

Back in my resolvers, I'll add a function for Craft. The first positional argument is the parent, which I don't need for this operation, so I'll use an underscore there. The second is the arguments passed into the query from which I want to decompose the id:

```js
const resolvers = {
  Query: {
    async Crafts() {
      return await craftController.getCrafts();
    },
    async Craft(_, { id }) {
      return await craftController.getCraftById({id})
    },
  },
};
```

Currently, my `getCraftById` function is expecting the request object. I'll need to update the function in `src/controllers/craftController.js`.

This original function

```js
// Get craft by id
exports.getCraftById = async (request, reply) => {
  try {
    const craft = await Craft.findById(request.params.id);
    return craft;
  } catch (error) {
    throw boom.boomify(error);
  }
};
```

becomes

```js
exports.getCraftById = async (request, reply) => {
  try {
    const id = request.params === undefined ? request.id : request.params.id;
    const craft = await Craft.findById(id);
    return craft;
  } catch (error) {
    throw boom.boomify(error);
  }
};

```

Awesome! Now, when we execute our query, a result will be returned.

![Screenshot of craft with id in GraphiQL](https://www.honeybadger.io/images/blog/posts/graphql-server-in-nodejs/craftbyid.png)

We'll need to help GraphQL to fill the fields that link to other types. If our consumer queried for the current owner of the craft, it would come back as `null`. We can add some logic to get the owner based on the `owner_id`, which is stored in the database. This can then be appended to our craft object before being passed back to our user.

```js
async Craft(_, { id }) {
  const craft = await craftController.getCraftById({ id });
  if (craft && craft.owner_id) {
    const owner = await ownerController.getOwnerById({
      id: craft.owner_id,
    });
    craft.owner = owner;
  }
  return craft;
},
```

Our `ownerController.getOwnerById` will need to be updated in the same way as the corresponding craft function. But, once that is handled, we can query the owner freely.

You can check the [finished-code directory](https://github.com/doingandlearning/creating-a-fastify-graphql-server/tree/main/finished-code) to find the resolvers for all the other fields and the updated controller functions.

## GraphQL Mutations

I can now provide for queries to a GraphQL endpoint confidently; all the read operations are some adaptations of what we have done already. What about other operations? Specifically, what about `Create`, `Update`, and `Delete`?

In GraphQL, each of these operations is referred to as a mutation. We are changing the data in some way. Setting up the backend for a mutation is almost exactly the same as setting up a query. We need to define the mutation in the schema and then provide the resolver function that will be executed when the mutation is called.

So, in `/schema/index.js`, I'm going to extend the `Mutation` type and add an `addCraft` mutation.

```graphql
type Mutation {
  addCraft(  
    name: String
    type: String
    brand: String
    price: String
    age: Int
  ): CraftType
}
```

As with previous field definitions, the values in parentheses show what fields can be passed into the function. This are each passed alongside their types. We then follow with what the mutation will return. In this case, an object in the shape of our CraftType.

Checking this out in GraphiQL, we can see that `mutation` is now a root type, when we click through, that our addCraft mutation exists in the schema.

![addCraft in GraphiQL](https://www.honeybadger.io/images/blog/posts/graphql-server-in-nodejs/addcraft.png)

Constructing a mutation in GraphiQL looks identical to constructing a query. We'll need to pass in query variables as we did before, and it'll look something like this:

![Mutation without any data](https://www.honeybadger.io/images/blog/posts/graphql-server-in-nodejs/mutationnull.png)

When we execute, though, we get a `null` response. This is hopefully unsurprising because we haven't yet created a resolver for this mutation. Let's do that now!

We'll add a `Mutation` key to our resolvers object and a function for our `addCraft` mutation.

```js
Mutation: {
  async addCraft(_, fields) {
    const { _id: id } = await craftController.addCraft({ ...fields });
    const craft = { id, ...fields };
    return craft;
  },
},
```

Our current `addCraft` function only returns the Mongoose response, which is the `_id` field. We'll extract that and return the entered fields, allowing us to conform to the CraftType we declared earlier.

The update and destroy functions are identical in their configuration and setup. In each case, we're extending the Mutation type in the schema and adding a corresponding resolver.

You can check the [finished-code directory](https://github.com/doingandlearning/creating-a-fastify-graphql-server/tree/main/finished-code) to find the resolvers for some of the other mutations.

## Conclusion

I entered into this wondering if building a GraphQL server was a huge unnecessary hassle. I finish quietly confident that I'll use GraphQL for my next backend project.

There is initially a bit more setup and boilerplate than reaching straight out for Mongo through our REST API. This could potentially be a sticking point. However, I think there are some compelling points that make this worthwhile.

No longer do you need to provide an endpoint for some niche use of your application. The consumer only needs to call on the fields that they need for a given context. This saves a cluttered routes file and multiple calls to your API when one will do.

In updating the schema and resolvers, you make this data immediately available to your consumers. While you can mark fields as deprecated, you could leave legacy fields in place with little cost to the user. Furthermore, this is a self-documenting API. Never again will your documentation site fall out of sync with the current state of your API.

Are you convinced? Will you move over to GraphQL, or are you on team REST API forever?