Harsh Mangalam
Posted on May 16, 2021
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
- typeDefs/base.schema.js
- 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 frombase.schema.js
In the same way create some file inside resolvers directory
- 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
`;
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,
];
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!
}
`;
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
}
`;
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,
]
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;
}
},
},
};
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,
},
};
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,
...
};
};
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
}
}
}
{
"data": {
"register": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjExLCJpYXQiOjE2MjExODE1MDAsImV4cCI6MTYyMTIxNzUwMH0.qZUyyNMAdZzy_N-U5o3FYEq6UXfQrQUe9sG2tbK-V0A",
"user": {
"id": "11",
"name": "Harsh Mangalam",
"email": "harsh@gmail.com",
"role": "USER",
"createdAt": "1621181500667"
}
}
}
}
me
put Authorization header
{
"Authorization":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjExLCJpYXQiOjE2MjExODE1MDAsImV4cCI6MTYyMTIxNzUwMH0.qZUyyNMAdZzy_N-U5o3FYEq6UXfQrQUe9sG2tbK-V0A"
}
{
me{
id
name
email
role
createdAt
}
}
{
"data": {
"me": {
"id": "11",
"name": "Harsh Mangalam",
"email": "harsh@gmail.com",
"role": "USER",
"createdAt": "1621181500667"
}
}
}
Okay today we have discuss many things in our next post we will work more on user section.
Posted on May 16, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.