DEV Community

Cover image for JWT User Authentification in Node.js using Express.js and MongoDB
Kristina Degtiareva
Kristina Degtiareva

Posted on

JWT User Authentification in Node.js using Express.js and MongoDB

To get started with this project, it is recommended to set up a basic project with separate folders for the client and server. This will help maintain a clear separation of concerns between frontend and backend functionalities.

MongoDB setup

To create a MongoDB database, you need to create an account, and then click on "Build Database." The free version is sufficient for our project. Choose the cloud, I am sticking to the default AWS.

Next, it will prompt you to create a username and password for your database. Pay attention to password you use as we will need it when setting up mongoDB in the project. For this project, I chose the cloud environment, and set the IP address to 0.0.0.0 (Note: This is not secure and is only used for this example project. For your real project, use a specific IP or range of IPs).

Once you have created your database, click "Connect" and choose "Connect to Application" and select Node.js as a driver. Copy the provided string, we will need in few moments for our project.

Setting up the server and database connection

Now we need to set up our environment variables by configuring the .env file. In the .env file, we'll add the MongoDB connection URL that we copied earlier, along with other variables such as the port number and token secret. Make sure to replace the password in the MongoDB connection URL with the actual password that you created earlier for your database.

PORT=5001
NODE_ENV=development
MONGO_URL= mongodb+srv://kristenking0110:dev321@cluster0.f9lb2mc.mongodb.net/?retryWrites=true&w=majority
TOKEN_SECRET = my_secret

Enter fullscreen mode Exit fullscreen mode

Next, we create a server.js file where we use Express.js to create our server and connect to the MongoDB database using the connectDB() function. We then define our routes and start listening to incoming requests on the specified port number.

const dotenv = require('dotenv');
dotenv.config({ path: './config/config.env' });
const express = require('express');
const connectDB = require('./config/database');
const logger = require('./utils/logger');
const userRouter = require('./routes/userRouter');

const app = express();

// Connect to database
connectDB();

app.use(express.json());

const PORT = process.env.PORT || 5001;

// Use routes
app.use('/users', userRouter);

const server = app.listen(PORT, () => {
  console.log(`Server is listening on PORT ${PORT}`);
});

process.on('unhandledRejection', (err) => {
  console.log(`Error: ${err.message}`);
  server.close(() => process.exit(1));
});
Enter fullscreen mode Exit fullscreen mode

We also need to create a database.js file, where we use Mongoose to connect to the MongoDB database and log a message indicating a successful connection.

const mongoose = require('mongoose'); 

const connectDB = async () => {
    const conn = await mongoose.connect(process.env.MONGO_URL, {
        useNewUrlParser: true, 
        useUnifiedTopology: true
    })

    console.log(`MongoDB Connected: ${conn.connection.host}`)
}

module.exports = connectDB;
Enter fullscreen mode Exit fullscreen mode

Installing packages

Make sure that you are only working within your server folder. Run 'npm init' to generate a separate package.json file. Make sure that your folder structure is set up appropriately.

folder structure

Now, let's install all the packages we'll use for this project:

npm i express mongoose dotenv jsonwebtoken bcrypt express-handler
Enter fullscreen mode Exit fullscreen mode

Before proceeding, you can run your server to check if the MongoDB connection is successful and ready to go.

mongoDB connection

Create userSchema

Once you have installed the necessary packages and ensured that your MongoDB connection is successful, the next step is to create the userSchema. This schema defines the structure of the user data that will be stored in the database. Additionally, we include a matchPasswords method to validate the entered password, as well as a pre-save hook to hash the password before storing it in the database.

const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const bcrypt = require('bcryptjs');

const userSchema = new Schema(
  {
    firstName: {
      type: String,
      required: [true, "Please provide a first name"],
    },
    lastName: {
      type: String,
      required: [true, "Please provide a last name"],
    },
    email: {
      type: String,
      unique: true,
      required: [true, "Please provide an email address"],
    },
    password: {
      type: String,
      required: [true, "Please provide a password"],
    },
    profileImage: {
      type: String,
    },
    isAdmin: {
      type: Boolean,
      default: false,
    }
  },
  { timestamps: true }
);

userSchema.methods.matchPasswords = function (enteredPassword) {
  return bcrypt.compare(enteredPassword, this.password);
};

userSchema.pre('save', async function (next) {
  if (!this.isModified('password')) {
    next();
  }

  const salt = await bcrypt.genSalt(10);
  this.password = await bcrypt.hash(this.password, salt)
});

