Step 2

Let's add a new file, UserDao.js to the data folder with the following content:

const User = require("../model/User");
const ApiError = require("../model/ApiError");

class UserDao {

  async create({ username, password, role }) {
    if (username === undefined || username === "") {
      throw new ApiError(400, "Every user must have a username!");
    }

    if (password === undefined || password === "") {
      throw new ApiError(400, "Every user must have a password!");
    }

    if (role !== "ADMIN" && role !== "CLIENT") {
      throw new ApiError(400, "Every user must have a valid role!");
    }

    const user = await User.create({ username, password, role });
    return user;
  }

  // to update or reset password, or to change role.
  async update(id, { password, role }) {
    const user = await User.findByIdAndUpdate(
      id,
      { password, role },
      { new: true, runValidators: true }
    );

    if (user === null) {
      throw new ApiError(404, "There is no user with the given ID!");
    }

    return user;
  }

  async delete(id) {
    const user = await User.findByIdAndDelete(id);

    if (user === null) {
      throw new ApiError(404, "There is no user with the given ID!");
    }

    return user;
  }

  // returns an empty array if there is no user with the given ID
  async read(id) {
    const user = await User.findById(id);
    return user ? user : [];
  }

  // returns null if no user matches the search query
  async readOne(username) {
    const user = await User.findOne({ username });
    return user;
  }

  // returns an empty array if there is no user in the database
  //  or no user matches the user role query
  async readAll(role = "") {
    if (role !== "") {
      const users = await User.find({ role });
      return users;
    }
    const users = await User.find({});
    return users;
  }
}

module.exports = UserDao;

This data access object is comparable to NoteDao except that we have an extra readOne operation where we search the database for a user with a given username. Moreover, note the readAll operation optionally queries based on a user role to, e.g., aggregate all clients vs. admins.