Follow

Follow
Create an API for real-time chatting app with GraphQL (4 Steps)

Create an API for real-time chatting app with GraphQL (4 Steps)

Nishchit's photo
Nishchit
·Jun 15, 2021·

12 min read

Table of contents

MongoDB is an open-source database that uses document-oriented data models. It is the most powerful no SQL system. It’s a no SQL database. It’s simply a database with its pros and cons like any other database. MongoDB stores data in flexible JSON-like documents. Point to attention here, I am saying JSON is not exactly JSON. The thing why JSON like? It’s because it makes it very easy to map the objects. When you store anything in your tables, rows, and columns, accessing the database can become a little bit challenging and can be a costly process for your processor or database server. But in a NoSQL database where everything is stored in a key-value pair, it becomes very easy to access the data and the accessing becomes much faster.

A lot of people believe that simplicity can introduce a lot of problems. That is also correct since there is so much flexibility given to you, if your test sets are not written properly, you can insert datasets into a database that you do not want to. It’s not a con of MongoDB but it’s more related to how you structure your database and how restrictive you’re in your test sets.

It is a distributive database at its core, so high availability, horizontal scaling, and geographic distribution are built-in and easy to use. It simply means scalability is a known issue in SQL databases. SQL databases are easy to use and we use them a lot but when it comes to scalability, it becomes challenging. At the same time, this kind of scaling is super easy in MongoDB. For example, you’re using geographic data of the users like Tinder where you'll find other people in your geographic location. Doing this thing in SQL database is definitely one of the toughest tasks. Definitely, you can achieve it but it is one of the most challenging parts. Achieving the same functionality in MongoDB is a lot easier.

Basic Terms in MongoDB:

Database

They are the containers that hold the sets of collections. It may contain one or more collections.

Collection

The collections are similar to "tables" in Relational Database but it doesn't have any schema. This means data in the same collections can have different fields.

Document

They're the basic unit of storing data in MongoDB. You can say that they're the key-value pair in any collection.

Field

They're like the columns in a table in the Relational Database. Let us take an example:

{
     id : 1
     name : "Elon"
}

In the above example, id and name are the fields.

MongoDB vs MySQL

Let’s start from the basic ones, MySQL stores the data in tables while MongoDB stores the data in JSON-like documents that can have different types of structure and can also store related pieces of data together. This allows MongoDB to work much faster than MySQL.

We don't have to define the schema in MongoDB, so updating or adding any additional field isn't a big issue. While the same is very difficult to achieve in MySQL, we are not saying it’s impossible but it becomes a big task as you have to revamp the complete schema.

MongoDB is very helpful in real-time data analytics applications which work on real-time data is a big task for MySQL as it needs to run separate queries for each data transfer.

Scalability is another feature of MongoDB as it comes with replication, sharding, and auto elections out of the box. Sharding allows you to horizontally scale the database which is quite difficult to achieve in MySQL databases as they offer master-slave replications.

Deadlocks are a common issue in MySQL databases when you’re working with High Volume - High-Speed data while deadlocks also happen in MongoDB but are not as frequent as they happen in MySQL.

MongoDB doesn’t support JOIN like MySQL. But it supports data types like arrays and documents. It also allows you to put one document into another document. This is known as embedding.

Basic Operations in MongoDB

Fetching data

db.collection_name.find()

In MongoDB find() helper function returns all the values in a collection.

Insertion of data

db.collection_name.insert(
  {
    field_1 : “a”,
    Field_2 : 121
  }
)

In MongoDB insert() is a helper function that is used to store data. You have to pass the field name and value in the function.

Updating a document

db.collection_name.update(
  { custage: { $gt: 2 } },
  { $set: { fieldname: "main" } },
  { multi: true }
)

In MongoDB, update() helper is used to update a single document. You have to pass the object ID and new values in it. There's one more argument that is multi, if it is set to true it will update multiple documents else it'll update only one document.

Deleting a document

db.collection_name.deleteMany({ field_name: "any condition here" })

The deleteMany() deletes one or more documents in a collection that matched the condition.

Now since we have already understood the basics and working of the MongoDB server. So let’s try to create the backend and API of a chatting application.

Chat Application API using MongoDB and GraphQL

So we’re going to work on a real-time small chat application backend and API. We’ll be using GraphQL for the API, MongoDB to store all the data, and Node.JS for all the backend coding. To reduce the complexity of MongoDB, we’ll be using Mongoose here. Mongoose is an ODM or you can say Object Document Mapper which provides an abstraction layer on the top of the MongoDB which makes the use of MongoDB much easier.

The API will include the creation of users, deletion of a user, sending messages, listing all the users, and updating user details.

Prerequisites:

  1. Node.JS
  2. GraphQL (If you’re not aware of GraphQL and how does it work, you can check out our previous post here)
  3. MongoDB
  4. Mongoose

step 1: Server setup

