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

Creating a RESTful Web API with Node.js and Express.js from scratch

Creating a RESTful Web API with Node.js and Express.js from scratch

In this article, I’ll show you step by step how to create a RESTful Web API with Node.js and Express.js by building a simple and useful Todo API. This article assumes you have basic javascript knowledge and terminal using capabilities.

In this article, I’ll show you step by step how to create a RESTful Web API with Node.js and Express.js by building a simple and useful Todo API. This article assumes you have basic javascript knowledge and terminal using capabilities.

You can also build a Web API in Node.js by using another framework except Express.js but Express.js is one of the most popular web framework for Node.js.

You can found the final source code of this Web API in this github repository.

Let’s start to create our mentioned Web API.

Before start

If you have never used Node.js or npm package manager you should install them.

To check whether the Node.js is already installed on your computer, open your terminal and run node -v command. If you see your Node.js version it's installed. Otherwise go to below link.

Click here to download and install Node.js (You can choose LTS version)

And if you don’t have any IDE or text editor for writing javascript I advice you Visual Studio Code.

Click here to download VS Code (Optional)

About express-generator

In fact we could use <a href="https://expressjs.com/en/starter/generator.html" target="_blank">express-generator</a> tool which designed to creating an Express Web API quickly but I want to create this API from scratch because of that tool puts some extra files and folder structures that we don't need them now. But you can use this useful tool next time on creating new Web API. I won't use it now due to keep article simple.

Creating Project

Go to your workspace root folder and create a new folder there named "todo-api".

Then create "package.json" and "server.js" files into "todo-api" folder like below.

package.json

{
    "name": "todo-api",
    "version": "1.0.0",
    "scripts": {
        "start": "node server.js"
    },
    "dependencies": {
        "express": "^4.16.4"
    }
}

server.js

const http = require('http');
const express = require('express');
const app = express();
app.use(express.json());
app.use('/', function(req, res) {
    res.send('todo api works');
});
const server = http.createServer(app);
const port = 3000;
server.listen(port);
console.debug('Server listening on port ' + port);

After creating above files open your terminal in the "todo-api" folder and run npm installcommand.

This command will be install your project dependencies which pointed at the "package.json" file.

After finished package download process, downloaded dependency files will be installed into"node_modules" folder at the root of the "todo-api" folder.

After finished package installing then run npm start to start our Web API.

Now our Web API listening. To see result open your web browser then write localhost:3000 to address bar and press enter.

As result you’ll see our request handler response in your browser: “todo api works”.

This is a dead simple Express.js Web API. And it needs the some development. For example we need to an api endpoint to get todo items. So let’s add a new API endpoint for this.

Create a new folder named "routes" in the root of the "todo-api" folder.

Then create a "items.js" file inside of "routes" folder and put following codes inside it.

Your final folder structure should be like below;

/todo-api
/node_modules
/routes
    items.js
package.json
server.js

items.js

const express = require('express');
const router = express.Router();
const data = [
    {id: 1, title: 'Finalize project', order: 1, completed: false, createdOn: new Date()},
    {id: 2, title: 'Book ticket to London', order: 2, completed: false, createdOn: new Date()},
    {id: 3, title: 'Finish last article', order: 3, completed: false, createdOn: new Date()},
    {id: 4, title: 'Get a new t-shirt', order: 4, completed: false, createdOn: new Date()},
    {id: 5, title: 'Create dinner reservation', order: 5, completed: false, createdOn: new Date()},
];
router.get('/', function (req, res) {
    res.status(200).json(data);
});
router.get('/:id', function (req, res) {
    let found = data.find(function (item) {
        return item.id === parseInt(req.params.id);
    });
    if (found) {
        res.status(200).json(found);
    } else {
        res.sendStatus(404);
    }
});
module.exports = router;

Initial code of "items.js" file contains two endpoints. First one gets all todo items and second one gets one item which matches given id parameter.

Before testing items routes we should register it in the "server.js" file.

Modify "server.js" file like below to register new item routes.

server.js

const http = require('http');
const express = require('express');
const itemsRouter = require('./routes/items');
const app = express();
app.use(express.json());
app.use('/items', itemsRouter);
app.use('/', function(req, res) {
    res.send('todo api works');
});
const server = http.createServer(app);
const port = 3000;
server.listen(port);
console.debug('Server listening on port ' + port);

Now run npm start to start our Web API.

Then open your web browser and write localhost:3000/items to address bar and press enter.

You’ll see todo items json array in the response body.

And write localhost:3000/items/3 to address bar and press enter.

You’ll see the todo item which has id 3 in the response body.

But not finished up yet.

CRUD Operations and HTTP methods

I think we’ll need CRUD operations to Create, Read, Update and Delete todo items.

We have already two endpoints for getting items. So we need Create, Update and Delete endpoints.

Let’s add also these endpoints into the items.js file.

Our final "items.js" file and endpoints should be like below.

const express = require('express');
const router = express.Router();

const data = [
  {id: 1, title: 'Finalize project',          order: 1, completed: false, createdOn: new Date()},
  {id: 2, title: 'Book ticket to London',     order: 2, completed: false, createdOn: new Date()},
  {id: 3, title: 'Finish last article',       order: 3, completed: false, createdOn: new Date()},
  {id: 4, title: 'Get a new t-shirt',         order: 4, completed: false, createdOn: new Date()},
  {id: 5, title: 'Create dinner reservation', order: 5, completed: false, createdOn: new Date()},
];

