DEV Community

Cover image for How to Build A GraphQL API with Node.js, Apollo-Server, and MongoDB Atlas
The ERIN
The ERIN

Posted on • Edited on

How to Build A GraphQL API with Node.js, Apollo-Server, and MongoDB Atlas

Introduction

Are you a developer looking to create an efficient and flexible API? Then look no further than GraphQL! GraphQL is a modern way of creating APIs that allow the frontend to request only the data they need, making it a more efficient alternative to REST APIs.

If you're a Node.js developer looking to build a GraphQL API, you're in the right place! In this article, you are going to create a food recipe GraphQL API with Node.js, Apollo Server, and MongoDB Atlas. With Apollo Server, you'll get a production-ready GraphQL implementation with many features. MongoDB Atlas is a database service that makes it easy to scale your application.

But before diving into the details, let's ensure we're all on the same page. If you're new to GraphQL, don't worry! You can check out our previous article on the fundamentals of GraphQL . It's an excellent introduction to the topic and a solid foundation for building your GraphQL API.

 

Prerequisites

  • Basic understanding of Node.js syntax and JavaScript
  • Fundamentals of GraphQL and its syntax
  • npm installed on your computer. You can get it here if you don’t have npm installed on your computer.

 

Setting up a MongoDB Atlas cluster

Before building the API, you need to set up your MongoDB Atlas cluster. An Atlas cluster is a group of servers configured to store and manage data in MongoDB. These clusters can be customized to meet specific performance, scalability, and security requirements. It’s a straightforward process, and I am going to walk you through the steps.

MongoDB Atlas is a cloud-based database service offered by MongoDB that provides a fully managed and scalable environment for our MongoDB databases. Think of it as a group of computers working together to store and manage data, ensuring it is always available and secure.

To get started,

  • Go to the MongoDB Atlas website on your browser by clicking here. If you have an account, you can log in or create one if you are not an existing user. It is free.

 

  • Once signed in, you are going to be redirected to your dashboard, where all the different functionalities and integrations can be performed. Click the “Build a Database” button to start building the database.

 

Image description

 

  • You need to set up basic configurations for your database. Select the M0 Free tier, as it suits your needs.

     

  • Choose any cloud service provider options in the “Provider” section. For this article, I recommend you select the AWS option.

     

  • For the region field, select the region closest to where you are.

     

  • Choose a suitable name for the cluster. It can be simple, as you can't change the name once the cluster is created. You can call your cluster “Food-Recipe” because you are building a food recipe API.

     

  • Once you have configured all primary fields in the template, click the “Create” button. This button creates the Atlas cluster needed for this article.

 

Image description

 

  • It would be best if you also authenticated your connection. MongoDB automatically generates a username and password for the first database user using the details provided during sign-up. To create other users with permission to read and write, choose the “Username and Password” option, which lets you create a user with a username and a secure password. Once you have inputted all the details, click the “Create User” button to save the user credentials.

 

Image description

 

  • You need to enable the network(s) access to read and write data to your cluster. MongoDB automatically adds your current IP address, but you can add more networks to access your cluster. Select the “My Local Environment” option to add an IP address to your network list. Input the IP address and add a short description to identify the IP address. Click the “Add Entry” button to add the network.

 

Image description

 

  • Click the “Finish” button to fully set up the “Food-Recipe” cluster. You can now connect your database to your application and start writing your API.

 

Connecting your application to the MongoDB database

To connect your application to your MongoDB database. You need to obtain the connection string for your cluster. To obtain the connection string,

 

  • Login to your dashboard, click the “Connect” button for your cluster

     

Image description

 

  • A list of different options to connect your application is available for you to choose from. I recommend you choose the “Drivers” method, as this method lets you use Node.js as your runtime environment.

 

Image description

 

  • Make sure you select the latest version of Node.js. You can then copy the connection string to use in your application. Keep this copy in your notepad, as it is needed in your index.js file to connect your application to your MongoDB database.

 

Image description

 

Setting up a development environment

In this section, you are going to set up a development environment by creating a project, installing necessary dependencies, and connecting your MongoDB Atlas cluster to your application.

  • In your preferred code editor, Open a new terminal and run the following command.

     



npm init –yes


Enter fullscreen mode Exit fullscreen mode

This command creates an empty package.json . The empty package.json is essential for installing and managing the dependencies for the project.

  • Installing the dependencies: In the terminal, run the following command.


