DEV Community

Cover image for Journey to the real world by cloning DEV.to backend server(part 6)
Harsh Mangalam
Harsh Mangalam

Posted on

Journey to the real world by cloning DEV.to backend server(part 6)

In our last post we have worked around providing interface to user from where user can register and login into their account.

But how can our application knew who is sending request . Suppose we want to edit our profile how application will knew who we are hence today we will work on context section in graphql where we will extract jwt token provided by user browser in header and recognise user.

One more problem we generally face in apollo graphql is splitting of schema and resolvers in different file in this post we will also split our schema and resolvers easily .

Organise project structure in more scalable way

Now create some file inside typeDefs

  1. typeDefs/base.schema.js
  2. typeDefs/auth.schema.js

base.schema.js is a js file where we will define our root Query, root Mutation and Root Subscription. For now we have no any Subscription we will introduce subscription in future.

auth.schema.js here we will define our Query, Mutation and Subscription related to authentication by extending root Query , Mutation and Subscription from base.schema.js

In the same way create some file inside resolvers directory

  1. auth.resolver.js

we have already index.js inside resolvers which will act as a central point where all resolvers file will be imported and we will combine all inside their specific root and export from here to server.js

auth.resolvers.js will contain all definition related to authentication

First we will write code for typeDefs/base.schema.js


const { gql } = require("apollo-server-express");

module.exports = gql`


  type Query

  type Mutation
`;

Enter fullscreen mode Exit fullscreen mode

The only work of this file to export root schema from here.
Here we have only provide root schema.

typeDefs/index.js



const { gql } = require("apollo-server-express");

const baseTypes = require("./base.schema");
const authTypes = require("./auth.schema");


module.exports = [
  baseTypes,
  authTypes,

];

Enter fullscreen mode Exit fullscreen mode

In this file we have imported our auth and base schema file and export from here so that it can use by server.js.
Apollo server typeDefs can be an array hence we have exported array of Schem from here.

typeDefs/auth.schema.js


const { gql } = require("apollo-server-express");

module.exports = gql`
  type AuthResponse {
    token: String!
    user: User!
  }

  extend type Query {
    me: User!
  }

  extend type Mutation {
    login(email: String!, password: String): AuthResponse!
    register(name: String!, email: String!, password: String!): AuthResponse!
  }
`;



Enter fullscreen mode Exit fullscreen mode

Here we have extend our root Query and add more schema inside root Query and Mutation.

me query is useful when we need to get authenticated user using provided jwt token we will look into me query later

We are returning AuthResponse after successful mutation on register and login and AuthResponse contain type defination for User which we have not defined yet.So lets first define User type for our Application

Create user.schema.js inside typeDefs

user.schema.js

const { gql } = require("apollo-server-express");

module.exports = gql`
  type User {
    id: ID!
    email: String
    name: String
    username: String
    avatar: String
    role: String
    location: [Float]
    createdAt: String
    updatedAt: String
  }



`;


Enter fullscreen mode Exit fullscreen mode

In this file we only define User type and in the previous way we will import this file in index.js

typeDefs/index.js

...
const userTypes = require("./user.schema");

...

module.exports = [
...

userTypes,
]

Enter fullscreen mode Exit fullscreen mode

Now we will move resolver defined for authentication in index.js into resolvers/auth.resolver.js

resolvers/auth.resolver.js


const { UserInputError, AuthenticationError } = require("apollo-server-errors");
const {
  generateHash,
  generateUsername,
  matchPassword,
  generateJwtToken,
} = require("../../utils/auth.util");