const User = mongoose.model('User', userSchema);
module.exports = User;
Enter fullscreen mode Exit fullscreen mode

Create userController

Next we need to create userController that will handle all user-related requests such as login, registration and I included some CRUD opertions.

const express = require('express');
const User = require('../models/users');
const asyncHandler = require("express-async-handler");
const jwt = require("jsonwebtoken");



const genToken = (id) => {
  return jwt.sign({ id }, process.env.TOKEN_SECRET, { expiresIn: '60d' });
}

const loginUser = asyncHandler(async (req, res) => {
  const { email, password } = req.body;
  const user = await User.findOne({ email });

  if (user && (user.matchPasswords(password))) {
    res.json({
      _id: user._id,
      firstName: user.firstName,
      lastName: user.lastName,
      isAdmin: user.isAdmin,
      token: genToken(user._id)
    })
  } else {
    res.status(401);
    throw new Error('Invalid email or password.')
  }
}
);


const registerUser = asyncHandler(async (req, res) => {
  const { firstName, lastName, email, password } = req.body;

  const userExists = await User.findOne({ email });
  if (userExists) {
    res.status(400)
    throw new Error("We already have an account with that email address. ");
  }

  const user = await User.create({
    firstName,
    lastName,
    email,
    password,
  });

  if (user) {
    res.status(201).json({
      _id: user._id,
      firstName: user.firstName,
      lastName: user.lastName,
      email: user.email,
      isAdmin: user.isAdmin,
      token: genToken(user._id),
    });
  } else {
    res.status(400);
    throw new Error('Invalid user data');
  }
});

const getUsers = async (req, res, next) => {
  try {
    const result = await User.find()

    res
      .status(200)
      .setHeader('Content-Type', 'application/json')
      .json(result)
  } catch (err) {
    throw new Error(`Error retrieving users: ${err.message}`)
  }
}

const postUser = async (req, res, next) => {
  try {
    const result = await User.create(req.body)

    res
      .status(201)
      .setHeader('Content-Type', 'application/json')
      .json(result)
  } catch (err) {
    throw new Error(`Error displaying a new user: ${err.message}`)
  }
}

const deleteUsers = asyncHandler(async (req, res, next) => {
  try {
    await User.deleteMany({});
    res.status(200).json({
      success: true,
      msg: 'All users were deleted'
    });
  } catch (err) {
    throw new Error(`Error deleting users: ${err.message}`);
  }
});

const getUser = asyncHandler(async (req, res, next) => {
  try {
    const result = await User.findById(req.params.id)
    if (!result) {
      res.status(404)
      throw new Error("User not found.");
    }

    res
      .status(200)
      .setHeader('Content-Type', 'application/json')
      .json(result)
  } catch (err) {
    throw new Error(`Error retrieving user: ${err.message}`)
  }
})

const putUser = asyncHandler(async (req, res, next) => {
  try {
    const result = await User.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
    if (!result) {
      res.status(404)
      throw new Error("User not found.");
    }

    res
      .status(200)
      .setHeader('Content-Type', 'application/json')
      .json(result)
  } catch (err) {
    throw new Error(`Error updating user: ${err.message}`)
  }
})

const deleteUser = asyncHandler(async (req, res, next) => {
  const userId = req.params.id;

  const user = await User.findById(userId);
  if (!user) {
    res.status(404);
    throw new Error('User not found');
  }

  await user.remove();

  res.json({ success: true, message: 'User deleted successfully' });
});


module.exports = {
  getUsers,
  postUser,
  deleteUsers,
  getUser,
  putUser,
  deleteUser,
  loginUser,
  registerUser
}
Enter fullscreen mode Exit fullscreen mode

Create useRouter

Once the userSchema and userController are set up, we can define the routes that will handle user-related requests.

const express = require('express');
const userController = require('../controllers/userController');

const userRouter = express.Router();

userRouter.get('/', userController.getUsers);
userRouter.post('/', userController.postUser);
userRouter.delete('/', userController.deleteUsers);
userRouter.get('/:id', userController.getUser);
userRouter.put('/:id', userController.putUser);
userRouter.delete('/:id', userController.deleteUser);
userRouter.post('/login', userController.loginUser);
userRouter.post('/register', userController.registerUser);

module.exports = userRouter;
Enter fullscreen mode Exit fullscreen mode

Check on Postman

Now we can check the registration and login user in Postman.

register user

login user

I hope this article helped to provide a general understanding of user authentification concepts.

Top comments (0)