Mastering Secure APIs: A Guide to GraphQL and MongoDB Integration

This blog is for you if you want to learn how to use GraphQL and MongoDB to construct secure APIs. This blog post will explain how to establish a schema, query, and resolvers, and use JWT tokens to authenticate requests inside of a GraphQL context.

What is GraphQL?

GraphQL is an open-source query language that facilitates data retrieval from databases. It is not an ORM; rather, it is used in conjunction with ORMs to provide precise responses to client requests.

There is just one endpoint for GraphQL, which is /GraphQL, and to obtain the needed data, you must pass a query.

GRAPHQL-Database

Instead of interacting with the database, GraphQL queries the information that the ORM has returned.

GraphQL Example

To understand how to develop secure APIs using GraphQL, let’s look at an example where a user may add, edit, and remove products from a database—but only if he is authorized.

Related read: What is GraphQL? Learn How GraphQL Is Revolutionizing API Development

Install the Following Packages to Follow Along

npm i @apollo/server bcrypt graphql jsonwebtoken mongoose

Short Description for Packages Installed

  1. @apollo/server – Creates an express server.
  2. Bcrypt – For hashing the user’s passwords.
  3. GraphQL – For writing schemas.
  4. Jsonwebtoken – For generating JWT tokens after creating a user.
  5. Mongoose – For interacting with MongoDB.

Let’s Create a GraphQL Schema for the User

export const typeDefs = `#graphql
interface CreateUser{
email:String,
password:String,
fullName:String
}
interface User{
email:String,
fullName:String,
id:String
}
type CreateUserMutationResponse{
id:String
password:String,
accessToken:String,
refreshToken:String,
message:String,
}

type Mutation{
createUser(email:String,password:String,fullName:String):CreateUserMutationResponse
}
`;

In our user schema, we have a createUser mutation function that creates a user, saves them in the database, and gives us the access token we need to subsequently validate the user.

Let’s Create Resolvers for the User

import { User } from "../../models/userModel.js";
import { accessToken, refreshToken } from "../../utils/jwtAuthentication.js";
import bcrypt from "bcrypt";

export const resolvers = {
Mutation: {
createUser: async (_, { email, password, fullName }, {}) => {
try {
const hashPassword = await bcrypt.hash(password, 10);
const user = await User.create({
email,
password: hashPassword,
fullName,
});
await user.save();
const userAccessToken = accessToken<any>({
id: user._id.toHexString(),
email,
time: new Date(),
});
const userRefreshToken = refreshToken<any>({
email,
time: new Date(),
});
return {
id: user._id.toHexString(),
password: hashPassword,
accessToken: userAccessToken,
refreshToken: userRefreshToken,
message: "User created..!",
};
} catch (error) {}
},
},
};

Let’s Create a Helper Function for Generating Access and Refresh the Token

import dotenv from "dotenv";
import jwt from "jsonwebtoken";
dotenv.config();

export const accessToken = function <T>(data: T) {
const jwtSecretKey = process.env.JWT_SECRET_KEY;
return jwt.sign(data, jwtSecretKey, { expiresIn: "30d" });
};

export const refreshToken = function <T>(data: T) {
const jwtSecretKey = process.env.JWT_SECRET_KEY;
return jwt.sign(data, jwtSecretKey, { expiresIn: "30d" });
};

Let’s Create a User Model

import { Schema, model } from "mongoose";

const userSchema = new Schema(
{
email: { type: String, required: true },
password: { type: String, required: true },
fullName: { type: String, required: true },
},
{ timestamps: true }
);

export const User = model("User", userSchema);

We will use this user model to save the user’s data in our MongoDB database.

Let’s Create a Product GraphQL Schema

export const typeDefs = `#graphql
interface Product{
name:String,
company:String,
price:Int
}
input ProductInput{
name:String,
company:String,
price:Int
}
type AddProductMutationResponse{
id:String
message:String,
}
type Mutation{
addProduct(input:ProductInput):AddProductMutationResponse
}
`;

