MERN Stack - Authentication and Deployment - Part 2/3

Sun Oct 31 2021

This is the second part of MERN Stack Authentication and Deployment series. In part 1 we worked on backend. In this part we will work on react app.

First we will create a react app. At root level of project open terminal and run

npx create-react-app client

I called the react app client feel free to name it anything you like. While our react app is being created lets add a dependency in backend

npm install -D concurrently

Open backend package.json file replace scripts with

"scripts": {
   "start": "node index.js",
   "server": "nodemon index.js",
   "dev": "concurrently \"npm run server\" \"npm start --prefix client\" "
}

While we are in development, when we run npm run dev it will run both backend and frontend with this single command so we don't have to run backend and frontend separately.

There is one more dependency we need to install in backend which is cors so that we don't get cors error when we attempt to communicate to backend from our react frontend.

npm install cors

In index.js file at the top we will bring this package

const cors = require("cors")

and before app.use(express.json()) add

app.use(cors())

Once the react app is created, from terminal navigate into react directory and run

npm install react-router-dom

In React app package.json, add the following

"proxy": "http://localhost:5000"

With this proxy we don't have to write to http://localhost:5000/auth when we make request to our backend, we will only write /auth and proxy will attach the url itself.

Open App.js file and replace the existing content with

import React from "react"
import { BrowserRouter, Switch, Route } from "react-router-dom"
import "./App.css"
import Register from "./components/Register"
import Login from "./components/Login"
import Home from "./components/Home"

const App = () => {
  return (
    <BrowserRouter>
      <Switch>
        <Route exact path="/register" component={Register} />
        <Route exact path="/login" component={Login} />
        <Route exact path="/home" component={Home} />
      </Switch>
    </BrowserRouter>
  )
}

export default App

In src directory create a folder components and inside it create three files Register.js, Login.js and Home.js

In Register.js paste the following

import React, { useState } from "react"
import { Link } from "react-router-dom"

const Register = () => {
  const [data, setData] = useState({
    name: "",
    email: "",
    password: "",
  })
  
  const { name, email, password } = data

  const handleChange = e =>
    setData({ ...data, [e.target.name]: e.target.value })

  const handleSubmit = async e => {
    e.preventDefault()
  }
  return (
    <div>
      <h4>Create an account</h4>
      <form onSubmit={handleSubmit}>
        <div>
          <label htmlFor="name">Name</label>
          <input
            type="text"
            name="name"
            value={name}
            onChange={handleChange}
          />
        </div>
        <div>
          <label htmlFor="email">Email</label>
          <input
            type="email"
            name="email"
            value={email}
            onChange={handleChange}
          />
        </div>
        <div>
          <label htmlFor="password">Password</label>
          <input
            type="password"
            name="password"
            value={password}
            onChange={handleChange}
          />
        </div>
        <div>
          <button>Register</button>
        </div>
        <p>
          Already a user? <Link to="/login">Login</Link>
        </p>
      </form>
    </div>
  )
}

export default Register

We initialized name, email and password in state and initial value is empty string for all of them. In return statement we are displaying a simple form and we have attached handleSubmit function to form. We also bind name, email and password to input fields along with onChange function which points to handleChange function.

handleChange will capture the user input of the fields and set the data provided by user in state. Now we will have to make request to our backend to register a user and we will do this in handleSubmit function.

We have two options here to use, fetch and axios. I will go with axios. Open terminal and make sure you are in client folder, run

npm install axios

In Register.js file at the top add

import axios from "axios"

Replace the existing handleSubmit function with the following

const handleSubmit = async e => {
    e.preventDefault()

    try {
      await axios.post(
        "/auth/register",
        { name, email, password },
        { headers: { "Content-Type": "application/json" } }
      )
      props.history.push("/login")
    } catch (err) {
      console.log(err)
    }
  }

Here we are making a post request and the first argument is the route which is /auth/register route, second argument is the data and finally headers. Upon successfully registration, we redirected the user to login page.

In Login.js paste the following

import React, { useState } from "react"
import { Link } from "react-router-dom"

const Login = props => {
  const [data, setData] = useState({    
    email: "",
    password: "",
  })

  const { email, password } = data

  const handleChange = e =>
    setData({ ...data, [e.target.name]: e.target.value })

  const handleSubmit = async e => {
    e.preventDefault()

    try {
      const res = await axios.post(
        "/auth/login",
        { email, password },
        { headers: { "Content-Type": "application/json" } }
      )
      localStorage.setItem("token", res.data.token)
      props.history.push("/")
    } catch (err) {
      console.log(err)
    }
  }
  return (
    <div>
      <h4>Log into your account</h4>
      <form onSubmit={handleSubmit}>        
        <div>
          <label htmlFor="email">Email</label>
          <input
            type="email"
            name="email"
            value={email}
            onChange={handleChange}
          />
        </div>
        <div>
          <label htmlFor="password">Password</label>
          <input
            type="password"
            name="password"
            value={password}
            onChange={handleChange}
          />
        </div>
        <div>
          <button>Login</button>
        </div>
        <p>
          Not a user? <Link to="/register">Register</Link>
        </p>
      </form>
    </div>
  )
}

export default Login

This component is similar to Register.js however there are a few differences. We don't have name field here and the request path is /auth/login and upon successful login we stored the token which we received from backend in localStorage and redirected the user to home page

In Home.js paste the following

import React, { useEffect, useState } from "react"
import axios from "axios"

const Home = props => {
  const [user, setUser] = useState(null)

  const getUser = async () => {
    const res = await axios.get("/auth", {
      headers: {
        Authorization: `Bearer ${localStorage.getItem("token")}`,
      },
    })
    setUser(res.data)
  }

  useEffect(() => {
    getUser()
  }, [])

  const logout = () => {
    localStorage.removeItem("token")
    props.history.push("/login")
  }

  return (
    <div>
      <p>Welcome {user && user.name}</p>
      <button onClick={logout}>Logout</button>
    </div>
  )
}

export default Home

We initialized user state, initially its null. We then defined getUser function which makes a get request to /auth and if you remember in our backend /auth is a protected route so we have provided Authorization in headers. We got the token from localStorage. In useEffect we called this getUser function.

logout function removes the token from localStorage and redirects user to login page. Ideally we should have a component for making a route protected but for this tutorial I will just add a check to make home page protected. Add the following after logout function

if (!localStorage.getItem("token")) {
  props.history.push("/login")
}

This will make sure if there is token in localStorage then user can visit home page otherwise user will be redirected to login page.

Open terminal make sure you are at root level of project (not inside client directory) and run

npm run dev

This will run both backend and frontend. Now try creating a user, login user and logout. Everything should work as expected.

So we have built backend in part 1 and frontend in this tutorial. In the next tutorial we will deploy our MERN stack app to heroku.

Share

Privacy Policy
icon
icon
icon
icon

Developed By Farhan Farooq