In this lesson we’re going to add the user to the Postgres table when they fill the registration form, and we’re going to create a session.

The user will be immediately logged in when they register.

Let’s do it!

First, I want to simulate the registration form, because I don’t want to keep using the website interface to test our functionality.

I do so using Insomnia, a powerful application to work with network requests, for HTTP requests and also for GraphQL (it’s cross platform, works on Mac / Windows / Linux).

Remember that in the registration form we have those 3 fields:

  • email
  • password
  • passwordconfirmation

and we submit those to the /api/auth/register server-side endpoint, as JSON data.

Let’s replicate this in an Insomnia request:

If all went well, the server should now print the data you sent in.

Let’s make the connection to Postgres now, so we can store the data in the users table.

Install the pg and sequelize npm packages:

npm install pg sequelize

pg handles the Postgres connection. Sequelize instead is an ORM, a tool that makes working with a database much, much easier than working with plain SQL.

Then create a database.js file with this content:

export const user = 'flaviocopes'
export const password = ''
export const host = 'localhost'
export const database = 'nextbnb'

and change the user variable with your username.

In this file we’ll just store the database credentials.

Next, create a model.js file, and import those 3 objects from sequelize:

import { Sequelize, Model, DataTypes } from 'sequelize'

We initialize a Sequelize instance passing the data to access our database, imported from database.js, telling it which kind of database this is in the dialect property (it can handle more than just Postgres).

We also disable logging, because it can be very verbose as it logs all the SQL queries, which we don’t really need to look at (unless you’re debugging a problem):

import { Sequelize, Model, DataTypes } from 'sequelize'
import { user, password, host, database } from './database.js'

const sequelize = new Sequelize(database ,user, password, {
  host,
  dialect: 'postgres',
  logging: false
})

Then we create a model for our users table, describing the data it contains and the rules we want to apply. In this case, we disable null, to always require an email and password:

export class User extends Model {}

User.init({
  email: {
    type: DataTypes.STRING,
    allowNull: false
  },
  password: {
    type: DataTypes.STRING,
    allowNull: false
  }
}, {
  sequelize,
  modelName: 'user',
  timestamps: false
})

I also tell timestamps: false otherwise we’d get an error, as Sequelize expects the createdAt and updatedAt fields in the table, which we don’t have. You could add them, but it’s unnecessary I think.

Now we use this model in the pages/api/auth/register.js file. We import it from the src/model.js file:

import { User } from '../../../model.js'

and then we call User.create() when we get a POST request, to add the user to the database.

I use await because this method returns a promise, so I also add async to the function assigned to post:

pages/api/auth/register.js

import { User } from '../../../model.js'

export default async (req, res) => {
  if (req.method !== 'POST') {
    res.status(405).end() //Method Not Allowed
    return
  }

  const { email, password, passwordconfirmation } = req.body

  try {
    const user = await User.create({ email, password })
    res.end(JSON.stringify({ status: 'success', message: 'User added' }))
  } catch (error) {
    res.end(JSON.stringify({ status: 'error', error }))
  }
}

Great! So now if you try to send the data using Insomnia, you’ll see the data into the table:

and a JSON message back:

{
  "status": "success",
  "message": "User added"
}

If you try to add the same user twice (with the same email), an error will show up:

We have 2 problems now: we return a 200 HTTP status code, which means SUCCESS (while we have an error), and the message is a bit too detailed, which is not good - we don’t want to expose that much information to the people using our app. We just want to send what happened, and show the user some information on how to proceed.

Let’s tackle the HTTP status code first. Any status starting with 2 means success. Any status starting with 5 means server error. And we have some conventions.

Check my full HTTP status codes list blog post to learn more.

We’ll send a 500 error back, adding res.statusCode = 500:

pages/api/auth/register.js

try {
  const user = await User.create({ email, password })
  res.end(JSON.stringify({ status: 'success', message: 'User added' }))
} catch (error) {
  res.statusCode = 500
  res.end(JSON.stringify({ status: 'error', error }))
}