Let’s Create a Product Model

import { Schema, model } from "mongoose";

const productSchema = new Schema(
{
name: { type: String, required: true },
company: { type: String, required: true },
price: { type: String, required: true },
},
{ timestamps: true }
);

export const Product = model("Product", productSchema);

Let’s Create Product Resolvers

import { Product } from "../../models/productModel.js";

export const resolvers = {
Mutation: {
addProduct: async (_, { input }, { decodedToken }) => {
try {
if (!decodedToken.id) {
throw new Error("You are not authorized to perform this action.");
}
const product = await Product.create({
name: input.name,
company: input.company,
price: input.price,
});
await product.save();
return {
id: product._id.toHexString(),
message: "Product added...",
};
} catch (error) {
return error;
}
},
},
};

A user may only add a product to the database if he is authorized. We can determine if the user is authorized by accessing the token in the fourth parameter of our resolver function. If the token lacks the ID, we will throw an error.

Elevate Your API Security Today! Explore the Integration of GraphQL and MongoDB for Creating Secure APIs. Dive in Now!

Now Let’s Combine the Schema and Resolvers

import { makeExecutableSchema } from "@graphql-tools/schema";
import { typeDefs as bookTypeDefs } from "./book/schema.js";
import { typeDefs as userTypeDefs } from "./user/schema.js";
import { resolvers as bookResolvers } from "./book/bookResolvers.js";
import { resolvers as userResolvers } from "./user/userResolvers.js";
import { typeDefs as productTypeDefs } from "./product/schema.js";
import { resolvers as productResolvers } from "./product/productResolver.js";

export const graphqlSchema = makeExecutableSchema({
typeDefs: [bookTypeDefs, userTypeDefs, productTypeDefs],
resolvers: [bookResolvers, userResolvers, productResolvers],
});

Now Let’s Create Our GraphQL Server and MongoDB Connection

DB Connection:

import mongoose from "mongoose";
import dotenv from "dotenv";
dotenv.config();

//DB uri
var uri = `${process.env.DB_URI}${process.env.DB}`;

mongoose.connect(uri);

export const connection = mongoose.connection;

Server:

import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";
import { graphqlSchema as schema } from "./modules/index.js";
import { connection } from "./utils/dbConnection.js";
import { getUserFromToken } from "./utils/getUserFromToken.js";
import { GraphQLError } from "graphql";

//Checking DB connection here
connection.once("open", function () {
console.log("MongoDB database connection established successfully");
startServer();
});

async function startServer() {
const server = new ApolloServer({
schema,
});

const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
context: async ({ req, res }: any) => {
const token = req.headers.authorization;
if (token) {
var decodedToken = await getUserFromToken(token);
if (!decodedToken) {
throw new GraphQLError(
"You are not authorized to perform this action.",
{
extensions: {
code: "FORBIDDEN",
},
}
);
}
return { decodedToken };
}
return {};
},
});

console.log(`🚀 Server ready at: ${url}`);
}

Take note that we are utilizing the context function to get the user ID from the token.

Now Let’s Create a User from the Sandbox

Create-User
Fig: Create User Tokens

Add Access Token in Headers and Fire addProduct Query

Access-Query
Fig: Access Query

If We Send an Invalid Token GraphQL will Throw an Error

Invalid-Token-Error
Fig: Invalid Token Error
coma

Conclusion

To work with huge queries and query just data that is truly needed, GraphQL is, in my view, the finest tool. Combining it with JWT tokens for secure endpoints is the icing on the cake. In this blog, we showed how to utilize GraphQL context to authenticate a user before making any requests.

Keep Reading

Keep Reading

Launch Faster with Low Cost: Master GTM with Pre-built Solutions in Our Webinar!

Register Today!
  • Service
  • Career
  • Let's create something together!

  • We’re looking for the best. Are you in?