Part 1: Creating our backend

i. Initializing our project

Set the current directory to wherever you want your project to live and initialize the project using npm .

➜ ~ mkdir mern-auth

➜ ~ cd mern-auth

➜ mern-auth npm init

After running the command, a utility will walk you through creating a package.json file.

You can enter through most of these safely, but go ahead and set the entry point to server.js instead of the default index.js when prompted (can do this later in our package.json ).

ii. Setting up our package.json

1. Set the “main” entry point to “server.js” instead of the default “index.js” , if you haven’t done so already (for conventional purposes)

2. Install the following dependencies using npm

➜ mern-auth npm i bcryptjs body-parser concurrently express is-empty jsonwebtoken mongoose passport passport-jwt validator

A brief description of each package and the function it will serve

bcryptjs : used to hash passwords before we store them in our database

: used to hash passwords before we store them in our database body-parser : used to parse incoming request bodies in a middleware

: used to parse incoming request bodies in a middleware concurrently : allows us to run our backend and frontend concurrently and on different ports

: allows us to run our backend and frontend concurrently and on different ports express : sits on top of Node to make the routing, request handling, and responding easier to write

: sits on top of to make the routing, request handling, and responding easier to write is-empty : global function that will come in handy when we use validator

: global function that will come in handy when we use jsonwebtoken : used for authorization

: used for authorization mongoose : used to interact with MongoDB

: used to interact with MongoDB passport : used to authenticate requests, which it does through an extensible set of plugins known as strategies

: used to authenticate requests, which it does through an extensible set of plugins known as passport-jwt : passport strategy for authenticating with a JSON Web Token (JWT); lets you authenticate endpoints using a JWT

: strategy for authenticating with a JSON Web Token (JWT); lets you authenticate endpoints using a JWT validator : used to validate inputs (e.g. check for valid email format, confirming passwords match)

3. Install the following devDependency (-D) using npm

➜ mern-auth npm i -D nodemon

Nodemon is a utility that will monitor for any changes in your code and automatically restart your server, which is perfect for development. The alternative would be having to take down your server ( Ctrl+C ) and stand it back up every time you made a change. Not ideal.

Make sure to use nodemon instead of node when you run your code for development purposes.

4. Change the “scripts” object to the following

"scripts": {

"start": "node server.js",

"server": "nodemon server.js",

},

Later on, we’ll use nodemon run server to run our dev server.

Your package.json file should look like the following at this stage.

{

"name": "mern-auth",

"version": "1.0.0",

"description": "Mern Auth Example",

"main": "server.js",

"scripts": {

"start": "node server.js",

"server": "nodemon server.js"

},

"author": "",

"license": "MIT",

"dependencies": {

"bcryptjs": "^2.4.3",

"body-parser": "^1.18.3",

"concurrently": "^4.0.1",

"express": "^4.16.4",

"is-empty": "^1.2.0",

"jsonwebtoken": "^8.3.0",

"mongoose": "^5.3.11",

"passport": "^0.4.0",

"passport-jwt": "^4.0.0",

"validator": "^10.9.0"

}

}

iii. Setting up our database

1. Head over to mLab and create an account if you don’t have one already

2. Create a new MongoDB Deployment

Select AWS as your cloud provider and Sandbox as your plan type. Then set your AWS region based on where you live. Finally, name your database and submit your order (don’t worry, it’s free).

3. Head over to your dashboard and click on your newly created database

Navigate to the Users tab, click Add Database User , and create a database user. Your database needs at least one user in order to use it.

Find your MongoDB URI; we will use this to connect to our database.

Replace <dbuser> and <dbpassword> with the database user credentials you just created.

4. Create a config directory and within it a keys.js file

➜ mern-auth mkdir config && cd config && touch keys.js

Within your keys.js file, let’s place the following for easy access outside of this file.

module.exports = {

mongoURI: "YOUR_MONGOURI_HERE"

};

And that’s it for this file, for now.

iv. Setting up our server with Node.js and Express

