The API I wish JavaScript GraphQL implementations supported

The API I wish JavaScript GraphQL implementations supported

The API I wish JavaScript GraphQL implementations supported - The GraphQL schema language is great! It is certainly the best way to communicate anything about a GraphQL service. No wonder all documentations now use it!

The API I wish JavaScript GraphQL implementations supported - The GraphQL schema language is great! It is certainly the best way to communicate anything about a GraphQL service. No wonder all documentations now use it!

The Schema Language

Imagine that you’re building a blog app (with GraphQL) that has "Articles" and "Comments" . You can start thinking about its API schema by basing it on what you plan for its UI. For example, the main page will probably have a list of articles and an item on that list might display a title, subtitle, author’s name, publishing date, length (in reading minutes), and a featured image. A simplified version of Medium itself if you may:

We can use the schema-language to plan what you need so far for that main page. A basic schema might look like:

type Query {
  articleList: [Article!]!
}
type Article {
  id: ID!
  title: String!
  subTitle: String
  featuredImageUrl: String
  readingMinutes: Int!
  publishedAt: String!
  author: Author!
}
type Author {
  name: String!
}

When a user navigates to an article, they’ll see the details of that article. We’ll need the API to support a way to retrieve an Article object by its id. Let’s say an article can also have rich UI elements like headers and code snippets. We would need to support a rich-text formatting language like Markdown. We can make the API return an article’s content in either Markdown or HTML through a field argument (format: HTML). Let’s also plan to display a "likes" counter in that view.

Put all these ideas on paper! The schema language is the most concise structured way to describe them:

type Query {
  # ...
  article(id: String!): Article!
}
enum ContentFormat {
  HTML
  MARKDOWN
}
type Article {
  # ...
  content(format: ContentFormat): String!
  likes: Int!
}

The one article’s UI view will also display the list of comments available on an article. Let’s keep the comment UI view simple and plan it to have a text content and an author name fields:

type Article {
  # ...
  commentList: [Comment!]!
}
type Comment {
  id: ID!
  content: String!
  author: Author!
}

Let’s focus on just these features. This is a good starting point that’s non-trivial. To offer these capabilities we’ll need to implement custom resolving logic for computed fields like content(format: HTML) and readingMinutes. We’ll also need to implement 1–1 and 1-many db relationships.

Did you notice how I came up with the whole schema description so far just by thinking in terms of the UI. How cool is that? You can give this simple schema language text to the front-end developers on your team and they can start building the front-end app right away! They don’t need to wait for your server implementation. They can even use some of the great tools out there to have a mock GraphQL server that resolves these types with random test data.

The schema is often compared to a contract. You always start with a contract.## Building a GraphQL Schema

When you’re ready to start implementing your GraphQL service, you have 2 main options (in JavaScript) today:

  1. You can "build" a non-executable schema using the full schema language text that we have and then attach a set of resolver functions to make that schema executable. You can do that with GraphQL.js itself or with Apollo Server. Both support this method which is commonly known as "schema-first" or "SDL-first". I’ll refer to it here as the "full-schema-string method".
  2. You can use JavaScript objects instantiated from the various constructor classes that are available in the GraphQL.js API (like GraphQLSchema, GraphQLObjectType, GraphQLUnionType, and many others). In this approach, you don’t use the schema-language text at all. You just create objects. This method is commonly known as "code-first" or "resolvers-first" but I don’t think these names fairly represent it. I’ll refer to it here as the "object-based method".

Both approaches have advantages and disadvantages.

The schema language is a great programming-language-agnostic way to describe a GraphQL schema. It’s a human-readable format that’s easy to work with. The frontend people on your team will absolutely love it. It enables them to participate in the design of the API and, more importantly, start using a mocked version of it right away. The schema language text can serve as an early version of the API documentation.

However, completely relying on the full schema language text to create a GraphQL schema has a few drawbacks. You’ll have to put in some effort to make the code modularized and clear and you have to rely on coding patterns and tools to keep the schema-language text consistent with the tree of resolvers (AKA resolvers map). These are solvable problems.

The biggest problem I see with the full-schema-string method is that you lose some flexibility in your code. You don’t have objects associated with types. You just have strings! And although these strings make your types more readable, in many cases you’ll need the flexibility over the readability.

