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.