npm install apollo-server graphql mongoose nodemon


Enter fullscreen mode Exit fullscreen mode

The following command installs the four packages needed for this project. The apollo-server dependency lets you create a server that can handle GraphQL queries and mutations. The graphql package provides the tools necessary to build and execute GraphQL queries and mutations, the mongoose package offers a simple way to interact with MongoDB databases, and the nodemon package automatically restarts your Node.js application whenever you make changes to your code.

  • Inside your package.json file, you need to include a start command. Whenever you run npm start in the terminal, the start command runs the index.js file, which serves as a server.


"scripts": {
    // include the start command after the “test” command
    "start": "nodemon index.js"
  },    


Enter fullscreen mode Exit fullscreen mode
  • In your project folder, create an index.js file. The code in the index.js file sets up an Apollo Server that handles your GraphQL queries, and mutations connect to a MongoDB database using mongoose and start listening for requests on a specified port.

     

  • In the index.js file, copy the following code.



// This is getting the ApolloServer object out of the Apollo-server library
const { ApolloServer } = require("apollo-server");

// This gives you access to Mongoose from the Mongoose package.
const mongoose = require("mongoose");

//You need to create a variable to store the MongoDB string you got from your database. Go to where you saved it and get the string. Replace the `username` and `password` with your MongoDB Atlas username and password.

const MONGODB =
  "mongodb+srv://<username>:<password>@food-recipe.7brtcty.mongodb.net/?retryWrites=true&w=majority"
const typeDefs = require("./graphql/typeDefs");
const resolvers = require("./graphql/resolvers");
const server = new ApolloServer({
  typeDefs,
  resolvers,
});

//You want your apollo-server to interact with Mongoose by passing in your MongoDB URI
mongoose
  .connect(MONGODB, { useNewUrlParser: true })
  .then(
    // to show when the connection is made
    () => {
      console.log("MongoDB Connected Successfully");
      return server.listen({ port: 5000 });
    }
  )
  // to handle the response object and show where your server is running
  .then((res) => {

    console.log(`Server is running on port ${res.url}`);
  });


Enter fullscreen mode Exit fullscreen mode

Let’s take a closer look at the code, line by line:



const { ApolloServer } = require("apollo-server");


Enter fullscreen mode Exit fullscreen mode

Here, you're importing the ApolloServer object from the apollo-server library. The object lets you create an Apollo server that can handle GraphQL queries and mutations.



const mongoose = require("mongoose");


Enter fullscreen mode Exit fullscreen mode

Next, you're importing the mongoose library, a MongoDB library. It provides a simple way to interact with the MongoDB database.



const MONGODB = "mongodb+srv://feyijimierinle:Backspace@2023@food-recipe.7brtcty.mongodb.net/?retryWrites=true&w=majority";


Enter fullscreen mode Exit fullscreen mode

Here, you defined a variable called MONGODB that stores the URI to your MongoDB database. The URI is a long string that contains your username, password, and host information needed to connect to your database.



const typeDefs = require("./graphql/typeDefs");
const resolvers = require("./graphql/resolvers");


Enter fullscreen mode Exit fullscreen mode

You're importing the type definitions and resolvers from separate files in a graphql folder. Type definitions describe the structure and functionality of a GraphQL schema. At the same time, resolvers handle the logic for resolving GraphQL queries and mutations.



const server = new ApolloServer({
  typeDefs,
  resolvers,
});


Enter fullscreen mode Exit fullscreen mode

Here, you created an instance of ApolloServer, passing in your type definitions and resolvers as arguments. The instance allows your server to handle GraphQL operations.



mongoose
  .connect(MONGODB, { useNewUrlParser: true })
  .then(() => {
    console.log("MongoDB Connected Successfully");
    return server.listen({ port: 5000 });
  })
  .then((res) => {
    console.log(`Server is running on port ${res.url}`);
  });


Enter fullscreen mode Exit fullscreen mode

Finally, you're connecting to your MongoDB database using the mongoose.connect() method, passing in your MongoDB URI as the first argument. Once the connection is established, you're using the server.listen() to start your Apollo Server, passing in an options object that specifies the port number you want to use (in this case, 5000). Finally, you're logging a message to the console to confirm that the server is running and to indicate where it's listening for requests.

 

Setting up the Mongoose Model

