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

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

·

16 min read

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

If you're new to GraphQL, don't worry! You can check out a 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 must 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 will walk you through the steps.

MongoDB Atlas is a cloud-based database service that provides a fully managed and scalable environment for your 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 will 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 build a food recipe API.

  • Once configuring 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 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 must 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 set up the “Food-Recipe” cluster fully. 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, which 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, which 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 will set up a development environment by creating a project, installing necessary dependencies, and connecting your MongoDB Atlas cluster to your application.

  • Open a new terminal in your preferred code editor and run the following command.
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.
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, offers a simple way to interact with MongoDB databases, and 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"
  },
  • In your project folder, create a 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}`);
  });

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

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

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");

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";

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");

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,
});

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

Finally, you connect 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 localhost:5000 on your browser to confirm if your server is running without errors.

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

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

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

First, you imported the model Schema objects from the mongoose package. model It is a function that lets you define a new data model. At the same time, Schema it 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,
});

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);

Finally, you export the modelfunction from this file, creating a data model. You're passing two arguments to the model function: "Recipe" and recipeSchema. "Recipe" This is the name of your data model, which is used to identify and query this data model later. recipeSchema It is the data model structure 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 behavior 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 must 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
  }
`;

This code defines the schema for your API using the apollo-server library. The gql function determines 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 an recipe object in our API. RecipeInput is an input type 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 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
    },
  },
};

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

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.

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.

  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, find 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 were 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 will test the Food-Recipe API in GraphQL Playground, a graphical interface for testing and interacting with GraphQL APIs.

  • In your terminal, run the command npm start . This command starts a GraphQL playground 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 on the left-hand pane. Click on it to view the documentation for the Food-Recipe API. This docs tab overviews the queries, mutations, and types 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
  }
}

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

  • To execute the query, Click the "Run" button at the top of the right-hand pane. The results are displayed in the bottom pane, and you will 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
  }
}

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 will 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
  }
}

This query retrieves a recipe from the database with the specified amount and returns its name, and description. Note that you have defined a variable called $amount in the query you must pass in the amount parameter's value.

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

{
  "amount": 5
}

This object defines the value of the $amount variable you specified in the query.

  • Click the "Run" button to execute the query. The results are displayed in the bottom pane; you will 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. This tool can explore the API further 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. 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.