The object-based method is flexible and easier to extend and manage. It does not suffer from any of the mentioned problems. You have to be modular with it because your schema is a bunch of objects. You also don’t need to merge modules together because these objects are designed and expected to work as a tree.

The only problem I see with the object-based method is that you have to deal with a lot more code around what’s important to manage in your modules (types and resolvers). A lot of developers see that as "noise" and you can’t blame them. We’ll work through an example to see that.

If you’re creating a small-scope and well-defined GraphQL service, using the full-schema-string method is probably okay. However, in bigger and more agile projects I think the more flexible and more powerful object-based method is the way to go.

You should still leverage the schema-language text even if you’re using the object-based method. At jsComplete, we use the object-based method but every time the schema is built we use the graphql.printSchema function to write the complete schema to a file. We commit and track that file in the Git repository of the project and that proved to be a very helpful practice!
To compare the 2 methods, I’ve implemented an executable schema for the blog example we started with using both of them. I’ve omitted some code for brevity but kept what matters for the comparison.

The full-schema-string method

We start with the schema-language text which defines 3 main custom types (Article, Comment, and Author). The fields under the main Query type are article and articleList which will directly resolve objects from the database. However, since the GraphQL schema we planned has custom features around an article object and since we have relations that we need to resolve as well we’ll need to have custom resolvers for the 3 main custom GraphQL types.

Here are a few screenshots for the code I wrote to represent the full-schema-string method. I’ve used Apollo Server here but this is also possible with vanilla GraphQL.js (and a bit more code).

Please note that this is just ONE way of implementing the full-schema-string method for this service. There are countless other ways. I am just presenting the simplest modular way here to help us understand the true advantages and disadvantages.

This is nice! We can see the types in the schema in one place. It’s clear where the schema starts. We’re able to modularize the code by type/feature.

This again is really great! Resolvers are co-located with the types they implement. There is no noise. This file beautifully contains what matters in a very readable format. I love it!

The modularity here is only possible with Apollo Server. If we’re to do this with vanilla GraphQL.js we will have to monkey with data objects to make them suitable to be a "resolvers tree". The mixing between the data structures and the resolvers graph is not ideal.
So what’s the downside here?

If you use this method then all your types have to be written in that certain way that relies on the schema language text. You have less flexibility. You can’t use constructors to create some types when you need to. You’re locked down to this string-based approach.

If you’re okay with that then ignore the rest of this article. Just use this method. It is so much cleaner than the alternative.

The object-based method

Let’s now look at the object-based approach. Here’s the starting point of an executable schema built using that method:

We don’t need a separate resolvers object. Resolvers are part of the schema object itself. That makes them easier to maintain. This code is also easier to programmatically extend and analyze!

It’s also so much more code that’s harder to read and reason about! Wait until you see the rest of the code. I couldn’t take the Article type screenshot on the laptop screen. I had to use a bigger screen.

No wonder the full-schema-string method is popular! There is certainly a lot of "noise" to deal with here. Types are not clear at first glance. Custom resolvers are mixed in one big configuration object.

My favorite part is when you need to create a non-null list of non-null items like [Article!]!. Did you see what I had to write?

new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Article))),

However, while this is indeed a lot more code that’s harder to understand, it is still a better option than having one big string (or multiple strings combined into one) and one big root resolvers object (or multiple resolvers objects combined into one). It’s better than having all the dependencies of your app managed in one single entry point.

There is a lot of power in modularizing your code using objects (that may depend on each other). It’s cleaner that way and it also makes writing tests and validations easier. You get more helpful error messages when you debug problems. Modern editors can provide more helpful hints in general. Most importantly, you have a lot more flexibility to do anything with these objects. The GraphQL.js constructors API itself also uses JavaScript objects. There is so much you can do with them.

But the noise is real too.

The object-based method without the noise

I am sticking with the object-based method but I sure wish the JavaScript GraphQL implementations had a better API that can give us some of the power of the full-schema-string method.

Wouldn’t be nice if we can write the Article type logic exactly as we did in the full-schema-string method but in a way that generates the flexible GraphQLObjectType that we can plug into an object-based schema?

Something like:

Wouldn’t that be ideal? We get the benefits of the full-schema-string method for this type but with no lockdown! Other types in the system can be maintained differently. Maybe other types will be dynamically constructed using a different maker logic!

