Slack Clone with React | Semantic UI | GraphQL | PostgresSQL (PART 5)

ajeasmith

AjeaS

Posted on September 24, 2020

Slack Clone with React | Semantic UI | GraphQL | PostgresSQL (PART 5)

Previously, we went over how GraphQL works. You can find that article here.

Today, we'll start to create our Graphql queries and mutations.

Open up the typesDefs.js file. The first thing we need to do is map out what our data is going to be. We know that we need to have a User object to represent a user in our project, so let's start there.

Types

Remove the previous code in our typeDefs.js file and replace it with this =>

const { gql } = require("apollo-server");
module.exports = gql`
  type User {
    username: String!
    email: String!
    password: String!
  }
`
Enter fullscreen mode Exit fullscreen mode

Next, we need to create a Team object for when a user needs to create a team

const { gql } = require("apollo-server");
module.exports = gql`
  type Team {
    owner: User!
    members: [User!]!
    channels: [Channel!]!
  }
`
Enter fullscreen mode Exit fullscreen mode

Were gonna need Channels to join =>

const { gql } = require("apollo-server");
module.exports = gql`
  type Channel {
    id: Int!
    name: String!
    public: Boolean!
    messages: [Message!]!
    users: [User!]!
  }
`
Enter fullscreen mode Exit fullscreen mode

Lastly, we need to be able to send and receive Messages =>

const { gql } = require("apollo-server");
module.exports = gql`
  type Message {
    id: Int!
    text: String!
    user: User!
    channel: Channel!
  }
`
Enter fullscreen mode Exit fullscreen mode

Let's look at these types closely.

  • A Team who created it? (owner prop), who's in the team? (members prop), and what channels are associated with this team? (channels prop).

  • A User we need to know, which user is creating the teams and channels (email, username, password props).

  • A Channel which channel it is (id prop), what's the name of the channel? (name prop), will it be public or private? (public prop), what are the messages (message prop), and list the users in this channel (users prop).

  • A Message which message it is? (id prop), what does the message say? (text prop), which user sent this message (user prop), lastly which channel does this message belong to (channel prop)

In the end, your typeDefs.js should look like this =>

Screen Shot 2020-09-16 at 2.12.35 AM.png

Now, let's define our queries (GET endpoints) and mutations (POST, PUT, DELETE endpoints)

Still, inside typeDefs.js file, let's add our queries

type Query {
    getUsers: [User!]!
    getMessages: [Message!]
    getUser(id: Int!): User!
  }
Enter fullscreen mode Exit fullscreen mode

These are what I have so far. Mutations look like =>

type Mutation {
    createUser(username: String!, email: String!, password: String!): User
    createMessage(channel_id: Int!, text: String!): Boolean
    createTeam(name: String!): Boolean
    createChannel(teamId: Int!, name: String!, public: Boolean = false): Boolean
  }
Enter fullscreen mode Exit fullscreen mode

FYI we only need to pass the params we need to use. As you can see all of our mutations involve creating something(POST) for now.

We're just defining it now, let's actually return some real data with our resolvers starting with creating a user using the createUser mutation.

Resolvers

Head over to the resolvers.js file and create a createUser mutation Remember, naming is important it needs to be the same name as the type query you defined

const bcrypt = require("bcrypt");
const { User } = require("../models");
module.exports = {
  Mutation: {
      createUser: async (_, args) => {
        let { username, email, password } = args;
        try {
        // 1. Check if user exist in DB
        const getUser = await User.findOne({ where: { email: email } });
        if (!getUser) {
          // 2. Hash user password
          password = await bcrypt.hash(password, 12);
          // 3. store user in DB
          const user = await User.create({
            username,
            email,
            password
          });
          return user;
        } else {
          throw Error("User already exist");
        }
      } catch (err) {
        return err;
      }
    }
};
Enter fullscreen mode Exit fullscreen mode

What we're doing is creating a user with the data passed to us through args (destructuring data). Were creating a user in the DB using the User model from Sequelize. I hope the rest of the comments help you get the gist of what's happening.

Let's test this endpoint in our playground. Have your server running and go to localhost:4000.

calling the createUser mutation should look like this =>

Screen Shot 2020-09-16 at 2.46.51 AM.png

You specify the type, either query or mutation. Then you pick the endpoint. It should return a user and create a user in the DB if successful =>

Screen Shot 2020-09-16 at 2.53.28 AM.png

Now check the DB.

Screen Shot 2020-09-16 at 2.58.19 AM.png
FYI ignore the first user, that was a test I did earlier. But as you can see JamesB was created with the password hashed, which is awesome.

let's create the rest of the mutations. Creating a Team, Channel, and Message.

const bcrypt = require("bcrypt");
const { Channel, Message, Team } = require("../models");
module.exports = {
  Mutation: {
    createChannel: async (_, args) => {
      try {
        await Channel.create(args);
        return true;
      } catch (err) {
        console.log(err);
        return false;
      }
    },
    createMessage: async (_, args) => {
      // const channel = Channel.findOne({where: {id: args.channel_ids}})
      try {
        await Message.create({
          ...args,
          userId: 1
        });
        return true;
      } catch (error) {
        console.log(error);
        return false;
      }
    },
    createTeam: async (_, args) => {
      try {
        await Team.create({
          ...args,
          owner: 1
        });
        return true;
      } catch (error) {
        console.log(error);
        return false;
      }
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

Now our queries =>

const bcrypt = require("bcrypt");
const { User } = require("../models");
module.exports = {
  Query: {
    getUsers: async () => {
      try {
        const users = await User.findAll();
        return users;
      } catch (err) {
        console.log(err);
      }
    },
    getUser: async (_, { id }) => {
      try {
        const user = await User.findOne({ where: { id } });
        return user;
      } catch (error) {
        console.log(error);
      }
    }
  },
}
Enter fullscreen mode Exit fullscreen mode

Now that we have users to work with, let's get users using our getUsers query.

Screen Shot 2020-09-16 at 3.02.18 AM.png

it returns exactly what we said it needed to, great. In a nutshell, these are the endpoints that we will be calling from our frontend.

Overall, your typeDefs.js file should now look like this =>

Screen Shot 2020-09-16 at 3.21.11 AM.png

And resolvers.js file

Screen Shot 2020-09-16 at 3.22.45 AM.png

Screen Shot 2020-09-16 at 3.23.34 AM.png

That's all for this one, hope that wasn't too mind-blowing. I have a few more articles to go before this series is caught up to where I'm currently at in this project. So until then, if you have any questions or if I missed anything let me know :)

💖 💪 🙅 🚩
ajeasmith
AjeaS

Posted on September 24, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related