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 = '<username>'
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,
},
session_token: {
type: DataTypes.STRING,
},
session_expiration: {
type: DataTypes.DATEONLY,
},
},
{
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 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
const user = await User.create({ email, password })
res.end(JSON.stringify({ status: 'success', message: 'User added' }))
}
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"
}
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 so far
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
}
const user = await User.create({ email, password })
res.end(JSON.stringify({ status: 'success', message: 'User added' }))
}
We need to do something important now: we need to encrypt the password stored in the database.
You never store passwords as plain text, as it is very dangerous for your reputation 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'
import bcrypt from 'bcrypt'
const sequelize = new Sequelize(database, user, password, {
host,
dialect: 'postgres',
logging: false,
})
class User extends Model {}
User.init(
{
email: {
type: DataTypes.STRING,
allowNull: false,
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
session_token: {
type: DataTypes.STRING,
},
session_expiration: {
type: DataTypes.DATEONLY,
},
},
{
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)
}
export { sequelize, User }
In the next lesson we’ll go on with handling the session.
The code for this lesson is available at https://github.com/flaviocopes/airbnb-clone-react-nextjs-2020/tree/5-2