All we need to make this happen is a magical <strong>typeMakerMethod</strong> to take the parts that matter and transform them into the complete GraphQLObjectType for Article.

The typeMakerMethod will need to parse a string into an AST, use that to build a GraphQLObjectType, then merge the set of custom resolver functions with the fieldsconfiguration that’ll be parsed from the typeDef string.

I like a challenge so I dug a little bit deeper to see how hard would it be to implement the typeMakerMethod. I knew I couldn’t use the graphql.buildSchema function because it only parses one full schema string to make a non executable schema object. I needed a lower-level part that parses a string that has exactly ONE type and then attaches custom resolvers to it. So I started reading the source code of GraphQL.js to look for clues. A few cups of coffee later, I found some answers (in 2 places):

That’s the core method used in buildSchema to construct ONE type from a type definition node (which we can easily get by parsing the typeDef string).

And:

That’s how easy it is to extend an object type and attach any logic needed in fields and interfaces!

All I needed to do is put a few pieces together and the dream can be true.

I did.

Ladies and gentlemen. I present to you the magical "typeMakerMethod" (which I named objectType):

That’s it (in its most basic form)! This will take a typeDef string that defines a single GraphQL type, an object of resolvers and a map of dependencies (for that type), and it’ll return a GraphQLObjectType ready to be plugged into your object-based schema as if it was defined normally with the object constructor.

Now you can use the object-based method but you have the option to define SOME types using an approach similar to the full-schema-string method. You have the power.

What do you think of this approach? I’d love to hear your feedback!

Please note that the objectType code above is just the basic use case. There are many other use cases that require further code. For example, if the types have circular dependencies (articleauthorarticle) then this version of objectType will not work. We can delay the loading of the circular dependencies until we’re in the fields thunk (which is the current approach to solve this problem in the object-based method). We can also use the "extend" syntax to design the schema in a way that avoids circular dependencies in the first place. I’ve skipped this part to keep the example simple.> If you’d like to give it a spin I published a more polished version of objectTypeand a few other maker functions like it under the graphql-makers npm package.

What is GraphQL and how to use it

It is basically a query language for API's

What is GraphQL?

It is basically a query language for API's

GraphQL shows what are the different types of data provided by the server and then the client can pick exactly what it wants.

Also in GraphQL you can get multiple server resources in One call rather than making multiple REST API calls.

You can check https://graphql.org/ for the full list of benefits.

The thing is until you see GraphQL in action, it's hard to understand the benefits. So let's get started with using GraphQL.

We will be using GraphQL along with NodeJS in this Article.

Pre-requisites

Install NodeJS from https://nodejs.org/en/

How to use GraphQL with NodeJs

GraphQL can be used with multiple languages. Here we will focus on how we can use GraphQL with javascript using NodeJS.

Create a Folder called as graphql-with-nodejs. Go into the project folder and run npm init to create the NodeJS project. The command for this is given below.

cd graphql-with-nodejs
npm init

Install the Dependencies

Install Express using the following command

npm install express

Install GraphQL using the following command. We will be installing graphql and graphql for express.

npm install express-graphql graphql

NodeJS Code

Create a file called as server.js inside the project and copy the following code into it

const express = require('express');
const port = 5000;
const app = express();

app.get('/hello', (req,res) => {
    res.send("hello");
   }
);

app.listen(port);
console.log(`Server Running at localhost:${port}`);

The above code has a single http get end point called as /hello.

The end point is created using express.

Now let us modify this code to enable GraphQL.

Enabling GraphQL in the code

GraphQL will have a single url endpoint called as /graphql which will handle all requests.

Copy the following code into server.js

//get all the libraries needed
const express = require('express');
const graphqlHTTP = require('express-graphql');
const {GraphQLSchema} = require('graphql');

const {queryType} = require('./query.js');

//setting up the port number and express app
const port = 5000;
const app = express();

 // Define the Schema
const schema = new GraphQLSchema({ query: queryType });

//Setup the nodejs GraphQL server
app.use('/graphql', graphqlHTTP({
    schema: schema,
    graphiql: true,
}));

app.listen(port);
console.log(`GraphQL Server Running at localhost:${port}`);