We are going to use graphql-yoga as the server. It makes it very easy to use the GraphQL server with minimal effort. The library is one of the most popular libraries for GraphQL and is based on express/apollo server, graphql-subscriptions, and graphql-playground.

Now we have to create a new project directory, so you can just open the terminal and type:

mkdir graphql-mongodb

cd graphql-mongodb

Once you’re in the directory, use the below command to initialize a node project:

npm init -y

Now, it’s time to install the dependencies:

npm i graphql-yoga mongoose

Now create an index.js and open it on your favourite code editor. I am using the Visual Code editor here or if you want you can pick any one from here.

Once you’re in, paste the below code:

const { PubSub, withFilter, GraphQLServer } = require("graphql-yoga")
const mongoose = require("mongoose")

Here we’re just importing the dependencies for our project that is graphql-yoga and mongoose

Now we have to connect index.js to MongoDB server. So just put the below code in your index.js file:

const { PubSub, GraphQLServer } = require("graphql-yoga")
const mongoose = require("mongoose")
const typeDefs = require("./typeDefs")
const resolvers = require("./resolvers")

mongoose.connect("mongodb://localhost/miniChat", {
  useNewUrlParser: true,
  useFindAndModify: false,
  useCreateIndex: true,
})

const pubsub = new PubSub()
const server = new GraphQLServer({
  typeDefs,
  resolvers,
  context: { pubsub },
})
mongoose.connection.once("open", () =>
  server.start(() => console.log("Check URL localhost:4000"))
)

step 2: Creating Models:

We now have to create models for our application. So create a new file models.js and paste the below code:

const User = mongoose.model("User", {
  name: {
    type: String,
    required: true,
  },
  username: {
    type: String,
    required: true,
    index: { unique: true },
  },
})

const Message = mongoose.model("Message", {
  message: String,
  senderUsername: String,
  receiverUsername: String,
  timestamp: Number,
})

Here we have got two models that are message and user. The user model has two fields that are name and username, both of them are string types and required as well. While the message model has 4 fields: message, senderUsername, receiverUsername and timestamp.

Step 3: Creating TypeDef and Resolvers:

Now we have to define our resolvers and typedef. If you don’t know what they’re, you can check out our previous article on Getting Started with GraphQL.

Let’s start with TypeDef first, so create a file name typeDefs.js and paste the below code. Don’t worry we’ll explain everything about the code.

const typeDefs = `
  type Query {
    users: [User]
    messages: [Message]
  }
  type User {
    id: ID!
    name: String!
    username: String!
    messages: [Message]
  }
  type Message {
    id: ID!
    message: String!
    senderUsername: String!
    receiverUsername: String!
    timestamp: Float!
    users: [User]
  }
  type Mutation {
    addNewUser(name: String! username: String!): User!
    updateUserDetails(id: ID! name: String!): User!
    deleteUser(username: String!): Boolean!
    userTyping(username: String! receiverUsername: String!): Boolean!
    sendMessage(senderUsername: String! receiverUsername: String! message: String! timestamp: Float!): Message!
    updateMessage(id: ID! message: String!): Message!
    deleteMessage(id: String!): Boolean!
  }
  type Subscription {
    newMessage(receiverUsername: String!): Message
    newUser: User
    oldUser: String
    userTyping (receiverUsername: String!): String
  }
`

module.exports = typeDefs

Code Explanation:

Here we’re using the type system to define the mutations and subscriptions.

  • Type user defines the data type for user where it can take input ID, name, messages, and username as input.

  • Type message defines the data type for messages that users will send. It can take inputs ID, message, sender username, receiver username, and timestamp.

  • Mutations: addNewUser, deleteUser, userTyping, updateUserDetails, userTyping, sendMessage, updateMessage, deleteMessage

Now we will define the resolvers:

const { PubSub, withFilter } = require("graphql-yoga")
const { User, Message } = require("./models")

