Backend For MERN app

swislokdev

Swislok-Dev

Posted on May 22, 2022

Backend For MERN app

Configure

Configure database for Mongo Atlas

To get started run npm install for the following dependencies:

  • dotenv
  • express
  • express-async-handler
  • mongoose
  • jsonwebtoken
  • brcyptjs

It will be helpful to also include npm install nodemon -D to be required for automatic restarts of the node server when making file changes during development.

Create a .env file as well to contain:

  • NODE_ENV = development
  • MONGO_URI = url to mongo database
  • PORT = 5001
  • JWT_SECRET = secretpeaches

./backend/config/db.js

This file will house how to connect to the Mongo database that once created via mongodb.com, allows for a remote database connection without the need to store locally.

const mongoose = require('mongoose')

const connectDB = async () => {
  try {
    const conn - await mongoose.connect(process.env.MONGO_URI)

    // Include confirmation of connecting to Mongo
    console.log(`MongoDB connected ${conn.connection.host}`)

  } catch (error) {
    console.log('MongoDB not connected', error)
    process.exit(1)
  }
}

// Export the module for use in `index.js`
module.exports = connectDB
Enter fullscreen mode Exit fullscreen mode

Configure Error Handler

Having a custom error handler will help for debugging feedback to trace where problems may have occurred.

./backend/middleware/errorMiddleware.js

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

  // Call error code retrieved from the response
  res.status(statusCode)

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

module.exports = { errorHandler }
Enter fullscreen mode Exit fullscreen mode

Configure User Model

The user model will contain name, email, and password. The password will be hashed and salted in another file coming up.

./backend/models/userModel.js

const mongoose = require('mongoose')

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

// Export the name of the model ('User') and the module itself (userSchema)
module.exports = mongoose.model('User', userSchema)
Enter fullscreen mode Exit fullscreen mode

Configure User Controller

The controller will hold the code for all user actions.

./backend/controllers/userController.js

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

const registerUser = asyncHandler(async (req, res) => {
  // Destructure attributes from request
  const { name, email, password } = req.body

  // Check for missing information on the form
  if (!name || !email || !password) {
    res.status(400)
    throw new Error('Please fill in all required fields')
  }

  const userExists = await User.findOne({ email })

  if (userExists) {
    res.status(400)
    throw new Error('User already exists')
    // Optional to redirect to login page
  }

  // Hash password
  const salt = await bcrypt.genSalt(24)
  // Take in `password` and use `salt` for hashing algorithm
  const hashedPassword = await bcrypt.hash(password, salt)

  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')
  }
})

const loginUser = asyncHandler(async (req, res) => {
  const { email, password } = req.body

  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')
  }
})

const generateToken = (id) => {
  return jwt.sign({ id }, process.env.JWT_SECRET, {
    expiresIn: '30d',  // Determines when this token will expire and user will need to login again
  })
}

module.exports = {
  registerUser,
  loginUser
}
Enter fullscreen mode Exit fullscreen mode

Configure User Authorization Middleware

Authorization will be required by the user to access any data that is owned by the user such as user details, created assets, or settings.

./backend/middleware/authMiddleware.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 {
      token = req.headers.authorization.split(' ')[1]
      const decodedToken = jwt.verify(token, process.env.JWT_SECRET)
      req.user = await User.findById(decodedToken.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, no token')
  }
})

module.exports = { protect }
Enter fullscreen mode Exit fullscreen mode

Configure User Routes

Routes that will be used by express to act upon actions by and for the user while utilizing the middleware to maintain authorization.

./backend/routes/userRoutes.js

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

// POST route  api/users/
router.post('/', registerUser)

// POST route  api/users/login
router.post('/login', loginUser)

module.exports = router
Enter fullscreen mode Exit fullscreen mode

Configure Express Server

This is the launching point for express to send information out and allow access to the rest of the backend files with custom error handling.

./backend/index.js

const express = require('express')
const dotenv = require('dotenv').config()
const connectDB = require('./config/db')
const { errorhandler } = require('./middleware/errorMiddleware')
const port = process.env.PORT || 5001

// Connect to Mongo Atlas database
connectDB()

const app = express()

app.use(express.json())
app.use(express.urlencoded({ extended: false })

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

app.use(errorhandler)

// Port to be used for the server to run on
app.listen(port, () => console.log(`Server running on port ${port}`))
Enter fullscreen mode Exit fullscreen mode

Conclusion

This was a basic boilerplate for setting up an express server for users to be customized as needed for the next use case.

Back to top

💖 💪 🙅 🚩
swislokdev
Swislok-Dev

Posted on May 22, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related