3 votes
324 views
by Nikolas Burk

Introduction

A GraphQL server can be implemented in various ways. To help you get a basic understanding of GraphQL server functionality and to play around with some GraphQL features, this tutorial will guide you through building your own GraphQL server from scratch using graphql-yoga.

graphql-yoga is a fully-featured GraphQL server library with a focus on having a simple setup, performance, a great developer experience, and it is the quickest option to get up and running quickly. It builds upon a number of other libraries (such as express, apollo-server, graphql-subscriptions and graphql-playground) and creates a great mix of convenience and flexibility with its straightforward and extensible API.

This tutorial gives a high level overview of building a GraphQL server. If you want to learn more about the mentioned concepts, we encourage you to check out our article series GraphQL Server Basics:

  1. The GraphQL Schema
  2. The Network Layer
  3. Demystifying the info argument in GraphQL resolvers

Building a GraphQL server with graphql-yoga

In this tutorial, you’ll build the API for a simple blogging application. You’ll start from scratch and add more functionality to the app step-by-step.

You can also find the final code for this project here.

Setting up the project directory

Let’s get started with the tutorial and setup the project!

Step 1

In a directory of your choice, create a new directory called blogr, and initialize your NPM project; doing so will add a package.json to the directory:

mkdir blogr
cd blogr
npm init -y
Next, you’ll create the entry-point for the server in a file called `index.js`.
Step 2

Inside the blogr directory, create your index.js file and its directory:

mkdir src
touch src/index.js
Step 3

Now, go ahead and install the graphql-yoga dependency:

yarn add graphql-yoga

Awesome, that’s it! You now have graphql-yoga installed and created the entry-point for the server! Let’s write the first lines of code 🙌

Configuring graphql-yoga & Writing your first resolver

The core primitive provided by graphql-yoga is a class called GraphQLServer. It is configured with everything related to the GraphQL schema as well as the web server configuration, such as the port it’s running on or its CORS setup.

For now, you’ll simply instantiate it with a GraphQL schema definition and the corresponding resolvers. Resolvers are the actual implementation of the API, i.e. the code that will actually be executed when a specific request reaches the API.

Step 4

Add the following code to src/index.js:

const { GraphQLServer } = require('graphql-yoga')

const typeDefs = `
type Query {
  description: String
}
`

const resolvers = {
  Query: {
    description: () => `This is the API for a simple blogging application`
  }
}

const server = new GraphQLServer({
  typeDefs,
  resolvers
})

server.start(() => console.log(`The server is running on http://localhost:4000`))

Here’s what’s going on in this snippet:

  • typeDefs contains our GraphQL schema definition written in GraphQL SDL (= Schema Definition Language), it defines the structure of the GraphQL API. The API defined by this schema exposes exactly one query called description which returns a simple string.
  • resolvers is the implementation of the API. Notice how the shape of the resolvers object matches the shape of the schema: Query.description. At this point, all it does is return a string with a short info about the API.
  • typeDefs and resolvers are passed to the constructor of the GraphQLServer. Now, whenever the server receives the description query, it will simply invoke the Query.description resolver and respond to the query with the string returned by that resolver.
  • Finally, you’re starting the server. Note that you’re also passing a callback that’s invoked once the server is started —here you simply print a short message to indicate that the server is running on port 4000 (which is the default port of graphql-yoga).

So, what happens when you run this thing now?

Step 5

Go ahead and try it out:

node src/index.js

Well, as expected, it prints the message in the console. Go ahead and open http://localhost:4000 inside a browser. You’ll see GraphQL Playground — a powerful GraphQL IDE that lets you work interactively with your GraphQL API. It is similar to tools like Postman, but specifically for GraphQL.

You can now go ahead and send the description query by typing the following into the left editor pane and then hit the Play-button in the middle:

query {
  description
}

Here is what you’ll see after you sent the query:

Congratulations, you just wrote your first GraphQL server!

Defining an application schema

All right, cool! So, you just learned how to write a GraphQL server with a very basic API. But how does that help for the blogging application we promised you to build? Honest answer: Not too much!

