The emphasis of this tutorial is on how to use GraphQL to execute all four CRUD actions in the context of a Node.js with MongoDB API. The second part builds a React client for our API. You can do Part 1 without doing Part 2.

You should be able to build the API even if you are new to Node.js and to GraphQL. You do, however, need to be comfortable using the command line, and have Node.js installed.


GraphQL is a query and manipulation language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL was developed internally by Facebook in 2012. It was released publicly in 2015, and in 2018 was spun off into the GraphQL Foundation, hosted by the non-profit Linux Foundation.

The standard architecture on the web for interacting with a database is called REST (Representational State Transfer). It has separate HTTP method and URL combinations (called endpoints) for each CRUD action you want to perform. For example, to get a list of articles you would make an HTTP GET request to example.com/articles. To post an article you would make an HTTP POST request to example.com/articles. To delete an article you would make a DELETE request to example.com/articles/:id, and so on.

GraphQL has only one endpoint, a POST request to your domain. In your API you set up responses to specific named queries and mutations that you expect to receive. Queries are readrequests (GET) while Mutations are write requests (POST, PUT or PATCH, and DELETE). The advantage to using GraphQL is the client can request exactly what they need in one request, which the API can then process as one request. If your app displays a lot of different data on one page the advantage of this architecture becomes obvious. For a simple app though, GraphQL would be overkill, so REST will still have its place.


Apollo is a software company that provides a number of tools and services around the GraphQL query language. On the client side Apollo has some very useful features for React that work with Angular and Vue as well.

For a GraphQL API you need to install the graphql npm package to process query and mutation requests. GraphQL is database agnostic meaning you can use whatever database you prefer. You still use the same ORM to make the database queries, in our case we will be using Mongoose.js with a MongoDB database.

To use GraphQL in a Node app you need a GraphQL HTTP Server. There are two popular npm packages that provide one: apollo-server and express-graphql.

Which is better? Well, they both get the job done. And for the simple app we are going to build either will work just as well and the code is pretty similar. Down the line Apollo Server does have some features that make it a good option. It allows you to cache data on the server side. It also has good error messaging capabilities. In this tutorial we will use Apollo-server.

The other option, Express-graphql, was created by Facebook and is now maintained by the GraphQL Foundation. In terms of npm downloads, it is roughly equal to apollo-server (including apollo-server-express). To see an alternate version of this tutorial using express-graphql go to: Build a GraphQL API with Node, Express, and MongoDB Tutorial

A note on syntax. Node recognizes most, but not all, ES6 syntax. Notably it does not recognize import/export statements, so we won't use them here. While we won't do it in this tutorial, you have the option of installing Babel which will transpile ES6 to ES5 for you at run time.

Project Setup

