Build CRUD application using GraphQL, NodeJs, Typescript, and MongoDB



In this tutorial, we will explain in detail the designing and developing the backend solution for CRUD operations using GraphQL, Mongo database, Apollo, Express, Typescript, Nodejs, to make it very understandable to all the readers we took the example of Blog systems. We have also provided the repo and demo for the users at the end of the tutorial section.

Blog Model



How to setup

  1. Clone the repository from
  2. Install the dependencies by executing the command npm install
  3. Make sure mongo database configuration details are updated in the config.ts
  4. Then in the local you can run with command npm run start:dev but if you are running the production you can directly use npm start so this command first creates the dist files and starts the server from dist/server.js

Project folder



Node.js setup with typescript and GraphQL Apollo Server

Let's begin with setting up the Node.js with GraphQL Apollo Server, If you observe the package.json, we are using the tsc the command to convert typescript to javascript and we have used the ncp utility to copy the schemas and store it in the dist folder and for starting the node with Apollo Server as middleware, we have used the below setup and used some utilities that will ease the GraphQL schema management and all the environment variables are stored in the .env file

const dotenv = require('dotenv');
dotenv.config();

import express from 'express';
import { ApolloServer } from 'apollo-server-express';
import depthLimit from 'graphql-depth-limit';
import { createServer } from 'http';
import compression from 'compression';
import cors from 'cors';
import schema from './graphql/schema';
import { MongoHelper } from './helpers/mongoHelpers';

const app = express();
const mHelper = new MongoHelper();
mHelper.initiateMongoConnection();

const server = new ApolloServer({
  schema,
  validationRules: [depthLimit(7)],
  introspection: true,
  playground: true,
  context: async ({ req }) => {
    return await mHelper.validateUser(req);
  },
});

app.use('*', cors());
app.use(compression());
server.applyMiddleware({ app, path: '/graphql' });

const httpServer = createServer(app);

httpServer.listen({ port: process.env.PORT }, (): void =>
  console.log('\nšŸš€ GraphQL is now running on http://localhost:${process.env.PORT}/graphql')
);

Mongo Database Model

Blog, comments, and users who posted comments and all these models can be found inside src/models. For example, the blog schema looks as below and similarly, the schemas are defined for comments and users. This will ease developers to mention any unique constraint or any required fields before inserting documents into the database.

How to reference object from one collection to another collection?

Here you can see that how we are referencing the comments object inside the blog schema and also it defined as an array as there may be many comments to any blog page and similarly each comment is added by a particular user hence, we added just user object in the model

comments: [
  {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Comment',
  },
]

How to add unique constraint in Mongoose model

In our case we wanted to keep the blog URL as unique entry in the blogs collection, hence we added

url: {
  type: String,
  required: true,
  index: { unique: true },
}

Typescript decorators and how and when to use them?

Decorators in simple terms are a special type of declarations and these can be used at class or method level and you can use them when you need to reuse the method and extend the behavior of some other functions or classes. You can majorly use them while analytical, logging, and instrumentation and during validation and run-time checks scenarios, but there are many advanced scenarios where you can take advantage of decorators especially in Python. In our case every time user tries to access the data, we are using decorator to validate whether the user is authorized to access the information or not.

export function VerifyAuthorization(
  _target: any,
  _propertyKey: string,
  descriptor: TypedPropertyDescriptor<(...args: any[]) => Promise<void>>
) {
  const fn = descriptor.value!;
  descriptor.value = async function DescriptorValue(...args: any[]) {
    try {
      if (!args[1][AppConstants.IS_USER_LOGGED]) {
        throw new GraphQLError(ErrorConstants.USER_NOT_AUTHORIZED);
      }
      return await fn.apply(this, args);
    } catch (error) {
      throw new GraphQLError(error);
    }
  };
  return descriptor;
}

GraphQL

As you already know that GraphQL is the best query language for APIs and helps to provide the information that we request instead of getting all the details, so this will only transfer the data to client what is requested.

Why Apollo GraphQL Server?

There are many ways to set GraphQL server and we have picked Apollo GraphQL server considering below points

  • Lot of features with great support for modern architecture
  • Good error handling
  • Better metrics and logging
  • As the application grows, one can easily integrate with Apollo studio for performance measure etc.
  • Easy integration with Apollo client
  • Good documentations
  • Good GraphiQL IDE for development
  • Well support for different kind of web application server such as Express, Hapi, Koa, Lamba, Micro, Restify, Azure functions and Adonis

GraphQL schemas and resolvers