What’s needed is an API that allows you to perform certain (domain-specific) operations that we'd expect from a blogging app. Let’s lay out a few requirements. The API should allow for the following operations:

  • Create new post items (i.e. blog articles) as drafts. A draft is a post that is not yet published. Each post should have a title and some content.
  • Publish a draft.
  • Delete a post, whether it’s published or not.
  • Fetch all post items or single one.

Great, that’s four requirements you can directly translate into corresponding API operations.

First, you need to ensure you have a proper data model — in this case, that’ll be represented by a single Post type. Here is what it looks like written in SDL:

type Post {
  id: ID!
  title: String!
  content: String!
  published: Boolean!
}

We’ll tell you in a bit where to put that code — bear with us for a minute. Next, you’re going to define the mentioned API operations. That’s done in terms of queries and mutations. Like with the definition of the description query above, you can add fields to the Query type as well as to a new Mutation type in the GraphQL schema definition:

type Query {
  posts: [Post!]!
  post(id: ID!): Post
}

type Mutation {
  createDraft(title: String!, content: String): Post
  deletePost(id: ID!): Post
  publish(id: ID!): Post
}

All right, but where do you put all that SDL code? Well, in theory you could simply add it to the existing typeDefs string in index.js since that’s where you define the API for your GraphQL server. However, a cleaner solution is to define the schema in its own file. This makes for better maintainability of the project on the long run. Another benefit is that you will get syntax highlighting for your GraphQL definitions if your editor is configured for that.

Step 6

Go ahead and create a new file called schema.graphql in the src directory:

touch src/schema.graphql
Step 7

schema.graphql contains the definition of your application schema. Here is what it looks like in its entirety:

type Query {
  posts: [Post!]!
  post(id: ID!): Post
  description: String!
}

type Mutation {
  createDraft(title: String!, content: String): Post
  deletePost(id: ID!): Post
  publish(id: ID!): Post
}

type Post {
  id: ID!
  title: String!
  content: String!
  published: Boolean!
}

You simply merged the Post type that was defined as your data model together with the API operations — et voilà — there’s your GraphQL schema definition!

