Creating the User Module

We will be using Mongoose, an ODM (object data modeling) library for MongoDB, to create the user model within the user schema.

First, we need to create the schema in /users/models/users.model.js:

const userSchema = new Schema({
   firstName: String,
   lastName: String,
   email: String,
   password: String,
   permissionLevel: Number
});

Once we define the schema, we can easily attach the schema to the user model.

const userModel = mongoose.model('Users', userSchema);

After that, we can use this model to implement all the CRUD operations that we want within our endpoints.

Let’s start with the “create user” operation by defining the route in users/routes.config.js:

app.post('/users', [
   UsersController.insert
]);

At this point, we can test our Mongoose model by running the server and sending a POST request to /users with some JSON data:

{
   "firstName" : "Marcos",
   "lastName" : "Silva",
   "email" : "marcos.henrique@toptal.com",
   "password" : "s3cr3tp4sswo4rd"
}
 

We can use the controller to hash the password appropriately in /users/controllers/users.controller.js:

exports.insert = (req, res) => {
   let salt = crypto.randomBytes(16).toString('base64');
   let hash = crypto.createHmac('sha512',salt)
                                    .update(req.body.password)
                                    .digest("base64");
   req.body.password = salt + "$" + hash;
   req.body.permissionLevel = 1;
   UserModel.createUser(req.body)
       .then((result) => {
           res.status(201).send({id: result._id});
       });
};

At this point the result of a valid post will be just the id from the created user: { "id": "5b02c5c84817bf28049e58a3" }

And we need to add the createUser method to the model in users/models/users.model.js:

exports.createUser = (userData) => {
    const user = new User(userData);
    return user.save();
};
 

All set, we need to see if the user exists. For that, we are going to implement the get user by id feature for the following endpoint: users/:userId.

First, we create a route in /users/routes/config.js:

app.get('/users/:userId', [
    UsersController.getById
]);

Then we create the controller in /users/controllers/users.controller.js:

 
exports.getById = (req, res) => {
   UserModel.findById(req.params.userId).then((result) => {
       res.status(200).send(result);
   });
};
 

And finally add the findById method to the model in /users/models/users.model.js:

exports.findById = (id) => {
    return User.findById(id).then((result) => {
        result = result.toJSON();
        delete result._id;
        delete result.__v;
        return result;
    });
};
 

The response will be like this:

{
   "firstName": "Marcos",
   "lastName": "Silva",
   "email": "marcos.henrique@toptal.com",
   "password": "Y+XZEaR7J8xAQCc37nf1rw==$p8b5ykUx6xpC6k8MryDaRmXDxncLumU9mEVabyLdpotO66Qjh0igVOVerdqAh+CUQ4n/E0z48mp8SDTpX2ivuQ==",
   "permissionLevel": 1,
   "id": "5b02c5c84817bf28049e58a3"
}

Note that we can see the hashed password. For this tutorial, we are showing the password, but the obvious best practice is never to reveal the password, even if it has been hashed. Another thing we can see is the permissionLevel, which we will use to handle the user permissions later on.

Repeating the pattern laid out above we can now add the functionality to update the user. We will use the PATCH operation since it will enable us to send only the fields we want to change. The route will, therefore, be PATCH to /users/:userid and we’ll be sending any fields we want to change. We will also need to implement some extra validation since changes should be restricted to the user in question or an admin, and only an admin should be able to change the permissionLevel. We’ll skip that for now and get back to it once we implement the auth module. For now, our controller will look like this:

exports.patchById = (req, res) => {
   if (req.body.password){
       let salt = crypto.randomBytes(16).toString('base64');
       let hash = crypto.createHmac('sha512', salt).update(req.body.password).digest("base64");
       req.body.password = salt + "$" + hash;
   }
   UserModel.patchUser(req.params.userId, req.body).then((result) => {
           res.status(204).send({});
   });
};
 

By default, we will send an HTTP code 204 with no response body to indicate that the request was successful.

And we’ll need to add the patchUser method to the model:

exports.patchUser = (id, userData) => {
    return new Promise((resolve, reject) => {
        User.findById(id, function (err, user) {
            if (err) reject(err);
            for (let i in userData) {
                user[i] = userData[i];
            }
            user.save(function (err, updatedUser) {
                if (err) return reject(err);
                resolve(updatedUser);
            });
        });
    })
};
 

The user list will be implemented as a GET at /users/ by the following controller:

exports.list = (req, res) => {
   let limit = req.query.limit && req.query.limit <= 100 ? parseInt(req.query.limit) : 10;
   let page = 0;
   if (req.query) {
       if (req.query.page) {
           req.query.page = parseInt(req.query.page);
           page = Number.isInteger(req.query.page) ? req.query.page : 0;
       }
   }
   UserModel.list(limit, page).then((result) => {
       res.status(200).send(result);
   })
};

The corresponding model method will be:

exports.list = (perPage, page) => {
    return new Promise((resolve, reject) => {
        User.find()
            .limit(perPage)
            .skip(perPage * page)
            .exec(function (err, users) {
                if (err) {
                    reject(err);
                } else {
                    resolve(users);
                }
            })
    });
};

The resulting list response will have the following structure:

[
   {
       "firstName": "Marco",
       "lastName": "Silva",
       "email": "marcos.henrique@toptal.com",
       "password": "z4tS/DtiH+0Gb4J6QN1K3w==$al6sGxKBKqxRQkDmhnhQpEB6+DQgDRH2qr47BZcqLm4/fphZ7+a9U+HhxsNaSnGB2l05Oem/BLIOkbtOuw1tXA==",
       "permissionLevel": 1,
       "id": "5b02c5c84817bf28049e58a3"
   },
   {
       "firstName": "Paulo",
       "lastName": "Silva",
       "email": "marcos.henrique2@toptal.com",
       "password": "wTsqO1kHuVisfDIcgl5YmQ==$cw7RntNrNBNw3MO2qLbx959xDvvrDu4xjpYfYgYMxRVDcxUUEgulTlNSBJjiDtJ1C85YimkMlYruU59rx2zbCw==",
       "permissionLevel": 1,
       "id": "5b02d038b653603d1ca69729"
   }
]

And the last part to be implemented is the DELETE at /users/:userId.

Our controller for deletion will be:

exports.removeById = (req, res) => {
   UserModel.removeById(req.params.userId)
       .then((result)=>{
           res.status(204).send({});
       });
};

Same as before, the controller will return HTTP code 204 and no content body as confirmation.

The corresponding model method should look like this:

exports.removeById = (userId) => {
    return new Promise((resolve, reject) => {
        User.remove({_id: userId}, (err) => {
            if (err) {
                reject(err);
            } else {
                resolve(err);
            }
        });
    });
};
 

We now have all the necessary operations for manipulating the user resource, and we’re done with the user controller. The main idea of this code is to give you the core concepts of using the REST pattern. We’ll need to return to this code to implement some validations and permissions to it, but first, we’ll need to start building our security. Let’s create the auth module.

Related Posts

© 2023 Software Engineering - Theme by WPEnjoy · Powered by WordPress