The basic flow for our server setup is as follows.

Pull in our required dependencies (namely express , mongoose and bodyParser )

, and ) Initialize our app using express()

Apply the middleware function for bodyparser so we can use it

so we can use it Pull in our MongoURI from our keys.js file and connect to our MongoDB database

from our file and connect to our MongoDB database Set the port for our server to run on and have our app listen on this port

Let’s place the following in our server.js file.

const express = require("express");

const mongoose = require("mongoose");

const bodyParser = require("body-parser"); const app = express(); // Bodyparser middleware

app.use(

bodyParser.urlencoded({

extended: false

})

);

app.use(bodyParser.json()); // DB Config

const db = require("./config/keys").mongoURI; // Connect to MongoDB

mongoose

.connect(

db,

{ useNewUrlParser: true }

)

.then(() => console.log("MongoDB successfully connected"))

.catch(err => console.log(err)); const port = process.env.PORT || 5000; // process.env.port is Heroku's port if you choose to deploy the app there app.listen(port, () => console.log(`Server up and running on port ${port} !`));

Run nodemon run server and the following should output.

➜ mern-auth nodemon run server

[nodemon] 1.18.3

[nodemon] to restart at any time, enter `rs`

[nodemon] watching: *.*

[nodemon] starting `node run server server.js`

Server up and running on port 5000 !

MongoDB successfully connected

Try changing the "Server up and running..." message in your file, hit save and you should see your server automatically restart.

Congratulations! You’ve set up a server using NodeJS and Express and successfully connected to your MongoDB database.

v. Setting up our database schema

Let’s create a models folder to define our user schema. Within models , create a User.js file.

➜ mern-auth mkdir models && cd models && touch User.js

Within User.js , we will

Pull in our required dependencies

Create a Schema to represent a User, defining fields and types as objects of the Schema

Export the model so we can access it outside of this file

Let’s place the following in our User.js file.

const mongoose = require("mongoose");

const Schema = mongoose.Schema; // Create Schema

const UserSchema = new Schema({

name: {

type: String,

required: true

},

email: {

type: String,

required: true

},

password: {

type: String,

required: true

},

date: {

type: Date,

default: Date.now

}

}); module.exports = User = mongoose.model("users", UserSchema);

Pretty standard set up for what you would expect a user to have.

vi. Setting up form validation

Before we set up our routes, let’s create a directory for input validation and create a register.js and login.js file for each route’s validation.

➜ mern-auth mkdir validation && cd validation && touch register.js login.js

Our validation flow for our register.js file will go as follows:

Pull in validator and is-empty dependencies

and dependencies Export the function validateRegisterInput , which takes in data as a parameter (sent from our frontend registration form, which we’ll build in Part 2)

, which takes in as a parameter (sent from our frontend registration form, which we’ll build in Part 2) Instantiate our errors object

object Convert all empty fields to an empty string before running validation checks ( validator only works with strings)

only works with strings) Check for empty fields, valid email formats, password requirements and confirm password equality using validator functions

functions Return our errors object with any and all errors contained as well as an isValid boolean that checks to see if we have any errors

Let’s place the following in register.js .

const Validator = require("validator");

const isEmpty = require("is-empty"); module.exports = function validateRegisterInput(data) {

let errors = {}; // Convert empty fields to an empty string so we can use validator functions

data.name = !isEmpty(data.name) ? data.name : "";

data.email = !isEmpty(data.email) ? data.email : "";

data.password = !isEmpty(data.password) ? data.password : "";

data.password2 = !isEmpty(data.password2) ? data.password2 : ""; // Name checks

if (Validator.isEmpty(data.name)) {

errors.name = "Name field is required";

} // Email checks

if (Validator.isEmpty(data.email)) {

errors.email = "Email field is required";

} else if (!Validator.isEmail(data.email)) {

errors.email = "Email is invalid";

} // Password checks

if (Validator.isEmpty(data.password)) {

errors.password = "Password field is required";

} if (Validator.isEmpty(data.password2)) {

errors.password2 = "Confirm password field is required";

} if (!Validator.isLength(data.password, { min: 6, max: 30 })) {

errors.password = "Password must be at least 6 characters";

} if (!Validator.equals(data.password, data.password2)) {

errors.password2 = "Passwords must match";

} return {

errors,

isValid: isEmpty(errors)

};

};

