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 havenpm
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.
-
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.
- 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.
- 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.
- 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
- 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.
- 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.
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
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, 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 astart
command. Whenever you runnpm start
in the terminal, the start command runs theindex.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 an
index.js
file. The code in theindex.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");
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");
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'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.
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 yourrecipe.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> </p>
Let’s take a closer look at the code, line by line:
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.
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 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 thetypeDefs
and theresolver
files. -
Create two files in the
graphql
folder and name themtypeDefs.js
andresolvers.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 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
},
},
};
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, 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 onlocalhost:5000
as specified in theindex.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
}
}
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
, anddateCreated
. 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 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
}
}
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
}
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.
Thank you for your continued support, and I look forward to providing you with more useful content in the future.
Top comments (2)
hey, really good writeup!
Two points:
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!
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.