Let us go through this code now

graphqlHTTP enables us to setup a GraphQL server at /graphql url. It basically knows how to handle the request that is coming in.

This setup is done in the following lines of code

app.use('/graphql', graphqlHTTP({
    schema: schema,
    graphiql: true,
}));

Now let us explore the parameters inside graphqlHTTP

graphiql

graphiql is a Web UI using which you can test the graphql endpoints. We will set this to true so that it is easier to test the various graphql endpoints we create.

schema

Though graphql has only one external endpoint /graphql, this in turn can have multiple other endpoints doing various things. These endpoints would be specified in the schema.

The schema would do things like:

  • Specify the endpoints
  • Indicate the input and output fields for the endpoint
  • Indicate what action should be done when an endpoint is hit and so on.

The Schema is defined as follows in the code

const schema = new GraphQLSchema({ query: queryType });

The schema can contain query as well as mutation types. This article will focus only on the query type.

query

It is seen the in the schema that query has been set to queryType.

We import queryType from query.js file using the following command

const {queryType} = require('./query.js');

query.js is a custom file which we will be creating soon.

query is where we specify the read only endpoints in a schema.

Create a file called as query.js in the project and copy the following code into it.

const { GraphQLObjectType,
    GraphQLString
} = require('graphql');


//Define the Query
const queryType = new GraphQLObjectType({
    name: 'Query',
    fields: {
        hello: {
            type: GraphQLString,

            resolve: function () {
                return "Hello World";
            }
        }
    }
});

exports.queryType = queryType;

query Explained

queryType is created as a GraphQLObjectType and given the name Query.

fields is where we specify the various endpoints.

So here we are adding one endpoint called as hello

hello has a type of GraphQLString Which means this endpoint has a return type of String. The type is GraphQLString instead of String since this a graphql schema. So directly using String will not work.

resolve function indicates the action to be done when the endpoint is called. Here the action is to return a String "Hello World".

Finally we export the querytype using exports.queryType = queryType. This is to ensure we can import it in server.js

Running the Application

Run the application using the following command

node server.js

The application runs on localhost:5000/graphql.

You can test the application by going to localhost:5000/graphql.

This url runs the Graphiql web UI as shown in the screen below.

The input is given in the left and the output is shown in the right.

Give the following input

{
  hello
}

This will give the following output

{
  "data": {
    "hello": "Hello World"
  }
}

Congrats

You have created your first GraphQL endpoint.

Adding more endpoints

We will create 2 new endpoints:

  • Specify the endpoints
  • Indicate the input and output fields for the endpoint
  • Indicate what action should be done when an endpoint is hit and so on.

Adding Data

Usually an application will read data from a Database. But for this tutorial we will be hardcoding the data in the code itself for simplicity.

Create a file called data.js and add the following code.

//Hardcode some data for movies and directors
let movies = [{
    id: 1,
    name: "Movie 1",
    year: 2018,
    directorId: 1
},
{
    id: 2,
    name: "Movie 2",
    year: 2017,
    directorId: 1
},
{
    id: 3,
    name: "Movie 3",
    year: 2016,
    directorId: 3
}
];

let directors = [{
    id: 1,
    name: "Director 1",
    age: 20
},
{
    id: 2,
    name: "Director 2",
    age: 30
},
{
    id: 3,
    name: "Director 3",
    age: 40
}
];

exports.movies = movies;
exports.directors = directors;

This file has the movies and directors data. We will be using the data in this file for our endpoints.

Adding the movie endpoint to the query

The new endpoints will be added to queryType in query.js file

The code for the movie endpoint is shown below

movie: {
            type: movieType,
            args: {
                id: { type: GraphQLInt }
            },
            resolve: function (source, args) {
                return _.find(movies, { id: args.id });
            }
        }

The return type of this endpoint is movieType which we will be defining soon.

args parameter is used to indicate the input to the movie endpoint. The input to this endpoint is id which is of type 

GraphQLInt

resolve function returns the movie corresponding to the id, from the movies list. find is a function from lodash library used to find an element in a list.

The complete code for query.js is shown below

const { GraphQLObjectType,
    GraphQLString,
    GraphQLInt
} = require('graphql');
const _ = require('lodash');

const {movieType} = require('./types.js');
let {movies} = require('./data.js');


