Now we are adding email functions to the server. For this, we use NodeMailer
. Use the below command to add it.
yarn add nodemailer
yarn add -D @types/nodemailer // this is for TS support
We can grab the code from there example and paste it into a file. Now we are creating forgotPassword
mutation. Before that, we need to add the email field to User
entity.
@Field()
@Property({ type: "text", unique: true })
email!: string;
Then we need to run the migration command to update the database. Then once we run the application we will get this error.
alter table "user" add column "email" text not null;
This is because we are trying to add a not null column, but we have some users without an email address. For now, let’s delete all users.
Now we need to update user registration mutation because now email is a mandatory field. Add it to query builder also.
Now we are changing login mutation to match with this new change.
First, change in fineOne
method that checks does the user passed username
or email
.
const user = await em.findOne(
User,
usernameOrEmail.includes("@")
? { email: usernameOrEmail }
: { username: usernameOrEmail }
);
Now there is a scenario that user can have @
in there username. Let’s handle that. With that validation, we create a separate util
file called validateRegister
. Then use that util function in register
mutation.
...
const errors = validateRegister(options);
if (errors) {
return {errors};
}
...
Here you will see that we are returning the error array
as an object. The returned object is matching with the return type.
Let’s change the front-end code to match with this back-end code.
We change the Login graphql
query to get the usernameOrEmail
first.
mutation Login($usernameOrEmail: String!, $password: String!) {
login(usernameOrEmail: $usernameOrEmail, password: $password) {
... // rest of code is same
Then change the Register graphql
query.
mutation Register($options: UsernamePasswordInput!) {
register(options: $options){
... //rest of code is same
Then add the email
input field in to Register.tsx
page.
After all these changes we are coming back to send emails for users that forgot password.
In the user.ts
file inside the resolvers folder, we are adding forgotPassword
mutation.
@Mutation(() => Boolean)
async forgotPassword(
@Arg("email") email: string,
@Ctx() { em }: RedditDbContext
) {
const user = await em.findOne(User, {email});
if (!user) {
return true;
}
const token = "asdasdsadassadsadsadsadsad";
await sendEmail(email,
'<a href="http://localhost:3001/reset-password/${token}">click here to reset password</a>');
return true;
}
Within there, we first check that user email exists, if so we create a token and attached it to the reset-password link. We use uuid
package for creating a unique user token to attach to the URL.
yarn add uuid ioredis
Also, install the type support to it.
yarn add -D @types/uuid @types/ioredis
Now we use ioredis
and let’s make relevant changes in the index.ts
file.
Also, we are passing redis
in to context that later we can use it in the resolver. So now we need to add that to RedditDbContext
type.
Then create a Redis
object and use it in the RedisStore
.
// inside the index.ts file
const redis = new Redis();
// use this redis object inside the RedisStore
...
store: new RedisStore({ client: redis, disableTouch: true }),
...
Then inside the forgotPassword
mutation use this redis object. There are few things happening in here.
First we create a toke using uuid
. Then we store this in Redis
. After that we set this token in URL.
const token = v4();
await redis.set(
FORGET_PASSWORD_PREFIX + token,
user.id,
"ex",
1000 * 60 * 60 * 24 * 3
);
I will wrap up this post from here. If you have anything to ask regarding this please leave a comment here. Also, I wrote this according to my understanding. So if any point is wrong, don’t hesitate to correct me. I really appreciate you. That’s for today friends. See you soon. Thank you.
References:
This article series based on the Ben Award - Fullstack React GraphQL TypeScript Tutorial. This is amazing tutorial and I highly recommend you to check that out.
Main image credit