To solve the error message details, we can check the error details:

{
  "status": "error",
  "error": {
    "name": "SequelizeUniqueConstraintError",
    "errors": [
      {
      ...

as you can see, we get the SequelizeUniqueConstraintError string that perfectly describes the kind of error we have.

So we can intercept this error code, and send the client a helpful message, and not say anything if it’s not that specific error code.

We can log the error on the server side, of course, to help us figure out why an error occurs, but the user does not have to know it, unless we explicitly want.

This is our code:

pages/api/auth/register.js

res.statusCode = 500
let message = 'An error occurred'
if (error.name === "SequelizeUniqueConstraintError") {
  message = 'User already exists'
}
res.end(JSON.stringify({ status: 'error', message }))

And here’s the result:

Much better!

Now I want to check if the password confirmation matches the password, and if not I’ll show an error and halt the user registration process:

pages/api/auth/register.js

if (password !== passwordconfirmation) {
  res.end(JSON.stringify({ status: 'error', message: 'Passwords do not match' }))
  return
}

Here’s the full code:

pages/api/auth/register.js

import { User } from '../../../model.js'

export default async (req, res) => {
  if (req.method !== 'POST') {
    res.status(405).end() //Method Not Allowed
    return
  }

  const { email, password, passwordconfirmation } = req.body

  if (password !== passwordconfirmation) {
    res.end(
      JSON.stringify({ status: 'error', message: 'Passwords do not match' })
    )
    return
  }

  try {
    const user = await User.create({ email, password })
    res.end(JSON.stringify({ status: 'success', message: 'User added' }))
  } catch (error) {
    res.statusCode = 500
    let message = 'An error occurred'
    if (error.name === 'SequelizeUniqueConstraintError') {
      message = 'User already exists'
    }
    res.end(JSON.stringify({ status: 'error', message }))
  }
}

Another approach would be to call User.findOne() passing the email, and if there’s no user found, we can add ours.

We need to do something important now: we need to encrypt the password stored in the database.

You never store passwords as plain text. Even though also Facebook engineers can get this wrong, it is completely unacceptable that you do so, and it can quickly dismiss your professionality in the eyes of other people. In addition to be very dangerous for you and your users.

We’re going to add a configuration to Sequelize to automatically encrypt passwords using bcrypt.

Install bcrypt using npm:

npm install bcrypt

(it might take a little while)

now import bcrypt in the model.js file:

import bcrypt from 'bcrypt'

and add this code to the second argument you pass to User.init() in the model.js file:

model.js

hooks: {
  beforeCreate: async user => {
    const saltRounds = 10
    const salt = await bcrypt.genSalt(saltRounds)
    user.password = await bcrypt.hash(user.password, salt)
  }
}

We’re also going to add a new method to the User object prototype, which we call isPasswordValid():

User.prototype.isPasswordValid = async function(password) {
  return await bcrypt.compare(password, this.password)
}

which we’ll use later on to verify the password is correct.

The full code should be:

model.js

import { Sequelize, Model, DataTypes } from 'sequelize'
import { user, password, host, database } from './database.js'

const sequelize = new Sequelize(database, user, password, {
  host,
  dialect: 'postgres',
  logging: false
})

export class User extends Model {}

User.init(
  {
    email: {
      type: DataTypes.STRING,
      allowNull: false
    },
    password: {
      type: DataTypes.STRING,
      allowNull: false
    }
  },
  {
    sequelize,
    modelName: 'user',
    timestamps: false,
    hooks: {
      beforeCreate: async user => {
        const saltRounds = 10
        const salt = await bcrypt.genSalt(saltRounds)
        user.password = await bcrypt.hash(user.password, salt)
      }
    }
  }
)

User.prototype.isPasswordValid = async function(password) {
  return await bcrypt.compare(password, this.password)
}

In the next lesson we’ll go on with handling the session using Passport.js.

The code for this lesson is available at https://github.com/flaviocopes/airbnb-clone-react-nextjs/commit/9b856e81029ec47b57009ea272633045236a76ff


Go to the next module