//Define the Query
const queryType = new GraphQLObjectType({
    name: 'Query',
    fields: {
        hello: {
            type: GraphQLString,

            resolve: function () {
                return "Hello World";
            }
        },

        movie: {
            type: movieType,
            args: {
                id: { type: GraphQLInt }
            },
            resolve: function (source, args) {
                return _.find(movies, { id: args.id });
            }
        }
    }
});

exports.queryType = queryType;

From the above code it can be seen that movieType is actually defined in types.js

Adding the Custom Type movieType

Create a file called as types.js.

Add the following code into types.js

const {
    GraphQLObjectType,
    GraphQLID,
    GraphQLString,
    GraphQLInt
} = require('graphql');

// Define Movie Type
movieType = new GraphQLObjectType({
    name: 'Movie',
    fields: {
        id: { type: GraphQLID },
        name: { type: GraphQLString },
        year: { type: GraphQLInt },
        directorId: { type: GraphQLID }

    }
});

exports.movieType = movieType;

It can be seen that movieType is created as a GraphQLObjectType

It has 4 fields id, name, year and directorId. The types for each of these fields are specified as well while adding them.

These fields come directly from the data. In this case it will be from movieslist.

Adding the query and type for director endpoint

Similar to movie, even the director endpoint can be added.

In query.js, the director endpoint can be added as follows

director: {
            type: directorType,
            args: {
                id: { type: GraphQLInt }
            },
            resolve: function (source, args) {
                return _.find(directors, { id: args.id });
            }
        }

directorType can be added as follows in types.js

//Define Director Type
directorType = new GraphQLObjectType({
    name: 'Director',
    fields: {
        id: { type: GraphQLID },
        name: { type: GraphQLString },
        age: { type: GraphQLInt },
        movies: {
            type: new GraphQLList(movieType),
            resolve(source, args) {
                return _.filter(movies, { directorId: source.id });
            }

        }

    }
});

Wait a minute. The directorType is slightly differnt from movieType. Why is this?

why is there a resolve function inside directorType. Previously we saw that resolve functions were present only in the query

The Speciality of directorType

When the director endpoint is called we have to return the director details, as well as all the movies the director has directed.

The first 3 fields id,name,age in directorType are straightforward and come directly from the data ( directors list)

The fourth field movies needs to contain the list of movies by this director.

For this we are mentioning that the type of movies field is a 

GraphQLList of movieType ( List of movies )

But how exactly will be find all the movies directed by this director?

For this we have a resolve function inside the movies field.

The inputs to this resolve function is source and args.

source will have the parent object details.

Lets say the fields id =1, name = "Random" and age = 20 for a director. Then source.id =1, source.name = "Random" and source.age = 20

So in this example, resolve function finds out all the movies where directorId matches the Id of the required Director.

Code

The Entire code for this application is available in this github repo

Testing the Application

Now Let us test the application for different scenarios.

Run the application using node server.js

Go to localhost:5000/graphql and try the following inputs.

movie

Input:

{
  movie(id: 1) {
    name
  }
}

Output:

{
  "data": {
    "movie": {
      "name": "Movie 1"
    }
  }
}

From the above it can be seen that, the client can request exactly what it wants and GraphQL will ensure only those parameters are sent back. Here only name field is requested and only that is sent back by the server.

In movie(id: 1), id is the input parameter. We are asking the server to send back the movie which has an id of 1.

Input:

{
  movie(id: 3) {
    name
    id
    year
  }
}

Output:

{
  "data": {
    "movie": {
      "name": "Movie 3",
      "id": "3",
      "year": 2016
    }
  }
}

In the above example name, id and year fields are requested. So the server sends back all of those fields.

director

Input:

{
  director(id: 1) {
    name
    id,
    age
  }
}

Output:

{
  "data": {
    "director": {
      "name": "Director 1",
      "id": "1",
      "age": 20
    }
  }
}

Input:

{
  director(id: 1) {
    name
    id,
    age,
    movies{
      name,
      year
    }
  }
}

Output:

{
  "data": {
    "director": {
      "name": "Director 1",
      "id": "1",
      "age": 20,
      "movies": [
        {
          "name": "Movie 1",
          "year": 2018
        },
        {
          "name": "Movie 2",
          "year": 2017
        }
      ]
    }
  }
}