module.exports = {
  Query: {
    // fetch current user
    async me(_, __, { prisma, userId }) {
      const user = await prisma.user.findUnique({
        where: {
          id: userId,
        },
      });

      if (!user) {
        throw new AuthenticationError("token is missing");
      }
      return user;
    },
  },
  Mutation: {
    // login user
    async login(_, { email, password }, { prisma }) {
      try {
        const user = await prisma.user.findUnique({
          where: {
            email,
          },
        });

        if (!user) {
          throw new UserInputError("USER_NOT_FOUND", {
            message: "Account with this email does not found create new one",
          });
        }

        const matchPass = await matchPassword(password, user.password);

        if (!matchPass) {
          throw new UserInputError("INCORRECT_PASSWORD", {
            message: "Password is incorrect",
          });
        }

        const token = generateJwtToken(user.id);

        return {
          user,
          token,
        };
      } catch (error) {
        return error;
      }
    },

    // create new account
    async register(_, { name, email, password }, { prisma }) {
      try {
        const checkEmail = await prisma.user.findUnique({
          where: {
            email,
          },
        });

        if (checkEmail) {
          throw new UserInputError("EMAIL_ALREADY_EXISTS", {
            message: "Account with this email is already exists ",
          });
        }

        username = generateUsername(email);
        password = await generateHash(password);

        const newUser = await prisma.user.create({
          data: {
            name,
            email,
            password,
            username,
          },
        });

        const token = generateJwtToken(newUser.id);

        return {
          token,
          user: newUser,
        };
      } catch (error) {
        return error;
      }
    },
  },
};


Enter fullscreen mode Exit fullscreen mode

Inside resolvers/indexjs import auth.resolver.js and export from there

resolvers/index.js


const auth = require("./auth.resolver");


module.exports = {
  Query: {
    ...auth.Query,

  },

  Mutation: {
    ...auth.Mutation,

  },
};


Enter fullscreen mode Exit fullscreen mode

We have organise our code now we can scale it for any number of features by creating new feature.schema.js and resolvers related to that features inside resolvers folder for example in future we want post feature we can easily create post.schema.js and post.resolver.js which will contain feature related to post section such that like post , delete post , edit post etc..

If you notice me resolver carefully you will notice that it has third argument prisma and userId prisma is ok we already see in previous post it come from context but we have not exported userId from context lets do that.

Now we work on middleware section which will verify user jwt token and extract userId from that because when we was creating login and register we created jwt token by wrapping payload object as userId.

src/context.js



...
const { decodeJwtToken } = require("./utils/auth.util");

module.exports = async ({ req, res }) => {
  const token = req.headers.authorization || "";

  let userId;
  if (token) {
    userId = decodeJwtToken(token);
  }

  return {
    userId,
   ...
  };
};

Enter fullscreen mode Exit fullscreen mode

Now we will test our application

Register


 mutation {
  register(name:"Harsh Mangalam",email:"harsh@gmail.com",password:"12345"){
    token
    user {
      id
      name
      email
      role
      createdAt
    }
  }
}


Enter fullscreen mode Exit fullscreen mode
{
  "data": {
    "register": {
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjExLCJpYXQiOjE2MjExODE1MDAsImV4cCI6MTYyMTIxNzUwMH0.qZUyyNMAdZzy_N-U5o3FYEq6UXfQrQUe9sG2tbK-V0A",
      "user": {
        "id": "11",
        "name": "Harsh Mangalam",
        "email": "harsh@gmail.com",
        "role": "USER",
        "createdAt": "1621181500667"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

me

put Authorization header

{
  "Authorization":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjExLCJpYXQiOjE2MjExODE1MDAsImV4cCI6MTYyMTIxNzUwMH0.qZUyyNMAdZzy_N-U5o3FYEq6UXfQrQUe9sG2tbK-V0A"
}

Enter fullscreen mode Exit fullscreen mode
{
  me{

      id
      name
      email
      role
      createdAt
    }
  }

Enter fullscreen mode Exit fullscreen mode
{
  "data": {
    "me": {
      "id": "11",
      "name": "Harsh Mangalam",
      "email": "harsh@gmail.com",
      "role": "USER",
      "createdAt": "1621181500667"
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

Okay today we have discuss many things in our next post we will work more on user section.

Top comments (0)