Our validation for our login.js follows an identical flow to the above, albeit with different fields.

const Validator = require("validator");

const isEmpty = require("is-empty"); module.exports = function validateLoginInput(data) {

let errors = {}; // Convert empty fields to an empty string so we can use validator functions

data.email = !isEmpty(data.email) ? data.email : "";

data.password = !isEmpty(data.password) ? data.password : ""; // Email checks

if (Validator.isEmpty(data.email)) {

errors.email = "Email field is required";

} else if (!Validator.isEmail(data.email)) {

errors.email = "Email is invalid";

} // Password checks

if (Validator.isEmpty(data.password)) {

errors.password = "Password field is required";

} return {

errors,

isValid: isEmpty(errors)

};

};

vii. Setting up our API routes

Now that we have validation handled, let’s create a new folder for our api routes and create a users.js file for registration and login.

➜ mern-auth mkdir routes && cd routes && mkdir api && cd api && touch users.js

At the top of users.js , let’s pull in our required dependencies and load our input validations & user model.

const express = require("express");

const router = express.Router();

const bcrypt = require("bcryptjs");

const jwt = require("jsonwebtoken");

const keys = require("../../config/keys"); // Load input validation

const validateRegisterInput = require("../../validation/register");

const validateLoginInput = require("../../validation/login"); // Load User model

const User = require("../../models/User");

Create the Register endpoint

For our register endpoint, we will

Pull the errors and isValid variables from our validateRegisterInput(req.body) function and check input validation

and variables from our function and check input validation If valid input, use MongoDB’s User.findOne() to see if the user already exists

to see if the user already exists If user is a new user, fill in the fields ( name , email , password ) with data sent in the body of the request

, , ) with data sent in the body of the request Use bcryptjs to hash the password before storing it in your database

Let’s place the following in our users.js file for our register route.



//

//

router.post("/register", (req, res) => {

// Form validation // @route POST api/users/register// @desc Register user// @access Publicrouter.post("/register", (req, res) => {// Form validation const { errors, isValid } = validateRegisterInput(req.body); // Check validation

if (!isValid) {

return res.status(400).json(errors);

} User.findOne({ email: req.body.email }).then(user => {

if (user) {

return res.status(400).json({ email: "Email already exists" });

} else {

const newUser = new User({

name: req.body.name,

email: req.body.email,

password: req.body.password

}); // Hash password before saving in database

bcrypt.genSalt(10, (err, salt) => {

bcrypt.hash(newUser.password, salt, (err, hash) => {

if (err) throw err;

newUser.password = hash;

newUser

.save()

.then(user => res.json(user))

.catch(err => console.log(err));

});

});

}

});

});

Setup passport

In your config directory, create a passport.js file.

➜ mern-auth cd config && touch passport.js

Before we setup passport, let’s add the following to our keys.js file.

module.exports = {

mongoURI: "YOUR_MONGOURI_HERE",

secretOrKey: "secret"

};

Back to passport.js . You can read more about the passport-jwt strategy in the link below. It does a great job breaking down how the JWT authentication strategy is constructed, explaining required parameters, variables and functions such as options , secretOrKey , jwtFromRequest , verify , and jwt_payload .

Let’s place the following in our passport.js file.

const JwtStrategy = require("passport-jwt").Strategy;

const ExtractJwt = require("passport-jwt").ExtractJwt;

const mongoose = require("mongoose");

const User = mongoose.model("users");

const keys = require("../config/keys"); const opts = {};

opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();