In this section, you need to set up your Mongoose model. A Mongoose model is a template that helps you create and handle MongoDB documents in a Node.js app. It sets the data format, including the fields and their types. It allows the apollo-server to interact with MongoDB database.

  • Make a new folder in the root directory of your project. You can name it "models" Make a new file in the models folder called Recipe.js In your recipe.js file, copy the following code.


​​// Getting the model and Schema object from the Mongoose package
const { model, Schema } = require("mongoose");
// Making a new Schema for the recipe
const recipeSchema = new Schema({
  //Here, you need to pass in all the properties expected in the recipeSchema
  name: String,
  description: String,
  dateCreated: String,
  originated: String,
});
// to export data from this file.
module.exports = model("Recipe", recipeSchema);
<p>&nbsp;</p>



Enter fullscreen mode Exit fullscreen mode

Let’s take a closer look at the code, line by line:



const { model, Schema } = require("mongoose");


Enter fullscreen mode Exit fullscreen mode

First, you imported the model and Schema objects from the mongoose package. model is a function that lets you define a new data model. At the same time, Schema is a class that lets you specify the structure and properties of the data model.



const recipeSchema = new Schema({
  name: String,
  description: String,
  dateCreated: String,
  originated: String,
});


Enter fullscreen mode Exit fullscreen mode

You created a new recipeSchema object, an instance of the Schema class. This recipeSchema object defines the structure of your recipe data model, specifying the properties you expect to have in your data model. Your recipeSchema object has four properties: name, description, dateCreated, and originated. Each of these properties is a string data type.



module.exports = model("Recipe", recipeSchema);


Enter fullscreen mode Exit fullscreen mode

Finally, you are exporting the model function from this file, which creates a data model. You're passing two arguments to the model function: "Recipe" and recipeSchema. "Recipe" is the name of your data model, which is used to identify and query this data model later. recipeSchema is the data model structure that you created earlier.

 

Setting up GraphQL typeDefs and resolvers

typeDefs is short for "type definitions." These blueprints describe the shape of the data that can be queried or mutated in a GraphQL API. You can define types like Query and Mutation that represent the root types of your API and then determine the properties and fields that can be queried or mutated under each type.

Resolvers are functions that implement the behaviour of the queries and mutations defined in your typeDefs. Resolvers tell GraphQL how to retrieve the data that's being requested and what to do with the data when it's retrieved.

In this section, you need to define the typeDefs and resolvers necessary for the food-recipe API.

  • Create a folder in the root directory of your project. It would be best if you named it grahql. This folder contains the typeDefs and the resolver files.

     

  • Create two files in the graphql folder and name them typeDefs.js and resolvers.js

     

  • In the typeDefs.js file, copy the following code,



const { gql } = require("apollo-server");
module.exports = gql`
  type Recipe {
    name: String
    description: String
    dateCreated: String
    originated: String
  }
  input RecipeInput {
    name: String
    description: String
    originated: String
  }
  type Query {
    recipe(ID: ID!): Recipe!
    getRecipes(amount: Int): [Recipe]
  }
  type Mutation {
    createRecipe(recipeInput: RecipeInput): Recipe!
    deleteRecipe(ID: ID!): Boolean
    editRecipe(ID: ID!, recipeInput: RecipeInput): Boolean
  }
`;


Enter fullscreen mode Exit fullscreen mode

This code defines the schema for your API using the apollo-server library. The gql function is used to determine the schema using the GraphQL syntax.

The schema defines the types that can be queried and mutated and the fields that can be accessed on those types. This particular schema defines three types: Recipe, RecipeInput, Query, and Mutation.

Recipe is a type that has fields for name, description, dateCreated and originated. This type represents a recipe object in our API. RecipeInput is an input type used for creating and editing recipe objects. It has fields for name, description, and originated, which are the properties that can be passed in from the client side.

A query is a type that defines the read operations in CRUD (Create, Read, Update, Delete) operations. The recipe field is used to retrieve a single recipe by its ID, while the getRecipes field is used to retrieve an array of recipes.

A mutation is a type that defines the write operations in CRUD operations. The createRecipe field is used to create a recipe object, the deleteRecipe area is used to delete a recipe by its ID, and the editRecipe field is used to update a recipe by its ID and create a recipe object.

This schema defines the types, inputs, queries, and mutations used to build the GraphQL API.

In the resolver.js file, copy the following code



