How to Build A GraphQL API with Node.js, Apollo-Server, and MongoDB Atlas
Tags: beginners, node, api, graphql
Published: 2023-04-26 09:17:12



## 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](https://www.redhat.com/en/topics/api/what-is-a-rest-api).

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 ](https://dev.to/onlyoneerin/a-comprehensive-guide-to-writing-your-first-graphql-query-21f4). It's an excellent introduction to the topic and a solid foundation for building your GraphQL API.

<p>&nbsp;</p>

## 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](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm/) if you don’t have `npm` installed on your computer.

<p>&nbsp;</p>

## 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](https://www.mongodb.com/atlas/database). If you have an account, you can log in or create one if you are not an existing user. It is free.
 
<p>&nbsp;</p>

- 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. 

<p>&nbsp;</p>

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3lhd2w5xosrk85mbl4dy.png)

<p>&nbsp;</p>

- You need to set up basic configurations for your database. Select the M0 Free tier, as it suits your needs. 
<p>&nbsp;</p>

- Choose any cloud service provider options in the “Provider” section. For this article, I recommend you select the AWS option. 
<p>&nbsp;</p>

- For the region field, select the region closest to where you are. 
<p>&nbsp;</p>

- 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.
<p>&nbsp;</p>

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

<p>&nbsp;</p>

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/064xaz6sz2ekrhkd9fsg.png)
<p>&nbsp;</p>

- 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. 

<p>&nbsp;</p>

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oqosm84g1k6g6xb78z3k.png)
<p>&nbsp;</p>

- 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.

<p>&nbsp;</p>

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0taiv5aam83fhfwaigri.png)
<p>&nbsp;</p>

- 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.

<p>&nbsp;</p>

## 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, 
<p>&nbsp;</p>

- Login to your dashboard, click the “Connect” button for your cluster
<p>&nbsp;</p>

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b3ls0kq62g4of6hmiw7a.png)
<p>&nbsp;</p>

- 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. 

<p>&nbsp;</p>

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l6v0ftrl23pibu45ez95.png)

<p>&nbsp;</p>

- 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.


<p>&nbsp;</p>

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qeoasqmzqa3amvdknp0g.png)

<p>&nbsp;</p>

## 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.
<p>&nbsp;</p>

```node
npm init –yes
```

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. 

```node
npm install apollo-server graphql mongoose nodemon
```

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. 


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


- 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.
<p>&nbsp;</p>

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


```javascript
// 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}`);
  });
```


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

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

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.

```javascript
const mongoose = require("mongoose");
```

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


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

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.

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

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.

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

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.


```javascript
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}`);
  });
```

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.


- Go to http://localhost:5000 on your browser to confirm if your server is running without errors. 

<p>&nbsp;</p>


## 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.


```javascript
​​// 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>

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

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

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.

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

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.

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

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. 

<p>&nbsp;</p>

## 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. 
<p>&nbsp;</p>

- Create two files in the `graphql` folder and name them `typeDefs.js` and `resolvers.js`
<p>&nbsp;</p>

- In the `typeDefs.js` file, copy the following code,

```javascript
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
  }
`;
```

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

```javascript
// 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
    },
  },
};
```
Let’s go over the code, line by line:

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

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.

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

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.

```javascript
  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;
    },
  },
```

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.


<p>&nbsp;</p>

## 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.
<p>&nbsp;</p>

- 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.
<p>&nbsp;</p>

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

```javascript
Query Recipe ($id: ID!)  {
  recipes(ID: $id) {
    name
    description
    dateCreated
  }
}
```
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`.
<p>&nbsp;</p>

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

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

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.
<p>&nbsp;</p>

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

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

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:

```json
{
  "amount": 5
}
```

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.
<p>&nbsp;</p>

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aowjcw1kjb3rtcxcyuth.png)](https://www.buymeacoffee.com/imagineerin)

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