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
:
constuserSchema =
newSchema({
firstName
:
String,
lastName
:
String,
:
String,
password
:
String,
permissionLevel
:
Number
});
Once we define the schema, we can easily attach the schema to the user model.
constuserModel = 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 =
newUser(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
(
leti
inuserData) {
user[i] = userData[i];
}
user.save(
function (err, updatedUser){
if
(err)
returnreject(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.