// This gives us access to the recipe model you created in the Recipe.js
const Recipe = require("../models/Recipe");
module.exports = {
  Query: {
    // This holds all our queries to the apollo-server
    async recipe(_, { ID }) {
      return await Recipe.findById(ID);
    },
    async getRecipes(_, { amount }) {
      return await Recipe.find().sort({ dateCreated: -1 }).limit(amount);
    },
  },
  Mutation: {
    // This holds all our mutation
    async createRecipe(_, { recipeInput: { name, description, originated } }) {
      // This code is setting up the module.
      const createdRecipe = new Recipe({
        name: name,
        description: description,
        dateCreated: new Date().toISOString(),
        originated: originated,
      });
      const response = await createdRecipe.save(); // This is saying save the cretedRecipe schema or module to our MongoDB
      // need to return a recipe to our apollo-server resolver
      return {
        id: response.id,
        ...response._doc, //take all of the different properties of the result and show all the various properties that are going to show what our recipe is all about
      };
    },
    async deleteRecipe(_, { ID }) {
      const wasDeleted = (await Recipe.deleteOne({ _id: ID })).deletedCount; // use a mongoose function called deleteOne
      return wasDeleted; // the deletedCount returns 1 if something was created and 0 if nothing was created
    },
    async editRecipe(_, { ID, recipeInput: { name, description } }) {
      const wasEdited = (
        await Recipe.updateOne(
          { _id: ID },
          { name: name, description: description }
        )
      ).modifiedCount; // returns an object similarly to the wasDeleted
      return wasEdited; // returns 0 if an ID can't be found
    },
  },
};


Enter fullscreen mode Exit fullscreen mode

Let’s go over the code, line by line:



const Recipe = require("../models/Recipe");


Enter fullscreen mode Exit fullscreen mode

This line imports the Recipe model you created in another file and stores it in the Recipe variable. You can then use the Recipe variable to interact with the Recipe collection in your MongoDB database.