const resolvers = {
  Query: {
    users: () => User.find(),
    messages: () => Message.find(),
  },

  User: {
    messages: async ({ username }) => {
      return Message.find({ senderUsername: username })
    },
  },

  Message: {
    users: async ({ senderUsername }) => {
      return User.find({ username: senderUsername })
    },
  },

  Mutation: {
    addNewUser: async (_, { name, username }) => {
      const user = new User({ name, username })
      await user.save()
      pubsub.publish("newUser", { newUser: user })
      return user
    },

    updateUserDetails: async (_, { id, name }) => {
      const user = await User.findOneAndUpdate(
        { _id: id },
        { name },
        { new: true }
      )
      return user
    },

    deleteUser: async (_, { username }) => {
      await Promise.all([
        User.findOneAndDelete({ username: username }),
        Message.deleteMany({ senderUsername: username }),
      ])
      pubsub.publish("oldUser", { oldUser: username })
      return true
    },

    userTyping: (_, { username, receiverUsername }) => {
      pubsub.publish("userTyping", {
        userTyping: username,
        receiverUsername,
      })
      return true
    },

    sendMessage: async (
      _,
      { senderUsername, receiverUsername, message, timestamp }
    ) => {
      const userText = new Message({
        senderUsername,
        receiverUsername,
        message,
        timestamp,
      })
      await userText.save()
      pubsub.publish("newMessage", {
        newMessage: userText,
        receiverUsername,
      })
      return userText
    },

    updateMessage: async (_, { id, message }) => {
      const userText = await Message.findOneAndUpdate(
        { _id: id },
        { message },
        { new: true }
      )
      return userText
    },

    deleteMessage: async (_, { id }) => {
      await Message.findOneAndDelete({ _id: id })
      return true
    },
  },

  Subscription: {
    newMessage: {
      subscribe: withFilter(
        () => pubsub.asyncIterator("newMessage"),
        (payload, variables) => {
          return (
            payload.receiverUsername === variables.receiverUsername
          )
        }
      ),
    },

    newUser: {
      subscribe: (_, {}, { pubsub }) => {
        return pubsub.asyncIterator("newUser")
      },
    },

    oldUser: {
      subscribe: (_, {}, { pubsub }) => {
        return pubsub.asyncIterator("oldUser")
      },
    },

    userTyping: {
      subscribe: withFilter(
        () => pubsub.asyncIterator("userTyping"),
        (payload, variables) => {
          return (
            payload.receiverUsername === variables.receiverUsername
          )
        }
      ),
    },
  },
}

const pubsub = new PubSub()

module.exports = resolvers

Code Explanation:

So we have got different resolvers for different tasks. For example, to create a new user in the database we have addNewUser, to update a user we have got updateUserDetails, and so on. You can notice that we’re using pubsub.publish() in most of the resolvers, it is because this is used for notifying the server that a change has been made which exposes a subscription API automatically to reflect the changes on the user side.

pubsub.asyncIterator() function is used for mapping the event by passing the subscription name. As you can see we are passing userTyping and others also here, which means that when a user is typing the message or some change happens, it’ll inform the server.

Step 4: Testing the API

Now on your terminal execute:

node index.js

Open your browser and go to the URL: http://localhost:4000/. You should see something like this:

[image]

Now, let’s try to insert user details. Copy the below code and paste it on input pane of the GraphQL playground:

mutation addNewUser($name: String!, $username: String!) {
  addNewUser(name: $name, username: $username) {
    ...userFields
  }
}

fragment userFields on User {
  name
  username
}

In the Query Variables section put:

{
  "name": "Bob",
  "username": "bob@graphql.com"
}

It should return the below output:

{
  "data": {
    "addNewUser": {
      "name": "Bob",
      "username": "bobber"
    }
  }
}

To list all the users use the below query:

query {
  users {
    id
    name
  }
}

To send a message use the below code in input pane:

mutation sendMessage(
  $senderUsername: String!
  $receiverUsername: String!
  $message: String!
  $timestamp: Float!
) {
  sendMessage(
    senderUsername: $senderUsername
    receiverUsername: $receiverUsername
    message: $message
    timestamp: $timestamp
  ) {
    ...messageFields
  }
}

fragment messageFields on Message {
  senderUsername
  receiverUsername
  message
  timestamp
}

And paste the below code in the variable pane:

{
  "senderUsername": "elon",
  "receiverUsername": "jeff",
  "message": "Hello this is test message",
  "timestamp": 1.22
}

It should return the below output:

{
  "data": {
    "sendMessage": {
      "senderUsername": "elon",
      "receiverUsername": "jeff",
      "message": "Hello this is test message",
      "timestamp": 1.22
    }
  }
}

To read messages of a user in Real Time:

In the left pane, add the below subscription code:

subscription($receiverUsername: String!) {
  newMessage(receiverUsername: $receiverUsername) {
    message
    senderMail
    receiverUsername
    id
    timestamp
    users {
      name
      username
    }
  }
}

Now in the Query pane use:

{
  "receiverUsername": "Jeff"
}

Explanation

We have already created a subscription here to receive messages in real-time. Now to implement that functionality we can use the above code. You have to pass the receiver username in order to get messages. If you test it on any tool like GraphiQL, it'll return Listening … which means it's working fine and listening to the endpoint.

To read messages of all users use:

query {
  users {
    id
    name
    username
    messages {
      message
    }
  }
}

It should return the messages of all the users, I have already inserted a few users so the output was this:

{
  "data": {
    "users": [
      {
        "id": "5f948746339c26933f89048e",
        "name": "Elon",
        "username": "elon",
        "messages": [
          {
            "message": "Hello this is a test message"
          }
        ]
      },
      {
        "id": "5f9498b86e9fa6972f2933df",
        "name": "Jeff",
        "username": "jeff",
        "messages": []
      }
    ]
  }
}

So this is it, you have completed the basics of GraphQL, and MongoDB and also created a small messaging application, where you can send messages, create users, update and delete users. You can easily add a UI for this and make it more beautiful. I hope you understood all the parts, comment down below if you’re stuck.

 
Share this