Create a directory for your project and cd into it. We'll call it graphql-node-apollo-server-and-react. I know that's a lot of typing so use a shorter name if you like.

  • mkdir graphql-node-apollo-server-and-react && cd graphql-node-apollo-server-and-react
  • Create the files for our API. You can use these UNIX commands from the command line, or do it in your text editor. Mkdir creates a directory and touch creates a file.

    touch server.js
    mkdir models
    touch models/article.js
    mkdir graphql
    touch graphql/typeDefs.js
    touch graphql/resolvers.js


    Generate a package.json file. This file contains the metadata for a Node.js project. Adding the --yes flag (-y for short) will use the defaults for the fields.

  • npm init -y
  • Install our npm packages. First the global packages. These get installed on your system and not attached to any specific project. The Nodemon package will allow you to do hot reloading of the app, which means it will restart the server every time you make a change and save it in your project. The --global or -g flag makes it global. If you already have it installed this will upgrade it to the latest version.

  • npm install -g nodemon
  • Local packages will be installed directly in your project in the node_modules folder. Install the following packages:

  • npm install mongoose graphql apollo-server
  • Your package.json file should look something like the below. A list of the project's installed packages with version and their dendencies is saved under dependencies. If you run npm install then npm would attempt to install or update the packages listed there.

    Make sure the "main" property's value is "server.js". Change it if it isn't.

    Also make sure you have the "start" script. Add it if you don't. You can run the commands in the scripts object from the command line with npm run script-name. I'll explain more in the next section.

     1 {
     2   "name": "graphql-node-apollo-server-and-react",
     3   "version": "1.0.0",
     4   "description": "GraphQL API built with Node.js, Apollo-Server, MongoDB, and React",
     5   "main": "server.js",
     6   "scripts": {
     7     "test": "echo \"Error: no test specified\" && exit 1",
     8     "start": "node server.js"
    10   },
    11   "keywords": [],
    12   "author": "",
    13   "license": "ISC",
    14   "dependencies": {
    15     "apollo-server": "^2.8.1",
    16     "graphql": "^14.4.2",
    17     "mongoose": "^5.6.6"
    18   }
    22 }

    You will also see a file named package-lock.json. That lists all the project's installed packages with their versions and their dendencies.

    Hello World

    Following the ancient traditions of my people we will start things off with a Hello World app powered by Apollo-Server. Actually this is the same Hello World app from the Apollo-Server docs. We'll use it as our starting point.

     1 const { ApolloServer, gql } = require('apollo-server');
     3 const PORT = 4000;
     5 // The GraphQL schema
     6 const typeDefs = gql`
     7   type Query {
     8     hello: String
     9   }
    10 `;
    12 // A map of functions which return data for the schema.
    13 const resolvers = {
    14   Query: {
    15     hello: function() { return 'world' }
    16   }
    17 };
    19 const server = new ApolloServer({
    20   typeDefs,
    21   resolvers,
    22 });
    24 server.listen(PORT).then(function({ url }) {
    25   console.log(`Server listening at ${url}.`);
    26 });

    This simple file will create a GraphQL HTTP server. When this file is run it creates an instance of ApolloServer. It takes an options object as its parameter with two required properties: typeDefs and resolvers. TypeDefs is a string representing the GraphQL schema. Resolvers is a map of functions that implement your schema.

    The server is listening for connections to localhost port 4000. Actually, ApolloServer uses port 4000 by default so you could leave the port out.

    You may have noticed that there is no Express. Express is the most popular web framework for Node.js. So what gives? Well, Express is there. It's a dependency of apollo-server, along with apollo-server-express, which connects apollo-server with express. Apollo-server uses them behind the scenes. For existing Express apps, if you want to keep Express in the foreground I'll show you how to do that at the end of this tutorial.

    Start the Server

    Now that you have a server for your app, there are a few different commands you can use to run it. I'll cover them briefly. First there is a standard node command to execute any file:

  • node server.js or just node server
  • This should log "Server listening at http://localhost:4000/." in the terminal, and if you open a browser to localhost:4000 you should see the GraphQL Playground (more on that in a minute). To stop the server, from the terminal press CTRL+C,

    We put a start script in the package.json file. You call the scripts in your package.json file with npm run script-name or in a few cases "run" is optional.

  • npm start
  • We add this script because many production servers start the app by calling npm start.

    The third way to execute the server.js file is to use the nodemon command. We installed nodemon for hot reloading. To use it just enter nodemon and it looks for a file named server.js to run by default. This is the command we'll use throughout this tutorial.

  • nodemon
  • GraphQL Playground

    Both apollo-server and express-graphql come with GUI tools that you run in your browser on your app's URL to send HTTP requests. Apollo-server's tool is called GraphQL Playground. It is similar to API request tools like Postman or Curl except it is specific to a GraphQL API.

    Let's test out our Hello World app by pasting a query in the left side panel:


    Then run it by clicking on the Execute Query button in the middle. The output should look like:

      "data": {
        "hello": "world"

    Now let's build something closer to a real app. One that uses a database.

    MongoDB database

    MongoDB is a noSQL database. If you've only dealt with relational databases like PostgreSQL, MySQL, SQLite, etc., it's a different way of thinking about it. This tutorial won't get into how noSQL works. There are plenty of other resources for that such as mongodb.com/nosql-explained. Don't worry if you've never used it before, you can continue with this tutorial without a problem.

    MongoDB offers a cloud database called Atlas, or you can download the "community version" on your local machine. You may have read about another popular cloud solution called mlabs, but they were bought by MongoDB and are no longer accepting new accounts. Which to use? I generally prefer a local version to work with in development. Sometimes there are connection issues with a cloud version (which I ran into). But if you deploy your app to Heroku then you need a cloud solution. I will explain both ways.

    Quick terminology for those used to relational databases. "Collections" are like tables in a relational database and "documents" are like records.

    Also, don't worry about creating the collections. Once you create the database and connect to it, MongoDB will automatically create a collection for you when you save a document to a collection that doesn't exist yet.

    Option 1) MongoDB Atlas

    At the time of this writing you can set up a free account for a small project like this one at mongodb.com/cloud/atlas. It is very fast and straight-forward to set up. Just follow their steps, but note a few things:

    Option 2) MongoDB local "community" version

    I'm on a Mac so I'll explain briefly how to load the MongoDB community version on a Mac using Homebrew. If you are on windows, installation instructions are at docs.mongodb.com/manual/tutorial/install-mongodb-on-windows.

    To see if MongoDB is installed on your machine with Homebrew run:

  • brew list mongodb or brew search mongodb
  • If it's installed it will show the path of any MongoDB executable files (there are several). If it's not installed run:

  • brew install mongodb
  • Now set up the directory where the database is stored. MongoDB expects you to use data/db so we'll go with that:

  • sudo mkdir -p data/db
  • And set the owner of this directory to yourself.

  • sudo chown -R $(whoami) data/db
  • Sudo means Super User Do. You need that for commands that require super user permissions like messing around with you Mac's root directory. Chown means change owner. The -R flag makes this recursive meaning it changes the owner of all the files and folders in the directory. $(whoami) runs the UNIX whoami command to get your username on your computer.

    If MongoDB was already installed you can check to see if the version is outdated.

  • brew outdated mongodb
  • This will return your version compared to the latest version if they differ. If it returns nothing then you have the latest version. You can upgrade the MongoDB version with:

  • brew upgrade mongodb
  • Run MongoDB

    Once it's installed you run MongoDB from the terminal (from any directory) and have to leave that window open and running to access the database:

  • mongod
  • Stop MongoDB from the same terminal window with CTRL+C.

    MongoDB Shell

    You can interact with the database directly using the MongoDB shell from the Terminal. There are also GUI tools like Robo T3 if you prefer that but frankly for small projects I find the command line easier to work with. To use the shell from anywhere in the terminal run:

  • mongo
  • The list of MongoDB commands are at docs.mongodb.com/manual/reference/mongo-shell and CRUD Operations. Useful commands include:

  • show dbs - Returns a list of your databases.
  • use my_local_db - Use the specified database. Creates it if it doesn't exist.
  • db - Returns the db you are currently in.
  • show collections - Returns the collections in the db you are currently in. Collections are like tables in an SQL database.
  • db.createCollection("articles") - Create a collection.
  • db.articles.find() - Show all documents in the collection. Documents are like records in an SQL database.
  • db.articles.insertOne( { title: "Learn MongoDB", content: "Lorem Ipsum." } ) - Insert a document into a collection. Must use double quotes for a string.
  • db.articles.find({title: "Learn MongoDB"}) - Returns all that match the condition.
  • db.articles.findOne({title: "Learn MongoDB"}) - Returns the first document that matches the condition.
  • db.articles.findOne({"_id" : ObjectId("id-string-here")}); - Find by id.
  • db.articles.updateOne({title:"Learn MongoDB"},{ $set: {content:"Blah Blah."}}) - Updates a specific field in a document. Adds document if it doesn't exist. Use updateMany() for multiple.
  • db.articles.update({"_id" : ObjectId("id-string-here")},{ $set: {content:"Blah Blah Blah!"}}) - Find by ID then update.
  • db.articles.deleteOne( { title: "Learn MongoDB" }) - Remove one document by non-id field. Use deleteMany() for multiple.
  • db.articles.deleteOne( { "_id": ObjectId("value") }) - Remove by id.
  • db.dropDatabase() - Deletes the db you are currently in.

  • Mongoose ORM

    We installed the mongoose package in our app. Mongoose is a middleware library that performs the Object Relational Mapping (ORM) between our web application and MongoDB. In essence it does the translations allowing them to talk to each other.

    We'll start by using Mongoose to connect to MongoDB in our server file. Let's replace the hello world response with our real code. This is all the code we will use for this file, but I'll only explain the Mongoose code for now.

     1 const { ApolloServer } = require('apollo-server');
    1. const mongoose = require('mongoose');
     4 const typeDefs = require('./graphql/typeDefs');
     5 const resolvers = require('./graphql/resolvers'); 
    2. const PORT = 4000; 
    3. const MONGODB_URI = "mongodb://localhost:27017/my_local_db";   
    4. mongoose.connect(MONGODB_URI, { useNewUrlParser: true, useFindAndModify: false }); 
    5. mongoose.connection.once('open', function() { 
    12   console.log('Connected to the Database.');
    13 });
    14 mongoose.connection.on('error', function(error) {
    15   console.log('Mongoose Connection Error : ' + error);
    16 });
    18 const server = new ApolloServer({ 
    19   typeDefs, 
    20   resolvers 
    21 });
    23 server.listen(PORT).then(({ url }) => {
    24   console.log(`Server listening at ${url}.`);
    25 });
    1. Import the mongoose package.
    2. Assigning constants for things like port number and the database URL gives us the flexibility to change the values in one place depending on whether we are in development or production environments.
      If you are using the MongoDB Atlas cloud database then paste the link there.
      If you are using the local version, MongoDB is accessed through localhost on port 27017 by default, and the path is the database name. We'll just call ours my_local_db.
    4. mongoose.connect() connects to your MongoDB database. The useNewUrlParser and useFindAndModify options are Mongoose requirements for some deprecation transitions. You'll get a warning if you leave them out.
    5. Optionally, log a message if the above connection was successful and one if it is not.


    Let's talk briefly about Cross-Origin Resource Sharing (CORS). By default Express will block cross-origin HTTP requests for security reasons. Cross-origin means the request is from a different origin (domain, protocol, and/or port) than its own origin. Our API is served on localhost port 4000. If our API is accessed by mobile apps for example they will be on a different origin and any requests will be rejected by default. To allow access from any origin (or from origins that you specify) you can install and configure an npm package called cors. That will apply additional HTTP headers to tell the browser to allow cross-origin access.

    An API paired with a dedicated client like React is really an integrated single web application even if the back end and front end run independently. In our app we only want to give access to the integrated front end (our React client in this case). When we create the Client we'll make our API a proxy for that app (explained later) and therefore won't need the cors package.

    If you also want the API to be accessed by mobile apps or other websites then you need to install the cors middleware package and connect it to your app.

  • npm install cors
  • // server
    const cors = require('cors');
    const server = new ApolloServer({ 
      cors: true

    Install and import the cors package. Then add it as an option to the the ApolloServer instance.

    For more on the CORS concept see developer.mozilla.org/en-US/docs/Web/HTTP/CORS

    The Model

    Populate the model file with the below:

     1 const { Schema, model } = require('mongoose');
    1. const articleSchema = new Schema({ 
     4   title: {
     5     type: String,
     6     required: true
     7   },
     8   content: {
     9     type: String,
    10     required: true
    11   }
    12 });
    2. module.exports = model('Article', articleSchema);
    1. A database schema is it's structure. The mongoose schema is a prototype that maps to a MongoDB collection and defines the shape of the documents within that collection. Here we are creating an instance of mongoose.Schema that defines two fields with type set to String and making them required. Mongoose Schema reference: mongoosejs.com/docs/guide.html.
    2. Models represent the data in an application. A mongoose model is a constructor function that creates and reads documents to and from the underlying MongoDB database. The first argument is the singular uppercase name of your database collection. So Article represents the articles MongoDB collection. The second argument is the schema which we defined above. An individual article is an instance of the Article model.


    If you are brand new to GraphQL I recommend going through the Getting Started tutorial on their website https://graphql.org/graphql-js. This tutorial is the next level up from where that ends. I won't repeat the basics here but will apply them to a real (albeit super basic) application with an actual database. And use all four CRUD operations.

    I skipped over explaining the Apollo/GraphQL specific code in the server.js file so I'll cover it now:

    // server.js
    const { ApolloServer } = require('apollo-server'); #1
    const typeDefs = require('./graphql/typeDefs');
    const resolvers = require('./graphql/resolvers'); 
    const server = new ApolloServer({                  #2
      typeDefs,                                        #3
    1. Import the apollo-server package which will set up our GraphQL HTTP server.
    2. Create an instance of ApolloServer and assign it to a variable.
    3. There are two properties we must pass into to the ApolloServer constructor function when we create our ApolloServer instance: typeDefs and resolvers. I'll explain each in turn.


    TypeDefs is a required property when creating an ApolloServer instance. It is a string representations of GraphQL schema in GraphQL's Schema Definition Language (SDL). It is generated by the gql function. If you have multiple TypeDefs files you can put them in an Array and ApolloServer will concatenate them.

    Populate the typeDefs file with the below:

    1. const{ gql } = require('apollo-server'); 
    2. const typeDefs =  gql(` 
    3. type Article { 
     5   id: ID!
     6   title: String!
     7   content: String!
     8 }
    4. input ArticleInput { 
    10   title: String!
    11   content: String!
    12 }
    5. type Query { 
    14   articles: [Article]
    15   article(id: ID!): Article
    16 }
    6. type Mutation { 
    18   createArticle(articleInput: ArticleInput): Article
    19   deleteArticle(id: ID!): Article
    20   updateArticle(id: ID!, articleInput: ArticleInput): Article!
    21 }
    22 `)
    7. module.exports = typeDefs; 
    1. Import the gql function from apollo-server. This is actually coming from the graphql library behind the scenes, which is why we had to install the graphql npm package.
    2. The gql function creates a GraphQL schema object from the GraphQL schema language, which is structured like JSON. You pass it in as one big string.
    3. GraphQL, unlike JavaScript, is strongly typed meaning you have to explicitly specify the data types. Here we are defining Article as a type. And we are specifying it's fields, and what each field's data type is. The GraphQL schema language supports the scalar types of String, Int, Float, Boolean, and ID. Adding ! at the end means the field is required.
    4. We'll use an input for our Create and Update mutations. Set required string fields for title and content.
    5. List the queries we will accept and what will be returned. So here we accept a query called article with id as it's parameter that returns the Article type defined above. We also accept a query called articles and return an array of the Article type defined above. Putting Article in brackets signifies that it will be an array of articles. "Article" and "articles" are the names of resolver functions that will make the actual queries to the the mongoDB database and return the results. We could define them in this file but we'll define them in a separate resolver file.
    6. Mutations are HTTP requests to modify data. We'll define three, which will call resolver functions to create, delete, and update an Article.
    7. Export the typeDefs object for it to be used as the schema for our graphqlHTTP server.


    Resolvers is the other required property after typeDefs when creating an ApolloServer instance. Resolvers is an object that maps resolvers with the types defined in typeDefs. The resolvers are grouped by category (i.e., Query, Mutation, Subscription, or custom). For each resolver the key should be the type name and the value should be a function to be executed for that type.

    Each resolver is the equivalent to an endpoint in a RESTful API. Instead of an HTTP method/URL path combinations like GET example.com/articles, the client sends a named query or mutation that is equal to one of the resolver functions. The resolver functions are where we will interact with the database. In our case it is MongoDB with Mongoose.js as our ORM. Let's define these now:

    1. const Article = require('../models/article');          
     3 const resolvers = {
     4   Query: {
    2.     articles: function() {                     
     6       return Article.find({});
     7     },
     8     article: function(parent, args) {
     9       return Article.findById(args.id)
    10     }
    11   },
    12   Mutation: {
    13     createArticle: function(parent, args) {
    14       let article = new Article(args.articleInput);
    15       return article.save();
    16     },
    17     deleteArticle: function(parent, args) {
    18       return Article.findByIdAndRemove(args.id);
    19     },
    20     updateArticle: function(parent, args) {
    3.       return Article.findByIdAndUpdate(args.id, args.articleInput, { new: true });
    22     }
    23   }
    24 };
    26 module.exports = resolvers; 
    1. Import the Article model which connects us to the database.
    2. There is one resolver function per API endpoint. Each performs a CRUD action on the database and returns the result. The find, findById, save, findByIDandRemove, and findByIdAndUpdate are all methods from the Mongoose ORM.
    3. The Model.findByIdAndUpdate(id, update, options, callback) method returns the original document by default not the updated one. To return the updated object you need to add the {new: true} option.

    Test the API with GraphQL Playground

    ApolloServer includes a web interface where you can send queries and mutations to the API. We used it briefly to test the Hello World app. Let's try it again, this time we'll test out our API against the database and make sure that the CRUD operations all work.

    First, if you are using a local MongoDB database, make sure it is running.

  • mongo
  • With all the changes we made it would be a good idea to restart the server.

  • nodemon
  • Open the browser to localhost:4000. Note that since we are only using Apollo-Server and not Express, we have no routers. That means the API will ignore any path you tack on to the domain. Localhost:4000/this/is/a/bogus/path will also work.

    In the browser you'll see the GraphQL API interface. In the panel on the left enter the createArticle mutation:

    mutation {
      createArticle(articleInput: { title: "Learn GraphQL", content: "Blah blah."}) {
        id, title, content

    This mutation will call the createArticle endpoint we defined on the resolvers file, passing in the articleInput object with values for the title and content properties. The property names in the curly braces are what we want returned if the mutation is successful. Click the execute button on the center and you should see the output on the panel to the right.

    Change the title and press it again so we have another article.

    Now let's do a query to to see our articles:

    query {
      articles {

    Press the execute button and you should see the articles you created. Copy the id of one of them and enter another query. (Note: query is the default type so you can leave off the word "query" if you like).

    query {
      article(id: "article-id-here") {

    Update that same article:

    mutation {
      updateArticle(id: "article-id-here", articleInput: { title: "Learn MongoDB", content: "Blah blah." }) {
        id, title, content

    Click execute and you should get the updated article back.
    And finally, send a delete mutation:

    mutation {
      deleteArticle(id: "article-id-here") {
        id, title, content

    Click execute and it should return null after deleting it. There is a history tab on the top left and if you click it you should see all the queries and mutations you ran. Select the the articles query then the Use button. It will queue up the query so you can execute it. The deleted article should be gone.

    There is a Docs menu on the right of the tool that lists all your query and mutation endpoints. You can drill down on it to see the available fields.

    If you are familiar with the Postman API query tool, recent versions have a GraphQL feature that allows you to do these same queries/mutations. It has the added advantage of being able to save your queries.

    Ta Da!, you are done with the API portion of the Build a GraphQL CRUD application with Node and React tutorial. If you want to add a React front end then on to part 2 Build a React app with GraphQL Tutorial

    Bonus Section

    Install Apollo-Server on an existing Express App

    If you want to tack GraphQL onto an existing Express app using Apollo-server most of the steps are the same as above. Just make the below changes.

    In addition to the apollo-server and mongoose packages, you need to install express and apollo-server-express. Technically they are already installed as dependencies to apollo-express, but it's cleaner to install them explicitly so they get listed in package.json:

  • npm install express apollo-server-express
  • Then change the server.js file to the below. The changes are bolded.

     6 const express = require('express');
     7 const { ApolloServer } = require('apollo-server-express');
     8 const mongoose = require('mongoose');
    10 const typeDefs = require('./graphql/typeDefs');
    11 const resolvers = require('./graphql/resolvers'); 
    13 const PORT = 4000; 
    14 const MONGODB_URI = "mongodb://localhost:27017/my_local_db";   
    16 const app = express();
    18 mongoose.connect(MONGODB_URI, { useNewUrlParser: true, useFindAndModify: false }); 
    19 mongoose.connection.once('open', function() { 
    20   console.log('Connected to the Database.');
    21 });
    22 mongoose.connection.on('error', function(error) {
    23   console.log('Mongoose Connection Error : ' + error);
    24 });
    26 const server = new ApolloServer({ 
    27   typeDefs, 
    28   resolvers 
    29 });
    31 server.applyMiddleware({ app, path: '/graphql' });
    33 app.listen(PORT, function() { 
    34   console.log(`Server listening on port ${PORT}.`);
    35 });

    Most of this is the standard Express setup.

  • You import Express.
  • Call express() to instantiate an instance of express and assign it to a variable called "app".
  • Listen for connections to your app with express's listen() method.
  • On the ApolloServer side, most of this is the same. You create an instance of ApolloServer passing in typeDefs and resolvers as the options parameter. But this time you add the express app as middleware, and you set an explicit path for the graphql endpoint. The other files remain the same. That's it!