module.exports = {
  Query: {
    async recipe(_, { ID }) {
      return await Recipe.findById(ID);
    },
    async getRecipes(_, { amount }) {
      return await Recipe.find().sort({ dateCreated: -1 }).limit(amount);
    },
  },


Enter fullscreen mode Exit fullscreen mode

The previous block of code exports an object with two properties: Query and Mutation. Query holds our queries to the apollo-server, which lets you read data. There are two query resolvers defined here. The recipe resolver takes an ID argument and returns a single Recipe object matching that ID. The getRecipes resolver takes an amount argument and returns an array of Recipe objects sorted by date created up to the specified amount.



  Mutation: {
    async createRecipe(_, { recipeInput: { name, description, originated } }) {
      const createdRecipe = new Recipe({
        name: name,
        description: description,
        dateCreated: new Date().toISOString(),
        originated: originated,
      });
      const response = await createdRecipe.save();
      return {
        id: response.id,
        ...response._doc,
      };
    },
    async deleteRecipe(_, { ID }) {
      const wasDeleted = (await Recipe.deleteOne({ _id: ID })).deletedCount;
      return wasDeleted;
    },
    async editRecipe(_, { ID, recipeInput: { name, description } }) {
      const wasEdited = (
        await Recipe.updateOne(
          { _id: ID },
          { name: name, description: description }
        )
      ).modifiedCount;
      return wasEdited;
    },
  },


Enter fullscreen mode Exit fullscreen mode

The previous block of code exports an object with a Mutation property. Mutation holds all your mutations on the apollo-server, which lets you write data. There are three mutation resolvers defined here. The createRecipe resolver takes an object with name, description, and originated properties, creates a Recipe object using the Recipe model, saves it to your database, and returns the newly created Recipe object. The deleteRecipe resolver takes an ID argument, finds the Recipe object with that ID in the database, and deletes it. It then returns the number of objects deleted, which is 1 if the delete operation was successful and 0 if it was not. The editRecipe resolver takes an ID argument and an object with name and description properties, finds the Recipe object with that ID in the database, updates its name and description properties, and returns the number of objects modified, which is 1 if the update operations was successful and 0 if it was not.

  • You have successfully created a Food-Recipe GraphQL API. You can now go ahead and test your API in the GraphQL playground.

 

Testing the Food-Recipe API in the GraphQL Playground

In this section, you are going to test the Food-Recipe API in the GraphQL Playground, which is a graphical interface for testing and interacting with GraphQL APIs.

  • In your terminal, run the command npm start . This command starts a GraphQL playground on localhost:5000 as specified in the index.js file. The playground lets you create, read, update, and delete a food recipe directly from the playground- all in one call.

     

  • The "Docs" tab is located at the left-hand pane. Click on it to view the documentation for the Food-Recipe API. This docs tab gives an overview of the queries, mutations, and types that are available in the API.

     

  • To test a query, enter the following code in the right-hand pane:



Query Recipe ($id: ID!)  {
  recipes(ID: $id) {
    name
    description
    dateCreated
  }
}


Enter fullscreen mode Exit fullscreen mode

This query retrieves all recipes from the database and return their names, descriptions, and dateCreated.

  • Click the "Run" button at the top of the right-hand pane to execute the query. The results is displayed in the bottom pane, and you would see a list of recipes along with their names, descriptions, and dateCreated.

     

  • To test a mutation, enter the following code in the right-hand pane:



mutation CreateRecipe ($recipeInput: RecipeInput) {
  createRecipe (recipeInput: $recipeInput) {
    name
    description
    createdAt
  }
}


Enter fullscreen mode Exit fullscreen mode

This mutation adds a new recipe to the database with the specified name, description, and createdAt.

  • Click the "Run" button to execute the mutation. The results is displayed in the bottom pane, and you would see the details of the newly added recipe.

     

  • To test a query with parameters, enter the following code in the right-hand pane:



Query GetRecipes ($amount: Int!) {
  getRecipeByNumber(amount: $amount) {
    name
    description
  }
}


Enter fullscreen mode Exit fullscreen mode

This query retrieves a recipe from the database with the specified amount and return its name, and description. Note that you have defined a variable called $amount in the query that you need to pass in the value for the amount parameter.

In the bottom pane, click on the "Query Variables" tab and enter the following JSON object:



{
  "amount": 5
}


Enter fullscreen mode Exit fullscreen mode

This object defines the value for the $amount variable that you defined in the query.

  • Click the "Run" button to execute the query. The results is displayed in the bottom pane, and you would see the details of the first five recipes in your database.

That's it! You have successfully tested the Food-Recipe API in the GraphQL Playground. You can use this tool to further explore the API and test different queries and mutations.

 

Conclusion

In conclusion, this tutorial has provided a comprehensive guide on building a GraphQL API with Node.js, Apollo-Server, and MongoDB Atlas. By using these technologies, you can create APIs that are flexible, efficient, and easy to maintain.

One of the benefits of GraphQL is that it allows clients to request only the data they need, which can lead to faster and more efficient data fetching. And by leveraging the features of Apollo-Server, you can build a robust, secure, and scalable API with various integrations and features.

With the knowledge gained from this tutorial, you can begin exploring the possibilities of GraphQL and API development and use these technologies to create innovative and exciting projects. With the right approach, you can create powerful and efficient APIs that meet the needs of modern applications and users.

If you liked my article and found it useful, please leave a tip. Your contribution would enable me to continue producing high-quality articles for you.

Image description

Thank you for your continued support, and I look forward to providing you with more useful content in the future.

Top comments (2)

Collapse
 
whatsavadim profile image
WhatsAVadim

hey, really good writeup!
Two points:

  1. I would definitely mention the ability to copy paste the Apollo docs syntax from a document directly into your mutations/queries file after pressing run. Everyone is probably wondering where that code came from. This is also the power of graphQL and why its so popular.
  2. With this tutorial you are just a step away from a MERN stack. Why not finish that up?! You are just missing the React portion :) -react router/useState deps -a couple of package.json files -concurrently to tun server client simultaneously -proxy inside the server package.json to run front end and back end at the same time for flavor

here is a good example of that:
github.com/Git-Vdim-Hub/programmin...

Overall really good! I learned a few things from here as well!

Collapse
 
onlyoneerin profile image
The ERIN

Thank you for your nice comments @whatsavadim

I completely agree that the ability to copy and paste the Apollo docs syntax is a great feature that makes it easy for developers to implement mutations and queries in their code. I will definitely keep that in mind and consider including it in future tutorials.

Regarding the MERN stack, you make an excellent point! Including the React portion would make this tutorial more comprehensive and useful for developers looking to build a full-stack application. I appreciate your suggestions for the additional dependencies and configuration needed to complete the MERN stack. I will take that into account for future articles.