opts.secretOrKey = keys.secretOrKey; module.exports = passport => {

passport.use(

new JwtStrategy(opts, (jwt_payload, done) => {

User.findById(jwt_payload.id)

.then(user => {

if (user) {

return done(null, user);

}

return done(null, false);

})

.catch(err => console.log(err));

})

);

};

Also, note that the jwt_payload will be sent via our login endpoint below.

Create the Login endpoint

For our login endpoint, we will

Pull the errors and isValid variables from our validateLoginInput(req.body) function and check input validation

and variables from our function and check input validation If valid input, use MongoDB’s User.findOne() to see if the user exists

to see if the user exists If user exists, use bcryptjs to compare submitted password with hashed password in our database

to compare submitted password with hashed password in our database If passwords match, create our JWT Payload

Sign our jwt , including our payload , keys.secretOrKey from keys.js , and setting a expiresIn time (in seconds)

, including our , from , and setting a time (in seconds) If successful, append the token to a Bearer string (remember in our passport.js file, we set opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken(); )

Let’s place the following in our users.js file for our login route.



//

//

router.post("/login", (req, res) => {

// Form validation // @route POST api/users/login// @desc Login user and return JWT token// @access Publicrouter.post("/login", (req, res) => {// Form validation const { errors, isValid } = validateLoginInput(req.body); // Check validation

if (!isValid) {

return res.status(400).json(errors);

} const email = req.body.email;

const password = req.body.password; // Find user by email

User.findOne({ email }).then(user => {

// Check if user exists

if (!user) {

return res.status(404).json({ emailnotfound: "Email not found" });

} // Check password

bcrypt.compare(password, user.password).then(isMatch => {

if (isMatch) {

// User matched

// Create JWT Payload

const payload = {

id: user.id,

name: user.name

}; // Sign token

jwt.sign(

payload,

keys.secretOrKey,

{

expiresIn: 31556926 // 1 year in seconds

},

(err, token) => {

res.json({

success: true,

token: "Bearer " + token

});

}

);

} else {

return res

.status(400)

.json({ passwordincorrect: "Password incorrect" });

}

});

});

});

Don’t forget to export our router at the bottom of users.js so we can use it elsewhere.

module.exports = router;

Pulling our routes into our server.js file

Make the following bolded additions to server.js .

const express = require("express");

const mongoose = require("mongoose");

const bodyParser = require("body-parser");

const passport = require("passport"); const users = require("./routes/api/users"); const app = express(); // Bodyparser middleware

app.use(

bodyParser.urlencoded({

extended: false

})

);

app.use(bodyParser.json()); // DB Config

const db = require("./config/keys").mongoURI; // Connect to MongoDB

mongoose

.connect(

db,

{ useNewUrlParser: true }

)

.then(() => console.log("MongoDB successfully connected"))

.catch(err => console.log(err)); // Passport middleware

app.use(passport.initialize()); // Passport config

require("./config/passport")(passport); // Routes

app.use("/api/users", users); const port = process.env.PORT || 5000; app.listen(port, () => console.log(`Server up and running on port ${port} !`));

viii. Testing our API routes using Postman

Testing our Register endpoint

Open Postman and

Set the request type to POST

Set the request url to http://localhost:5000/api/users/register

Navigate to the Body tab, select x-www-form-urlencoded , fill in your registration parameters and hit Send

You should receive a HTTP status response of 200 OK and have the new user returned as JSON .

Check your database on mLab and you should see a new user created with the above credentials.

Testing our Login endpoint

Similar to the above, in Postman

Set the request type to POST

Set the request url to http://localhost:5000/api/users/login

Navigate to the Body tab, select x-www-form-urlencoded , fill in your login parameters and hit Send

You should receive a HTTP status response of 200 OK and have the jwt returned in the response.

Testing errors

You should play around and test your validator errors by playing around with different cases for when a user signs up and logs in (e.g. invalid email formats, passwords that don’t match). When you test the API out in Postman, you should see your errors object returned.

We’ll eventually bring these errors into our frontend and display the messages within the form itself.