How to implement reset password functionality with Node/PostgreSQL/Knex
Tags: node, postgres, sendgrid, knex
Published: 2021-02-17 03:10:00

Have you ever wondered how to implement a reset password functionality on the server with NodeJS and PostgreSQL? 

I found many articles and tutorials on how to do it with Node and MongoDB, here I'm going to tell you how I've done it with PostgreSQL.

First, we need to find an email service, I've tried Nodemailer at first and it works perfectly in my local environment, however, in production I was getting some security issues with my Gmail account. That's why I decided to go with SendGrid.

Create a [SendGrid account](https://sendgrid.com/), go to Email API, Integration Guide, choose Web API and just follow instructions to generate an API Key

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/y7go5mo4u3g9080cyrko.png)

Install dependencies
```javascript
npm install --save @sendgrid/mail
npm install express bcryptjs jsonwebtoken knex
```

Now that you have created the API key, create a .env file to store your key.

Let's see how our user table looks
I'm using the Knex library to interact with the database
```javascript
table.increments();
table.string("email", 128).unique().notNullable();
table.string("password", 128).notNullable();
table.string("resetLink", 255);
```
As you see we define `"resetLink"` to be optional.


Now let's create the routes

```javascript
const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const sgMail = require('@sendgrid/mail');

const route = express.Router();
// bring env variables into file
const sendGridKey = process.env.SENDGRID_KEY;
const resetSecret = process.env.RESET_SECRET;

route.patch('/forgot-password', async (req, res) => {
  const { email } = req.body;

  try {
    // look for email in database
    const [user] = await filterBy({ email });
    // if there is no user send back an error
    if(!user) {
      res.status(404).json({ error: "Invalid email" });
    } else {
      // otherwise we need to create a temporary token that expires in 10 mins
      const resetLink = jwt.sign({ user: user.email }, 
      resetSecret, { expiresIn: '10m' });
      // update resetLink property to be the temporary token and then send email
      await update(user.id, { resetLink });
      // we'll define this function below
      sendEmail(user, resetLink);
      res.status(200).json({ message: "Check your email"} );
    }
  } catch(error) {
    res.status(500).json({ message: error.message });
  }
}
```

Let's define the new `PATCH` route to update the new password
```javascript

route.patch('/reset-password/:token', async (req, res) => {
  // Get the token from params
  const resetLink = req.params.token;
  const newPassword = req.body;

  // if there is a token we need to decoded and check for no errors
  if(resetLink) {
    jwt.verify(resetLink, resetPassword, (error, decodedToken) => {
         if(error) {
           res.status().json({ message: 'Incorrect token or expired' })
         }
    })
  }

  try {
    // find user by the temporary token we stored earlier
    const [user] = await filterBy({ resetLink });

    // if there is no user, send back an error
    if(!user) {
      res.status(400).json({ message: 'We could not find a match for this link' });
    }

    // otherwise we need to hash the new password  before saving it in the database
    const hashPassword = bcrypt.hashSync(newPassword.password, 8);
    newPassword.password = hashPassword;

    // update user credentials and remove the temporary link from database before saving
    const updatedCredentials = {
      password: newPassword.password,
      resetLink: null
    }

    await update(user.id, updatedCredentials);
    res.status(200).json({ message: 'Password updated' });
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
})
```

Let's also define the functions we are using to interact with the database in this case is
`findBy() and update()`

```javascript
// I'm using knex to interact with the database here
// but all is doing is looking into 'users' table and filtering the data by the filter parameter
function filterBy(filter) {
  return db('users').where(filter);
}

// same here, looking into 'users' table by 'id' and then updating the values
function update(id, changes) {
  return db('users').where({ id }).update(changes);
}
```

Let's define `sendEmail` function
```javascript
function sendEmail(user, token) {
  sgMail.setApiKey(sendGridKey);
  const msg = {
    to: user.email,
    from: "", // your email
    subject: "Reset password requested",
    html: `
     <a href="${clientURL}/reset-password/${token}">${token}</a>
   `
   // I'm only going to use an (a tag) to make this easier to
   // understand but feel free to add any email templates 
   // in the `html` property
  };

  sgMail.send(msg)
    .then(() => {
      console.log("Email sent");
  }).catch((error) => {
      console.error(error);
  })
}
```

Now that we've created the routes I'm going to use Postman to test it, we need to make a `PATCH` request
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hb7qe0k2obnaehpmjqt5.png)

You should get a 200 status if everything is okay, check your email and copy the token from the email
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zck6i04hij963auqf50a.png)

On Postman paste the token  `url/auth/reset-password/:token`
see example below

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b2deivygc9xgfecjeo4p.png)

You should get a 200 status code with a `Password update` message

Here is a link to one of my repos with full example code [repo](https://github.com/fullstack-ecommerce/ecommerce-site/blob/main/server/routes/resetPassword.ts)

I hope you find this helpful! If you have any questions let me know in the comments.
I'm planning to add the same functionality but using Redis to store the temporary token instead of the database.