In the above example we see the power of GraphQL. We indicate we want a director with id 1. Also we indicate we want all the movies by this director. Both the director and movie fields are fully customizable and the client can request exactly what it wants.

Similarily this can be extended to other fields and types. For example we could run a query like Find a director with id 1. For this director find all the movies. For each of the movie find the actors. For each actor get the top 5 rated movies and so on. For this query we need to specify the relationship between the types. Once we do that, the client can query any relationship it wants.

Introduce to Mutation and Database Access in GraphQL

Introduce to Mutation and Database Access in GraphQL

I'll introduce you to GraphQL mutation. This should leave you with the skills to build a GraphQL API to perform CRUD operations. This should let you brag with your friends that you're now a GraphQL developer :)

GraphQL, described as a data query and manipulation language for APIs, and a runtime for fulfilling queries with existing data, allows varying clients use your API and query for just the data they need. It helps solve some issues that some REST services have. Which is over-fetching and under-fetching, and this is a performance issue. In this post, I'll introduce you to GraphQL mutation. We will also

Adding A Database

Before we move to creating GraphQL mutations, I want us to use a database for the existing queries we have in our GraphQL system. We will be using Prisma as a data access layer over MySQL database. For this example we will use Prisma demo server running on Prisma cloud service.

Let's go ahead and define a database schema. Add a new file src/prisma/datamodel.prisma with the following content

type Book {
    id: ID! @id
    title: String!
    pages: Int
    chapters: Int
    authors: [Author!]!
}

type Author {
id: ID! @id
name: String! @unique
books: [Book!]!
}

The above schema represent our data model. Each type will be mapped to a database table. Having ! with a type will make that column in the database to be non-nullable. We also annotated some fields with the @id directive. GraphQL directives are preceded by @ and be used in the schema language or query language.

The @id directive is managed by Prisma, and will mark the field as the primary key in the database and auto-generate global unique ID for that column in the database. The @unique directive will mark that column with a unique constraint in the database. This will also allow us to find authors by their names as you'll see later.

Next we add a new file src/prisma/prisma.yml which will contain configuration options for Prisma.

# The HTTP endpoint for the demo server on Prisma Cloud
endpoint: ""

Points to the file that contains your datamodel

datamodel: datamodel.prisma

Specifies language & location for the generated Prisma client

generate:

  • generator: javascript-client
    output: ./client

This will be used by the Prisma CLI to configure and update the Prisma server in the cloud, and generate a client API based on the data model. The endpoint option will contain the URL to the Prisma Cloud server. The datamodel option specifies a path to the data model, the generate option specifies we're using the javascript client generator and it should output the client files to the /client folder. Prisma CLI can generate the client using other generators. There are generators for TypeScript and Go currently. We're working wit JavaScript so I've opted to use the javascript-client generator.

We need the Prisma CLI to deploy our Prisma server and for generating the Prisma client. We'll install the CLI globally using npm. Run the following command to install the Prisma CLI.

npm install -g prisma

I'm running version 1.34.0 of the CLI. With that installed we now need to deploy our data model. Follow the instructions below to setup the database on Prisma cloud.

  1. Run cd src/prisma && prisma deploy in the command line.
  2. You'll get prompted to choose how you want to set up the Prisma server. Select Demo Server to continue.
  3. The CLI might want to authenticate your request by opening a browser window for you to login or signup to Prisma. Once you've logged in, close the window and go back to the command prompt.
  4. The next prompt requires you to choose a region for the demo server to be hosted on Prisma Cloud. Pick any of you choice and press Enter key to continue.
  5. Now you get asked to choose a name for the service. Enter graphql-intro (or any name of your choosing) and continue.
  6. the next prompt ask for a name to give the current stage of our workflow. Accept the default by pressing the Enter to continue.

The CLI takes those information and the information in prisma.yml to set up the demo server. Once it's done it updates the file with the endpoint to the Prisma server. It'll also print in the console information regarding how the database was set up.

With the server set up, the next step is to generate the Prisma client for our data model. The Prisma client is auto-generated based on your data model and gives you API to communicate with the Prisma service. Run the following command to generate our Prisma client.

prisma generate

