Getting Started With MERN Stack (Part II)

Prakash
by Prakash 

MERN stack is a free JavaScript software stack for building dynamic web applications, consisting of four key technologies.

  • MongoDB
  • ExpressJS
  • ReactJS
  • NodeJS

This blog is the second part of a ‘Getting started with MERN stack’ series that aims to get you acquainted with the MERN stack. Read the first one here, before moving on with this one.

Connecting to MongoDB Atlas

Follow the Get Started with Atlas guide to create an account, deploy your first cluster, and locate your cluster’s connection string.

Now that you have the connection string, go back to the ‘backend’ directory and create a ‘.env’ file under config directory. There, assign the connection string to a new MONGO_URI variable. Once done, your file should look similar to the one below. Replace and the with your database username and password.

NODE_ENV = development
PORT = 5000
MONGO_URI = mongodb+srv://<username>:<password>@mahatcluster.8vj8e.mongodb.net/mernapp?retryWrites=true&w=majority

We can add the following code to connect to our database under the config folder and inside the db.js file.

const mongoose = require('mongoose')

const connectDB = async () => {
 try {
   const conn = await mongoose.connect(process.env.MONGO_URI)
    console.log(`MongoDB Connected: ${conn.connection.host}`.cyan.underline)
 } catch {
   console.log(error)
   process.exit(1)
 }
}

module.exports = connectDB

After that we bring the connectDB() to server.js

const connectDB = require('./config/db')
connectDB()

Building the RESTful APIs with the MERN stack

Create a folder named routes. In it, create a file named userRoutes.js which will forward the route to a particular API inside userController.js under the controller folder.

Also, apply middleware to protect the user route and to change the default express error handler. Here, ‘protect’ middleware function is used to protect the route.

// userRoutes.js
const express = require('express')
const router = express.Router()
const {registerUser, loginUser, getMe} = require('../controllers/userController')
const {protect} = require('../middleware/authMiddleware')

router.post('/', registerUser)
router.post('/login', loginUser)
router.get('/me', protect, getMe)

module.exports = router

In the above code, I’ve created three different routes, one for creating/registering users, another for logging in or authenticating user, and the last one for getting user details. Two of them are post requests and one is a get request.

You might also be interested in reading: Getting Started with React Native

Now, we need to install express-async-handler and jsonwebtoken

npm i express-async-handler jsonwebtoken bcryptjs

Write API logic inside userController.js under the controller folder and we bring the user model here which will be discussed later. We need to bring jsonwebtoken for representing claims securely between two parties and bcryptjs to hash passwords.

// userController.js
const jwt = require('jsonwebtoken')
const bcrypt = require('bcryptjs')
const asyncHandler = require('express-async-handler')
const User = require('../models/userModel')

// @desc     Register new user
// @route    POST /api/users
// @access   Public
const registerUser = asyncHandler(async (req, res) => {
 const { name, email, password } = req.body

 if(!name || !email || !password) {
   res.status(400)
   throw new Error('Please add all fields')
 }

 // Check if user exists
 const userExists = await User.findOne({email})

 if(userExists) {
   res.status(400)
   throw new Error('User already exists')
 }

 // Hash password
 const salt = await bcrypt.genSalt(10)
 const hashedPassword = await bcrypt.hash(password, salt)

 // Create user
 const user = await User.create({
   name,
   email,
   password: hashedPassword
 })

 if(user) {
   res.status(201).json({
     _id: user.id,
     name: user.name,
     email: user.email,
     token: generateToken(user._id)
   })
 } else {
   res.status(400)
   throw new Error('Invalid user data')
 }
})

// @desc     Authenticate a user
// @route    POST /api/users/login
// @access   Public
const loginUser = asyncHandler(async (req, res) => {
 const {email, password} = req.body

 // Check for user email
 const user = await User.findOne({email})

 if(user && (await bcrypt.compare(password, user.password))) {
   res.json({
     _id: user.id,
     name: user.name,
     email: user.email,
     token: generateToken(user._id)
   })
 } else {
   res.status(400)
   throw new Error('Invalid credentials')
 }
})

// @desc     Get user data
// @route    GET /api/users/me
// @access   Private
const getMe = asyncHandler(async (req, res) => {
 res.status(200).json(req.user)
})

// Generate JWT
const generateToken = (id) => {
 return jwt.sign({ id }, process.env.JWT_SECRET, {
   expiresIn: '30d',
 })
}

module.exports = {
 registerUser,
 loginUser,
 getMe,
}

We are using express-async-handler for handling exceptions inside of async express routes and passing them to our express error handlers.

Also, we are going to check to see if the created API is working properly using postman.

Under the folder backend, create another folder named middleware and two files called authMiddleware.js and errorMiddleware.js inside middleware.

// authMiddeware.js
const jwt = require('jsonwebtoken')
const asyncHandler = require('express-async-handler')
const User = require('../models/userModel')

const protect = asyncHandler(async (req, res, next) => {
 let token

 if(req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
   try {
     // Get token from header
     token = req.headers.authorization.split(' ')[1]
    
     // Verify token
     const decoded = jwt.verify(token, process.env.JWT_SECRET)
  
     // Get user from the token
     req.user = await User.findById(decoded.id).select('-password')

     next()
   } catch (error) {
     console.log(error)
     res.status(401)
     throw new Error('Not authorized')
   }
 }

 if(!token) {
   res.status(401)
   throw new Error('Not authorized')
 }
})

module.exports = { protect }

In the authMiddleware.js, I’m trying to get a token from request headers, verify it with JWT_SECRET, get the user(not password) from the token, and then call the next() middleware.

// errorMiddleware.js
const errorHandler = (err, req, res, next) => {
 const statusCode = res.statusCode ? res.statusCode : 500

 res.status(statusCode)

 res.json({
     message: err.message,
     stack: process.env.NODE_ENV === 'production' ? null : err.stack
 })
}

module.exports = {
 errorHandler,
}

In the above code, we are passing the err object to overwrite the express error handler message where the status code is checked and set to status code from userController or status code set to 500. We will show error messages accordingly and stack in development mode (in production set to null).

In order to interact with our database, we need to create a model for each of our resources. So, create a folder called model inside the backend folder, and inside the model folder, create a file called userModel.js and update it with this:

const mongoose = require('mongoose')

const userSchema = mongoose.Schema(
 {
   name: {
     type: String,
     required: [true, 'Please add a name']
   },
   email: {
     type: String,
     required: [true, 'Please add an email'],
     unique: true
   },
   password: {
     type: String,
     required: [true, 'Please add a password']
   },
 },
 {
   timestamps: true
 }
)
module.exports = mongoose.model('User', userSchema)

We are creating a user schema passing object with fields name, email, password, and timestamps. Email must be a unique field and all fields are mandatory.

In our server.js file we need to set up route for userRoutes.js

app.use('/api/users', require('./routes/userRoutes'))

Conclusion

In the part II of the blog, we learnt how to connect to MongoDB Atlas. In the next, and final part of the series, we will learn to set up react Application.

ReactJS is a widely used JavaScript library for building interactive user interfaces. Read about our collaboration with Almond to build a mobile commerce app based on React that enables communities in developing countries to grow revenue.

Stuck with web application development? Contact us for a consultation on Application Development.