There are few things to note about the types in that example:

  • Query and Mutation are the so-called [root types](http://graphql.org/learn/schema/#the-query-and-mutation-types of your schema. They define the entry points for the API. (They are somewhat comparable to routes in a REST API.)
  • ID, String and Boolean are scalar types that are supported by the official GraphQL type system.
  • Post is a custom object type you define inside your schema.
  • The ! following a type means that the corresponding value can not be null. For example, the posts query can not return a list where some elements would be null. The post query on the other hand might return null if no Post item with the provided id exists. Similarly, all mutations might return null in case they fail.
Step 8

Also, since you’re now defining the application schema in a separate file, you can delete the typeDefs variable from index.js and instantiate the GraphQLServer like so:

const server = new GraphQLServer({
  typeDefs: './src/schema.graphql',
  resolvers
})

All right, that’s pretty much all you need to know! So, what’s left to do so your API can actually be used? Implement the corresponding resolvers.

Each root field (i.e. a field on a root type) needs to have a backing resolver so the GraphQL engine inside the GraphQLServer knows what data to return when a query is requesting that field.

Implementing resolvers for the application schema

For now, you’ll use a simple in-memory store rather than an actual persistence layer. You can add a proper database later!

Step 9

Inside index.js, go ahead and replace the current implementation of the resolvers object with the following:

let idCount = 0
const posts = []

const resolvers = {
  Query: {
    description: () => `This is the API for a simple blogging application`,
    posts: () => posts,
    post: (parent, args) => posts.find(post => post.id === args.id),
  },
  Mutation: {
    createDraft: (parent, args) => {
      const post = {
        id: `post_${idCount++}`,
        title: args.title,
        content: args.content,
        published: false,
      }
      posts.push(post)
      return post
    },
    deletePost: (parent, args) => {
      const postIndex = posts.findIndex(post => post.id === args.id)
      if (postIndex > -1) {
        const deleted = posts.splice(postIndex, 1)
        return deleted[0]
      }
      return null
    },
    publish: (parent, args) => {
      const postIndex = posts.findIndex(post => post.id === args.id)
      posts[postIndex].published = true
      return posts[postIndex]
    },
  },
}

There you go! Each field from the application schema now has a backing resolver:

  • Query.description: Same as before.
  • Query.posts: Returns our in-memory array called posts where we’re storing all the Post items.
  • Query.post: Searches the posts array for a Post item with a given id. Notice that the id is contained inside the args argument that’s passed into the resolver. If you want to learn more about the various resolver arguments, check out this article.
  • Mutation.createDraft: Creates a new unpublished Post item and appends it to the posts array. Again, the arguments are retrieved from the args object. They correspond to the arguments defined on the belonging root field!
  • Mutation.deletePost: Removes the Post item with the given id from the posts array.
  • Mutation.publish: Sets the published property of the Post item with the given id to true and returns it.

Go ahead and use this API by starting the server again: node src/index.js.

Here’s a few queries and mutations you can play around with, send them one-by-one in the Playground on http://localhost:4000:

# 1. Create a new draft
mutation {
  createDraft(
    title: "graphql-yoga is awesome"
    content: "It really is!"
  ) {
    id
    published
  }
}

# 2. Publish the draft
mutation {
  publish(id: "post_0") {
    id
    title
    content
    published
  }
}

# 3. Retrieve all posts
query {
  posts {
    id
    title
    published
  }
}

Here’s what the result of the last query will look like after performing the previous mutations:

Feel free to play around with this API a bit further and explore its capabilities. Of course, no data is actually stored beyond the runtime of the GraphQLServer. When you stop and start the server, all previous Post items are deleted.

What you've learned so far

Congratulations, you have made it through the first part of this tutorial! Here is a quick summary of what you have done so far:

  • You wrote a GraphQL schema in schema.graphql that defines the API operations of your GraphQL server.
  • You implemented the API by writing resolver functions for each operation defined in the schema. Whenever the GraphQL server receives a request for a specific operation, it automatically invokes the resolver for it.
  • You learned how to use a GraphQL Playground to send queries and mutations to the GraphQL server.

Connecting a database

So, with what we’ve covered by now you should have gotten a feeling for what’s going on in the internals of a GraphQL server. You define a schema, implement resolvers and there’s your API!

The nice thing about resolvers is that they are super flexible, meaning they’re not bound to a particular data source! This allows, for example, to return data simply from memory as seen in the example, or otherwise fetch data from a database, an existing REST API, a 3rd-party service or even another GraphQL API.

In this example, we’ll connect the resolvers to Prisma. Prisma provides a GraphQL API as an abstraction over a database (in this tutorial, you'll use MySQL). Thanks to prisma-binding (hold on a bit, we’ll talk about this soon), implementing the resolvers merely becomes a question of delegating incoming queries to the underlying Prisma API instead of writing complicated SQL yourself.

Think of Prisma as an ORM-like layer for your GraphQL server.

Adding a Prisma configuration to the project

To introduce Prisma into the project, you need to to make a few rearrangements.

Step 10

First, you need to update the application schema in schema.graphql so that it looks as follows:

# import Post from "./generated/prisma.graphql"

type Query {
  posts: [Post!]!
  post(id: ID!): Post
  description: String!
}

type Mutation {
  createDraft(title: String!, content: String): Post
  deletePost(id: ID!): Post
  publish(id: ID!): Post
}

Wait what? You’re removing the Post type and instead import it (using a GraphQL comment?!) on top of the file from a file that doesn’t even exist? 😠

Well, yes! The comment syntax for importing stems is from the graphql-import library and is not part of the official spec (yet!). You’ll take care of creating the generated/prisma.graphql file in a bit!

Next, you'll set up your Prisma database service.

Step 11

Therefore, you first need to install the Prisma CLI:

npm install -g prisma
Step 12

Once installed, you can use the prisma init command to create a new directory which will contain the configuration files for your Prisma database service:

prisma init database
Step 13

When prompted by the CLI whether you want to create a new Prisma server or deploy an existing one, select the Demo server and hit Enter.

Note that this requires you to authenticate with Prisma Cloud as that's where the Prisma server is hosted.

If you have Docker installed, you can also deploy to a Prisma server that's running locally. To do so, you can choose the Create new database option in the prompt.

Step 14

The CLI further prompts you to select a region to which the Prisma service should be deployed as well as a name and a stage for the service. You can just select the suggested values by hitting Enter.

All prisma init is doing here is creating a new directory called database and places two files in there:

  • prisma.yml: The root configuration file for your Prisma service.
  • datamodel.graphql: Contains the definition of your data model in SDL (Prisma will translate this into an actual database schema).

Your generated prisma.yml should look similar to this:

endpoint: https://eu1.prisma.sh/jane-doe/database/dev
datamodel: datamodel.graphql

The jane-doe part of the endpoint will be different in your case as that's the identifier for your personal workspace in Prisma Cloud. If you have deployed the service locally with Docker,there is no such workspace ID.

Step 15

Next, update the contents of datamodel.graphql to look as follows:

type Post {
  id: ID! @unique
  title: String!
  content: String!
  published: Boolean! @default(value: "false")
}

The definition is identical to the Post from before, except that you’re adding these @default and @unique directives. The @unique directive enforces that no two Post items can have the same value for the id field; the @default directive means that each Post item that will be stored with Prisma will have the value for this field set to false if not otherwise specified. And the best thing is, Prisma is taking care of that without us needing to do anything else. Neat! 🙌

Deploying the Prisma service

Ok cool! So, what did we win now? So far, not much! But let’s go ahead and deploy the Prisma service and see what we can do then.

Step 16

Navigate into the database directory and execute prisma deploy:

cd database
prisma deploy

After the command has finished, it prints an HTTP endpoint that you can open in a browser. This is the same endpoint that's already specified in prisma.yml.

When you’re opening the URL with a browser, you’ll see the Playground for the Prisma API. This API defines CRUD operations for the Post type that’s defined in your data model. For example, you can send the following queries and mutations:

# 1. Create a new Post
mutation {
  createPost(data: {
    title: "graphql-yoga is awesome"
    content: "It really is!"
  }) {
    id
    published
  }
}

# 2. Update title and content of an existing post
mutation {
  updatePost(
    where: {
      id: "__POST_ID__"
    }
    data: {
      title: "New title"
      content: "New content"
    }
  ) {
    id
    title
    published
  }
}


# 3. Retrieve all posts
query {
  posts {
    id
    title
    content
    published
  }
}

You now have a GraphQL API that mirrors the CRUD operations of the underlying database. But how does that help with your GraphQL server and implementing the resolvers for the application schema?

Meet GraphQL bindings!

Implementing resolvers with Prisma bindings

GraphQL bindings are a way to easily reuse and share existing GraphQL APIs. Each GraphQL API is represented as a JavaScript object that exposes a number of methods. Each method corresponds to a query or mutation — but instead of having to spell out the entire query or mutation as a string, you can invoke the corresponding method and the binding object will construct and send the query under the hood.

This is particularly nice for typed languages, where you then get compile-time error checks as well as auto-completion features for GraphQL operations! 💯

Step 17

The first step to introduce bindings is to add the corresponding dependency to your project (the following command needs to be executed inside the blogr, not the database directory):

yarn add prisma-binding
Step 18

Now, you can update the implementation of index.js as follows:

const { GraphQLServer } = require('graphql-yoga')
const { Prisma } = require('prisma-binding')

const resolvers = {
  Query: {
    posts(parent, args, ctx, info) {
      return ctx.db.query.posts({ }, info)
    },
    post(parent, args, ctx, info) {
      return ctx.db.query.post({ where: { id: args.id } }, info)
    },
  },
  Mutation: {
    createDraft(parent, { title, content }, ctx, info) {
      return ctx.db.mutation.createPost(
        {
          data: {
            title,
            content,
          },
        },
        info,
      )
    },
    deletePost(parent, { id }, ctx, info) {
      return ctx.db.mutation.deletePost({ where: { id } }, info)
    },
    publish(parent, { id }, ctx, info) {
      return ctx.db.mutation.updatePost(
        {
          where: { id },
          data: { published: true },
        },
        info,
      )
    },
  },
}

const server = new GraphQLServer({
  typeDefs: './src/schema.graphql',
  resolvers,
  context: req => ({
    ...req,
    db: new Prisma({
      typeDefs: 'src/generated/prisma.graphql', // the generated Prisma DB schema
      endpoint: '__PRISMA_ENDPOINT__',          // the endpoint of the Prisma DB service
      secret: 'mysecret123',                    // specified in database/prisma.yml
      debug: true,                              // log all GraphQL queries & mutations
    }),
  }),
})

server.start(() => console.log('Server is running on http://localhost:4000'))

Note that you need to replace the __PRISMA_ENDPOINT__ placeholder in line 47 with the endpoint of your Prisma service (the one specified in prisma.yml).

In each resolver, you’re now basically forwarding the incoming request to the underlying Prisma API with its powerful query engine. Explaining what’s going on there in detail is beyond the scope of this tutorial — but if you want to learn more, check out these articles:

In any case, you can see that the implementations of the resolvers are mostly one-liners and the hard work is done by the underlying Prisma service. And now just imagine you’d have to write SQL queries in these resolvers… 😱

Generating the Prisma database schema

There is one missing piece before you can start the server again, and that is the dubious generated/prisma.graphql file.

The workflow for getting a hold of this file is based the GraphQL CLI as well as on graphql-config (a configuration standard for GraphQL projects). To get this up-and-running,you first need to install the GraphQL CLI and then create a .graphqlconfig file.

Step 19

Run the following command in your terminal to install the GraphQL CLI:

npm install -g graphql-cli
Step 20

Next, create you .graphqlconfig file inside the blogr directory:

touch .graphqlconfig.yml
Step 21

Then, add the following contents to it:

projects:
  database:
    schemaPath: src/generated/prisma.graphql
    extensions:
      prisma: database/prisma.yml
  app:
    schemaPath: src/schema.graphql
    extensions:
      endpoints:
        default: http://localhost:4000

Notice that you’re also adding information about your local graphql-yoga server — not only the Prisma API!

Step 22

Now, to generate the prisma.graphql file (also called Prisma database schema), all you need to do is run get-schema command from the GraphQL CLI:

graphql get-schema

Because the GraphQL CLI “understands” the .graphqlconfig.yml, it knows that it should download the schema from the endpoint specified in prisma.yml and put the generated GraphQL schema definition into src/generated/prisma.graphql, pretty smart huh? 😎

To ensure your Prisma database schema is always in sync with the deployed API, you can also add a post-deploy hook to your prisma.yml file. Whenever you're updating the data model (and therefore the Prisma database schema) by running prisma deploy, the CLI will automatically download the schema for the updated API.


To do so, add the following code to the end of `prisma.yml`:
hooks:
  post-deploy:
    - graphql get-schema

Testing the app

All right, everything is in place now! You can finally start the GraphQL server again: node src/index.js

Because the application schema hasn’t changed (only its implementation was updated), you can send the same queries and mutations from before to test your API. Of course, now the data you're storing will be persisted in the database that's proxied by Prisma.

Also, here’s a little gem: If you download the standalone version of the GraphQL Playground, you can work with the API of your graphql-yoga server and the Prisma API side-by-side (run prisma playground after you downloaded and installed it on your machine). The projects are read from the .graphqlconfig.yml file as well:

Where to go from here?

In this post, you learned how to build a GraphQL server from scratch. In the end, you had a working API for a blogging application where posts would be stored in an actual database.

Along the way you learned about important concepts, such as GraphQL schemas, resolver functions, GraphQL bindings, graphql-config and a lot more!

Also, we didn’t tell you this before but with this tutorial you basically rebuilt the node-basic GraphQL boilerplate project.

If you want to go one step further and learn about how you can implement authentication and realtime functionality with GraphQL subscriptions, you can check out the fully-fledged Node tutorial on How to GraphQL. To deploy your GraphQL server, check out this tutorial: Deploying GraphQL Servers with Zeit Now.

Additionally, there are other GraphQL servers libraries that you can try out:

  • express-graphql is Facebook’s Express middleware released in 2015, alongside the official GraphQL spec and GraphQL.js reference implementation. This was the first library that helped developers to build GraphQL servers.
  • apollo-server-express is the Express version of apollo-server and also built upon Facebook’s GraphQL.js.

In case you got lost at some point during the tutorial, you can check out the working version of the final project here.

Namaste 🙏