router.get('/', function (req, res) {
  res.status(200).json(data);
});

router.get('/:id', function (req, res) {
  let found = data.find(function (item) {
    return item.id === parseInt(req.params.id);
  });

  if (found) {
    res.status(200).json(found);
  } else {
    res.sendStatus(404);
  }
});

router.post('/', function (req, res) {
  let itemIds = data.map(item => item.id);
  let orderNums = data.map(item => item.order);

  let newId = itemIds.length > 0 ? Math.max.apply(Math, itemIds) + 1 : 1;
  let newOrderNum = orderNums.length > 0 ? Math.max.apply(Math, orderNums) + 1 : 1;

  let newItem = {
    id: newId,
    title: req.body.title,
    order: newOrderNum,
    completed: false,
    createdOn: new Date()
  };

  data.push(newItem);

  res.status(201).json(newItem);
});

router.put('/:id', function (req, res) {
  let found = data.find(function (item) {
    return item.id === parseInt(req.params.id);
  });

  if (found) {
    let updated = {
      id: found.id,
      title: req.body.title,
      order: req.body.order,
      completed: req.body.completed
    };

    let targetIndex = data.indexOf(found);

    data.splice(targetIndex, 1, updated);

    res.sendStatus(204);
  } else {
    res.sendStatus(404);
  }
});

router.delete('/:id', function (req, res) {
  let found = data.find(function (item) {
    return item.id === parseInt(req.params.id);
  });

  if (found) {
    let targetIndex = data.indexOf(found);

    data.splice(targetIndex, 1);
  }

  res.sendStatus(204);
});

module.exports = router;

Short Explanation

I wanna explain shortly some points of our last codes.

First of all you must have noticed that our api works on a static data and keeps it on memory. All of our GET, POST, PUT and DELETE http methods just manipulate a json array. The purpose of this is to keep article simple and draw attention to the Web API structure.

Due to this situation our POST method has some extra logic such as calculating next item ids and order numbers.

So you can modify logic and data structures in these http methods to use a database or whatever you want.

Testing API with Postman

We have tested the GET methods of our Web API in our web browser and seen responses. But we can’t test directly POST, PUT and DELETE http methods in web browser.

If you want to test also other http methods you should use Postman or another http utility.

Now I’ll show you how to test the Web API with Postman

Before we start click here and install Postman.

When you first launch Postman after installing you’ll see start window. Close this start window by clicking close button on top right corner. Then you must see following screen.

An empty Postman request

Sending GET Request

Before sending a request to API we should start it by running npm startcommand as we do before.

After start the Web API and seeing “Server listening on…” message write localhost:3000/itemsto address bar as seen below and click Send button. You'll see todo items array as API response like below.

Sending a GET request with Postman

You can try similarly by giving an item id in request url like this localhost:3000/items/3

Sending POST Request

To sending a POST request and create a new todo item write localhost:3000/items to address bar and change HTTP verb to POST by clicking arrow at front of the address bar as seen below.

Sending a POST request with Postman

Before sending the POST request you should add request data to body of the request by clicking body tab and selecting raw and JSON as seen below.

Attaching a JSON body to POST request in Postman

Now click Send button to send POST request to the Web API. Then you must get “201 Created” http response code and seeing created item in the response body.

To see the last status of todo items send a get request to localhost:3000/itemsaddress. You must see newly created item at the end of the list.

Sending PUT Request

Sending PUT request is very similar to sending POST request.

The most obvious difference is request url should be pointed specific item like this localhost:3000/items/3

And you should choose PUT as http verb instead of POST and send all of the required data in the request body unlike POST.

For example you could send a JSON body in the PUT request as below.

An example JSON body for PUT request

{
    "title": "New title of todo item",
    "order": 3,
    "completed": false
}

When you click Send button you must get “204 No Content” http response code. You can check item you updated by sending a get request.

Sending DELETE Request

To send a DELETE request, change the request url to address a specific item id like this localhost:3000/items/3

And select DELETE as http verb and click Send button.

You must get “204 No Content” http response code as result of the DELETE operation.

Send a get request and see the last status of list.

About the DELETE Http Request

I want to say a few words about DELETE http request. You must have noticed something in our delete code. DELETE request returns “204 No Content” every situation.

Http DELETE requests are idempotent. So what that mean? If you delete a resource on server by sending DELETE request, it’s removed from the collection. And every next DELETE request on the same resource won’t change outcome. So you won’t get “404 Not Found” in the second request. Each request returns same response whether succeed or not. That’s mean idempotent operation.

Conclusion

Finally we’ve tested all http methods of our Web API.

As you can see, it works just fine.

Thanks for reading ❤

If you liked this post, share it with all of your programming buddies!

Develop this one fundamental skill if you want to become a successful developer

Throughout my career, a multitude of people have asked me&nbsp;<em>what does it take to become a successful developer?</em>

Throughout my career, a multitude of people have asked me what does it take to become a successful developer?

It’s a common question newbies and those looking to switch careers often ask — mostly because they see the potential paycheck. There is also a Hollywood level of coolness attached to working with computers nowadays. Being a programmer or developer is akin to being a doctor or lawyer. There is job security.

But a lot of people who try to enter the profession don’t make it. So what is it that separates those who make it and those who don’t? 

Read full article here