We have placed all the GraphQL related files in the src/graphql and it contains GraphQL schemas and GraphQL resolvers. Schemas are written using GraphQL schema definition language and it represents all the data along with type that is retrieved from the API and there are three types of root operations you can perform from GraphQL and those are query, mutation, and subscription and currently, in the project, we have used query and mutation. You can find all the functions required for queries and mutations for reading and modifying the data in the database and these are present in src\graphql\resolvers\resolvers.ts

import { GraphQLResolveInfo } from 'graphql';
import { Context } from '../../models/context';
import { IResolvers } from 'graphql-tools';
import * as jwt from 'jsonwebtoken';
import { BlogsController } from '../../controllers/blogs.controller';
import { CommentsController } from '../../controllers/comments.controller';
import { AppConstants } from '../../constants/app.constants';
import { UsersController } from '../../controllers/users.controller';

const blogController = new BlogsController();
const commentsController = new CommentsController();
const usersController = new UsersController();

const resolvers: IResolvers = {
  Query: {
    blog: (_: void, args: any, ctx: Context, _info: GraphQLResolveInfo) => {
      return blogController.getBlog(args, ctx);
    },
    blogs: (_: void, args: any, ctx: Context, _info: GraphQLResolveInfo) => {
      return blogController.getBlogs(args, ctx);
    },
    token: (_, args: any) => {
      return jwt.sign({ data: args[AppConstants.EMAIL] }, <string>process.env.auth_encryption_salt);
    },
  },

  Mutation: {
    addBlog: (_, inputObject, ctx: Context) => {
      return blogController.addBlog(inputObject, ctx);
    },
    updateBlog: (_, inputObject, ctx: Context) => {
      return blogController.updateBlog(inputObject, ctx);
    },
    deleteBlog: (_, inputObject, ctx: Context) => {
      return blogController.deleteBlog(inputObject, ctx);
    },
    addComment: (_, inputObject, ctx: Context) => {
      return commentsController.addComment(inputObject, ctx);
    },
    updateComment: (_, inputObject, ctx: Context) => {
      return commentsController.updateComment(inputObject, ctx);
    },
    deleteComment: (_, inputObject, ctx: Context) => {
      return commentsController.deleteComment(inputObject, ctx);
    },
    addUser: (_, inputObject, ctx: Context) => {
      return usersController.addUser(inputObject, ctx);
    },
    updateUser: (_, inputObject, ctx: Context) => {
      return usersController.updateUser(inputObject, ctx);
    },
  },
};

export default resolvers;

Authorization in GraphQL

It is always best practice to consider security aspects while developing any applications and we have implemented basic authorization before accessing any APIs and here we have taken the help of GraphQL Context which are passed across all the API and if the JWT is valid then only the operation will be performed otherwise an error will be thrown if you see the code below you can get the best use of passing context information while setting the Apollo GraphQL server as below and here we are validating the weather token passed in the request invalid against information present in the database


const server = new ApolloServer({
  schema,
  validationRules: [depthLimit(7)],
  introspection: true,
  playground: true,
  context: async ({ req }) => {
    return await mHelper.validateUser(req);
  },
});

public async validateUser(req: any) {
  const token = req.headers.authorization || '';
  try {
    const payload = <{ data: string; iat: number }>(
      jwt.verify(token, <string>process.env.auth_encryption_salt)
    );
    const email = payload['data'];
    return await User.find({ email: email }).then((response: any) => {
      if (response.length > 0) {
        return { isUserLogged: true };
      }
      return { isUserLogged: false };
    });
  } catch (error) {
    return { isUserLogged: false };
  }
}

How to use GraphiQL Playground

You can use the playground easily by accessing the endpoint mentioned node server and we have highlighted all the important items in the playground.

How to perform CRUD operations

You can run these below queries and mutations in the GraphQL cli to test

Add the user
mutation {
  addUser(
    input: {
      email: "[email protected]"
      name: "Universal Tutorial"
      provider: "self"
    }
  ) {
    name
    email
  }
}
Get the access token
query {
  token(email: "[email protected]")
}
Add the Blog
mutation {
  addBlog(
    input: {
      title: "Entering my first blog"
      description: "Short description"
      body: "Universal Tutorial"
      url: "/blog/graphql-tutorial-demo"
      keywords: "graphql,node.js,typescript,apollo"
      category: "graphql"
    }
  ) {
    title
    url
  }
}
Add the Comment to Blog

mutation {
  addComment(
    blogUrl: "/blog/graphql-tutorial-demo"
    commentDescription: "adding the comment"
  ) {
    url
    comments {
      comment
      user {
        name
      }
    }
  }
}

Demo

Currently, all the code is available in Github and we have deployed the live application in Heroku as well, you can follow the links mentioned below

Github: https://github.com/allabouttech0803/graphql-mongodb-typescript-blog

Demo: https://ts-node-graphql-node-mongo.herokuapp.com/graphql

0
0