Backend For MERN app
Tags: mongodb, javascript, express, node
Published: 2022-05-22 23:12:23

## Configure

- [Mongo Atlas database](#configure-database-for-mongo-atlas)
- [Error Handler](#configure-error-handler)
- [User Model](#configure-user-model)
- [User Controller](#configure-user-controller)
- [User Authorization Middleware](#configure-user-authorization-middleware)
- [User Routes](#configure-user-routes)
- [Express Server](#configure-express-server)

## 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](mongodb.com), allows for a remote database connection without the need to store locally.

```JavaScript
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
```

## Configure Error Handler

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

### ./backend/middleware/errorMiddleware.js

```JavaScript
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 }
```

## 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

```JavaScript
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)
```

## Configure User Controller

The controller will hold the code for all user actions.

### ./backend/controllers/userController.js

```JavaScript
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
}
```

## 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

```JavaScript
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 }
```

## 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

```JavaScript
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
```

## 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

```JavaScript
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}`))
```

## 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](#configure)