This command generates the client API to access the demo server we created from earlier. It should dump a couple of files in src/prisma/client.

The next step for us is to connect our GraphQL server to the database server using the Prisma client, and get data from there.

Open src/index.js and import the prisma instance exported from the generated client, and then delete the books variable.

const { GraphQLServer } = require("graphql-yoga");
const { prisma } = require('./prisma/client')

....//rest of the code remains untouched

We also need a dependency which is needed to run the Prisma client. Open the command line and run the command npm install prisma-client-lib to install this package.

Using Prisma Client In Resolvers

Now that we have Prisma client generated we'll need to use that in our resolvers. We'll pass down the prisma instance using through the context argument that every resolver function gets. 

I mentioned that the context argument is useful for holding contextual information and you can read or write data to it. To work with the prisma client, we'll write the prisma instance from the generated client to the context object when the GraphQL client is been initialized.

In src/index.js, on line 32, update the initialisation of the GraphQLServer as follows.

const server = new GraphQLServer({
typeDefs,
resolvers,
context: { prisma }
});

We will also update the resolvers to use prisma for resolving queries. Update the Query property in theresolvers variable as follows:

const resolvers = {
Query: {
books: (root, args, context, info) => context.prisma.books(),
book: (root, args, context, info) => context.prisma.book({ id: args.id })
},
...
}

In those resolvers we're calling a function on the prisma client instance attached to the context. The function prisma.books() gives us all books in the database, while prisma.book({ id: args.id}) gets us a book based on the passed in id.

Adding Mutation Operations

So far we're able to fetch data from the GraphQL API but we need a way to update data on the server. GraphQL mutation is a type of operation that allows clients modify data on the server. It is through this operation type that we’re able to add, remove, and update records on the server.

To read data we use GraphQL query operation type, which you learnt from the previous post, and we touched on it in the previous section.

We will add new a new feature to our GraphQL API so that we can add books and authors. We will start by updating the GraphQL schema. Update the typeDefs variable in index.js as follows

const typeDefs = `
type Book {
id: ID!
title: String!
pages: Int
chapters: Int
authors: [Author!]!
}

type Author {
id: ID!
name: String!
books: [Book!]!
}

type Query {
books: [Book!]
book(id: ID!): Book
authors: [Author!]
}

type Mutation {
book(title: String!, authors: [String!]!, pages: Int, chapters: Int): Book!
}
`;

We have updated our GraphQL schema to add new types, Author and Mutation. We added a new field authors which is a list of Author to the Book type, and a new field authors: [Author!] to the root query type. I also changed fields named id to use the ID type.

This is because we're using that type in our data model and the database will generate global unique identifier for those fields, which won't match the Int type we've been using so far. The root Mutation type defines our mutation operation and we have only one field in it called book, which takes in parameters needed to create a book.

The next step in our process of adding mutation to the API is to implement resolvers for the new fields and types we added. With index.js still open, go to line 30 where the resolvers variable is defined and add a new field Mutation to the object as follows.

const resolvers = {
Mutation: {
book: async (root, args, context, info) => {
let authorsToCreate = [];
let authorsToConnect = [];

  for (const authorName of args.authors) {
    const author = await context.prisma.author({ name: authorName });
    if (author) authorsToConnect.push(author);
    else authorsToCreate.push({ name: authorName });
  }

  return context.prisma.createBook({
    title: args.title,
    pages: args.pages,
    chapters: args.chapters,
    authors: {
      create: authorsToCreate,
      connect: authorsToConnect
    }
  });
}

},
Query: {
...
},
Book: {
...
}
};

Just like every other resolver functions, the resolver for books in the root mutation type takes in four arguments and we get the data that needs to be created from the args parameter, and the prisma instance from the context parameter. This resolver is implemented such that it will create the book record in the database, create the author if it does not exist, and then link the two records based on the data relationship defined in our data model.

All this will be done as one transaction in the database. We used what Prisma refers to as nested object writes to modify multiple database records across relations in a single transaction.

While we have the resolver for the root mutation type, we still need to add resolvers for the new Author type and the new fields added to Query and Book type. Update the Book and Query resolvers as follows:

const resolvers = {
Mutation: {
...
},
Query: {
books: (root, args, context, info) => context.prisma.books(),
book: (root, args, context, info) => context.prisma.book({ id: args.id }),
authors: (root, args, context, info) => context.prisma.authors()
},
Book: {
authors: (parent, args, context) => context.prisma.book({ id: parent.id }).authors()
},
Author: {
books: (parent, args, context) => context.prisma.author({ id: parent.id }).books()
}
};

The authors field resolver of the root query operation is as simple as calling prisma.authors() to get all the authors in the database. You should notice the resolvers for the fields with scalar types in Book and Author was omitted. This is because the GraphQL server can infer how to resolve those fields by matching the result to a property of the same name from the parent parameter. The other relation fields we have cannot be resolved in the same way so we needed to provide an implementation. We call in to Prisma to get this data as you've seen.

After all these edits, your index.js should be same as the one below:

const { GraphQLServer } = require("graphql-yoga");
const { prisma } = require("./prisma/client");

const typeDefs = `
type Book {
id: ID!
title: String!
pages: Int
chapters: Int
authors: [Author!]!
}

type Author {
id: ID!
name: String!
books: [Book!]!
}

type Query {
books: [Book!]
book(id: ID!): Book
authors: [Author!]
}

type Mutation {
book(title: String!, authors: [String!]!, pages: Int, chapters: Int): Book!
}
`;

const resolvers = {
Mutation: {
book: async (root, args, context, info) => {
let authorsToCreate = [];
let authorsToConnect = [];

  for (const authorName of args.authors) {
    const author = await context.prisma.author({ name: authorName });
    if (author) authorsToConnect.push(author);
    else authorsToCreate.push({ name: authorName });
  }

  return context.prisma.createBook({
    title: args.title,
    pages: args.pages,
    chapters: args.chapters,
    authors: {
      create: authorsToCreate,
      connect: authorsToConnect
    }
  });
}

},
Query: {
books: (root, args, context, info) => context.prisma.books(),
book: (root, args, context, info) => context.prisma.book({ id: args.id }),
authors: (root, args, context, info) => context.prisma.authors()
},
Book: {
authors: (parent, args, context) =>
context.prisma.book({ id: parent.id }).authors()
},
Author: {
books: (parent, args, context) =>
context.prisma.author({ id: parent.id }).books()
}
};

const server = new GraphQLServer({
typeDefs,
resolvers,
context: { prisma }
});
server.start(() => console.log(Server is running on http://localhost:4000));

Testing The GraphQL API

So far we have updated our schema and added resolvers to call in to the database server to get data. We have now come to the point where we need to test our API and see if it works as expected. Open the command line and run node src/index.js to start the server. Then open localhost:4000 in your browser. This should bring up the GraphQL Playground. Copy and run the query below to add a book.

mutation{
book(title: "Introduction to GraphQL", pages: 150, chapters: 12, authors: ["Peter Mbanugo", "Peter Smith"]){
title
pages
authors{
name
}
}
}

Now that the book is created we can query and see how for the authors in the application.

query{
authors {
name
books {
title
}
}
}

That's A Wrap!

I introduced you to GraphQL mutation, one fo the three root operation types in GraphQL. We updated our schema with new functionalities which included mutation to add books to the application and using Prisma as our database access layer. I showed you how to work with a data model using the same schema definition language from GraphQL, working with the CLI and generating a Prisma client, and how to read and write data using the Prisma client. Since our data is stored on Prisma cloud, you can access your services and database online on app.prisma.io.

You added new functionalities to our application in this post. This should leave you with the skills to build a GraphQL API to perform CRUD operations. This should let you brag with your friends that you're now a GraphQL developer. To proof that to you, I want you to add a new set of functionalities to your API as follows:

  1. Add a query to find authors by their name.
  2. Allow books to have publishers. This will have you add a new type to the schema. You should be able to independently add publishers and query for all the books belonging to a publisher.

If you get stuck or want me to have a look at your solution. Hope this tutorial will surely help and you!

Further reading

☞ The Modern GraphQL Bootcamp (Advanced Node.js)

☞ An introduction GraphQL with AWS AppSync

☞ GraphQL API with AWS and Use with React

☞ GraphQL Tutorial: Understanding Spring Data JPA/SpringBoot

☞ Getting started with GraphQL and TypeScript

An Intro to GraphQL API

Originally published on dev.to