Now we need to create the Mongoose models that will be reflect in the MongoDB, this is similar to Entities in the Java world. I have created the directory structure below to keep all the models in one place.

I have created 3 Models User, Profile and Post as seen below, most of the below is self explaining, the only thing to notice is that in MongoDB you can have documents inside documents which I have an example of this in my Post Model where I use the User Model. Models are pretty straight forward and I will leave you to the Mongoose documenaytion for more complex examples.
| User Model | const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
avatar : {
type: String
},
date: {
type: Date,
default: Date.now
}
});
module.exports = User = mongoose.model('user', UserSchema); |
| Profile Model | const mongoose = require('mongoose');
const ProfileSchema = new mongoose.Schema({
// reference the User model by id
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user'
},
company: {
type: String
},
website: {
type: String
},
location: {
type: String
},
status: {
type: String,
required: true
},
skills: {
type: [String],
required: true
},
bio: {
type: String
},
githubusername: {
type: String
},
// an array of other fields
experience: [
{
title: {
type: String,
required: true
},
company: {
type: String,
required: true
},
location: {
type: String
},
from: {
type: Date,
required: true
},
to: {
type: Date
},
current: {
type: Boolean,
default: false
},
description: {
type: String
}
}
],
education: [
{
school: {
type: String,
required: true
},
degree: {
type: String,
required: true
},
fieldofstudy: {
type: String,
required: true
},
from: {
type: Date,
required: true
},
to: {
type: Date
},
current: {
type: Boolean,
default: false
},
description: {
type: String
}
}
],
social: {
youtube: {
type: String
},
twitter: {
type: String
},
facebook: {
type: String
},
linkedin: {
type: String
},
instagram: {
type: String
}
},
// use a default value
date: {
type: Date,
default: Date.now
}
});
module.exports = Profile = mongoose.model('profile', ProfileSchema); |
| Post Model | const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const PostSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
text: {
type: String,
required: true
},
name: {
type: String
},
avatar: {
type: String
},
likes: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'users'
}
}
],
comments: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
text: {
type: String,
required: true
},
name: {
type: String
},
avatar: {
type: String
},
date: {
type: Date,
default: Date.now
}
}
],
date: {
type: Date,
default: Date.now
}
});
module.exports = Post = mongoose.model('post', PostSchema); |
By using Mongoose we have a number of methods (called CRUD operations - Create, Read, Update, Delete) we can use to extract data from the MongoDB, below is taken from the Mongoose doc's and you can see there many methods we can use, I will show examples of these throughout the section.

This section I cover validation, however we need to setup a piece of configuration (server.js) that allows use to parse the body of a HTTP request which can be seen below, the express.json is a json body parser, it part of the middleware, the extended of false means that you cannot post nested objects (actually parses using a different library querystring instead of qs)

So a typical POST request would look something like below with data being sent in the body of the request, express than will parse this body to extract out the data

Next we can use the express validator to perform checks and validation on incoming data, add the below line to the users.js file.
The User post route would then look something like below for checking data, to handle the response we see if there are any errors and if there are we return a status of 400 and the list of errors via an array.

So when you send a request that has errors you get the below back, I purposefully triggered all the errors, also notice that the status is 400 (BAD Request)

If you send the correct data then the code below the error checking will then be executed, also you get a status of 200, if you have nothing to execute.
I have added comments to the users.js file to explain what is happening on the complex parts, some of the code is self explain and thus no comments have been added. I will cover JWT in detail in the next section.
| User Registration | const express = require('express');
const router = express.Router();
const gravatar = require('gravatar');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const config = require('config');
const {check, validationResult} = require('express-validator');
// User model
const User = require('../../models/User');
// @route GET api/users
// @desc Test Route
// @access Public
router.get('/', (req, res) => res.send('User route'));
// @route POST api/users
// @desc Register user
// @access Public
router.post(
'/',
[
// check validation, see below if check if there were errors
check('name', 'Name is required').not().isEmpty(),
check('email', 'Please include a valid email').isEmail(),
check('password', 'Please enter a password with 6 or more characters').isLength({min: 6})
],
async (req, res) => {
const errors = validationResult(req);
// Use below to check for errors with data
if(!errors.isEmpty()){
// return array with message
return res.status(400).json({ errors: errors.array() });
}
const { name, email, password } = req.body;
// use a try-catch in case something breaks
try {
// See if the user exists we use a Mongoose CRUD operation findOne()
let user = await User.findOne({ email });
if(user) {
// return array with message
return res.status(400).json({ errors: [{ msg: 'User already exists'}] });
}
// Get users gravatar
const avatar = gravatar.url(email, {
s: '200',
r: 'pg',
d: 'mm'
});
// get a User Model object
user = new User({
name,
email,
avatar,
password
});
// Encrypt password
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(password, salt);
// Now save the user to MongoDB using Mongoose CRUD save method
await user.save();
// Return JWT
const payload = {
user: {
id: user.id
}
};
jwt.sign(
payload,
config.get('jwtSecret'),
{ expiresIn: 360000 },
(err, token) => {
if (err) throw err;
res.json({token});
}
);
} catch(err) {
console.error(err.message);
res.status(500).send('Server error');
}
}
);
module.exports = router; |
If all goes well then you will end up with something like below in the MongoDB

JSON Web Token (JWT) is broken into three parts for example xxxxx.yyyyy.zzzzz
A graphical example of a JWT is below

We are going to use the jsonwebtoken package, which you can go to, to get the full documentation. The code entered in the users.js (full code was shown in the above section) is below, we create a payload, we then create a jwt.sign that takes in the payload and we then create a global variable for the secret token (config/default.json), we pass in some options in this case the expiry time and then create a simple callback.

When we send a postman test request we get the response below back, which is our JWT token.

In order to authenticate using the JWT we need to create some middleware (you could use passport package), I will cover the next statements in a moment
![]() |
![]() |
As the above is a middleware function you need to pass three parameters, the normal res and req objects but a nextobject which is used to call the next piece of middleware, If the current middleware function does not end the request-response cycle, it must call next() method to pass control to the next middleware function. Otherwise, the request will be left hanging.

Now we can use this to protect the route, we update the auth.js file, we pull in the auth middleware and then add it as a second parameter to the router.get() method as per below, the -password omits the password from the user object.

We can then use postman to test the protected route, we pass the token we got earlier as a x-auth-token and send a GET request and you should be something like below

I am not going to go into too much detail as most of the code we have covered anyway, I have commented were we have not covered. There are alots of mongoose CRUD examples and cover HTTP GET, POST, PUT, DELETE, etc. I even have a github request example as well (see bottom of profile).
| Profile | const express = require('express');
const request = require('request');
const router = express.Router();
const auth = require('../../middleware/auth');
const {check, validationResult} = require('express-validator');
// Bring in the models
const Profile = require('../../models/Profile');
const User = require('../../models/User');
const Post = require('../../models/Post');
// @route GET api/profile/me
// @desc Get current users profile
// @access Private
router.get('/me', auth, async (req, res) => {
try {
// get the user by id, we can use populate to also bring in the user name and avatar from the User Model
const profile = await Profile.findOne({user: req.user.id}).populate('user', ['name', 'avatar']);
if (!profile) {
return res.status(400).json({msg: 'There is no profile for this user'});
}
res.json(profile);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
// @route POST api/profile
// @desc Create or Update a users profile
// @access Private
router.post(
'/',
[
auth,
[
check('status', 'Status is required').not().isEmpty(),
check('skills', 'Skills is required').not().isEmpty()
]
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({errors: errors.array()});
}
// Create a number of fields that will be populated by the request body
const {
company,
website,
location,
bio,
status,
githubusername,
skills,
youtube,
facebook,
twitter,
instagram,
linkedin
} = req.body;
// Build profile object
const profileFields = {};
profileFields.user = req.user.id;
if (company) profileFields.company = company;
if (website) profileFields.website = website;
if (location) profileFields.location = location;
if (bio) profileFields.bio = bio;
if (status) profileFields.status = status;
if (githubusername) profileFields.githubusername = githubusername;
if (skills) {
// need to turn text data into an array
profileFields.skills = skills.split(',').map(skill => skill.trim());
}
// Build social object
profileFields.social = {};
if (youtube) profileFields.social.youtube = youtube;
if (twitter) profileFields.social.twitter = twitter;
if (facebook) profileFields.social.facebook = facebook;
if (linkedin) profileFields.social.linkedin = linkedin;
if (instagram) profileFields.social.instagram = instagram;
try {
let profile = await Profile.findOne({user: req.user.id});
if (profile) {
// Update
profile = await Profile.findOneAndUpdate(
{user: req.user.id}, // update the user with id passed
{$set: profileFields}, // will update the fields passed
{new: true} // return the updated document by default returns original
);
return res.json(profile);
}
// Create
profile = new Profile(profileFields);
await profile.save(profile);
res.json(profile);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
}
);
// @route GET api/profile
// @desc Get all profiles
// @access Public
router.get('/', async (req, res) => {
try {
const profiles = await Profile.find().populate('user', ['name', 'avatar']);
res.json(profiles);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
// @route GET api/profile/user/:user_id
// @desc Get profile by user ID
// @access Public
router.get('/user/:user_id', async (req, res) => {
try {
const profile = await Profile.findOne({user: req.params.user_id}).populate('user', ['name', 'avatar']);
if (!profile) {
return res.status(400).json({msg: 'Profile not found'});
}
res.json(profile);
} catch (err) {
console.error(err.message);
if (err.kind === 'ObjectId') {
return res.status(400).json({msg: 'Profile not found'});
}
res.status(500).send('Server Error');
}
});
// @route DELETE api/profile
// @desc Delete profile, user and posts
// @access Private
router.delete('/', auth, async (req, res) => {
try {
// remove users posts
await Post.deleteMany({user: req.user.id});
// Remove profile
await Profile.findOneAndRemove({user: req.user.id});
// Remove user
await User.findOneAndRemove({_id: req.user.id});
res.json({msg: 'User and profile removed'});
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
// @route PUT api/profile/experience
// @desc Add profile experience
// @access Private
router.put('/experience',
[
auth,
[
check('title', 'Title is required').not().isEmpty(),
check('company', 'Company is required').not().isEmpty(),
check('from', 'From date is required').not().isEmpty()
]
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({errors: errors.array()});
}
// Load the variables using the data passed by the user
const {
title,
company,
location,
from,
to,
current,
description
} = req.body;
// Create a new object using the data above
const newExp = {
title,
company,
location,
from,
to,
current,
description
};
try {
const profile = await Profile.findOne({user: req.user.id});
profile.experience.unshift(newExp);
await profile.save();
res.json(profile);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
}
);
// @route DELETE api/profile/experience/:exp_id
// @desc Delete experience from profile
// @access Private
router.delete('/experience/:exp_id', auth, async (req, res) => {
try {
const profile = await Profile.findOne({user: req.user.id});
// Get remove index
const removeIndex = profile.experience
.map(item => item.id)
.indexOf(req.params.exp_id);
profile.experience.splice(removeIndex, 1);
await profile.save();
res.json(profile);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
// @route PUT api/profile/education
// @desc Add profile education
// @access Private
router.put(
'/education',
[
auth,
[
check('school', 'School is required').not().isEmpty(),
check('degree', 'Degree is required').not().isEmpty(),
check('fieldofstudy', 'Field of study is required').not().isEmpty(),
check('from', 'From date is required').not().isEmpty()
]
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({errors: errors.array()});
}
const {
school,
degree,
fieldofstudy,
from,
to,
current,
description
} = req.body;
const newEdu = {
school,
degree,
fieldofstudy,
from,
to,
current,
description
};
try {
const profile = await Profile.findOne({user: req.user.id});
profile.education.unshift(newEdu);
await profile.save();
res.json(profile);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
}
);
// @route DELETE api/profile/education/:edu_id
// @desc Delete education from profile
// @access Private
router.delete('/education/:edu_id', auth, async (req, res) => {
try {
const profile = await Profile.findOne({user: req.user.id});
// Get remove index
const removeIndex = profile.education
.map(item => item.id)
.indexOf(req.params.edu_id);
profile.education.splice(removeIndex, 1);
await profile.save();
res.json(profile);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
// @route GET api/profile/github/:username
// @desc Get user repos from Github
// @access Public
// router.get('/github/:username', (req, res) => {
// try {
// const options = {
// uri: `https://api.github.com/users/${
// req.params.username
// }/repos?per_page=5&sort=created:asc&client_id=${config.get(
// 'githubClientId'
// )}&client_secret=${config.get('githubSecret')}`,
// method: 'GET',
// headers: { 'user-agent': 'node.js' }
// };
//
// request(options, (error, response, body) => {
// if (error) console.error(error);
//
// if (response.statusCode !== 200) {
// return res.status(404).json({ msg: 'No Github profile found' });
// }
//
// res.json(JSON.parse(body));
// });
// } catch (err) {
// console.error(err.message);
// res.status(500).send('Server Error');
// }
// });
module.exports = router; |
Some more example regarding the posts, you probably can see a recurring theme
| Post examples | const express = require('express');
const router = express.Router();
const { check, validationResult } = require('express-validator');
const auth = require('../../middleware/auth');
const Post = require('../../models/Post');
const Profile = require('../../models/Profile');
const User = require('../../models/User');
// @route POST api/posts
// @desc Create a post
// @access Private
router.post(
'/',
[
auth,
[
check('text', 'Text is required').not().isEmpty()
]
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
try {
const user = await User.findById(req.user.id).select('-password');
const newPost = new Post({
text: req.body.text,
name: user.name,
avatar: user.avatar,
user: req.user.id
});
const post = await newPost.save();
res.json(post);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
}
);
// @route GET api/posts
// @desc Get all posts
// @access Private
router.get('/', auth, async (req, res) => {
try {
const posts = await Post.find().sort({ date: -1 });
res.json(posts);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
// @route GET api/posts/:id
// @desc Get post by ID
// @access Private
router.get('/:id', auth, async (req, res) => {
try {
const post = await Post.findById(req.params.id);
if (!post) {
return res.status(404).json({ msg: 'Post not found' });
}
res.json(post);
} catch (err) {
console.error(err.message);
if (err.kind === 'ObjectId') {
return res.status(404).json({ msg: 'Post not found' });
}
res.status(500).send('Server Error');
}
});
// @route DELETE api/posts/:id
// @desc Delete a post
// @access Private
router.delete('/:id', auth, async (req, res) => {
try {
const post = await Post.findById(req.params.id);
if (!post) {
return res.status(404).json({ msg: 'Post not found' });
}
// Check user
if (post.user.toString() !== req.user.id) {
return res.status(401).json({ msg: 'User not authorized' });
}
await post.remove();
res.json({ msg: 'Post removed' });
} catch (err) {
console.error(err.message);
if (err.kind === 'ObjectId') {
return res.status(404).json({ msg: 'Post not found' });
}
res.status(500).send('Server Error');
}
});
// @route PUT api/posts/like/:id
// @desc Like a post
// @access Private
router.put('/like/:id', auth, async (req, res) => {
try {
const post = await Post.findById(req.params.id);
// Check if the post has already been liked
if (
post.likes.filter(like => like.user.toString() === req.user.id).length > 0
) {
return res.status(400).json({ msg: 'Post already liked' });
}
post.likes.unshift({ user: req.user.id });
await post.save();
res.json(post.likes);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
// @route PUT api/posts/unlike/:id
// @desc Like a post
// @access Private
router.put('/unlike/:id', auth, async (req, res) => {
try {
const post = await Post.findById(req.params.id);
// Check if the post has already been liked
if (
post.likes.filter(like => like.user.toString() === req.user.id).length ===
0
) {
return res.status(400).json({ msg: 'Post has not yet been liked' });
}
// Get remove index
const removeIndex = post.likes
.map(like => like.user.toString())
.indexOf(req.user.id);
post.likes.splice(removeIndex, 1);
await post.save();
res.json(post.likes);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
// @route POST api/posts/comment/:id
// @desc Comment on a post
// @access Private
router.post(
'/comment/:id',
[
auth,
[
check('text', 'Text is required').not().isEmpty()
]
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
try {
const user = await User.findById(req.user.id).select('-password');
const post = await Post.findById(req.params.id);
const newComment = {
text: req.body.text,
name: user.name,
avatar: user.avatar,
user: req.user.id
};
post.comments.unshift(newComment);
await post.save();
res.json(post.comments);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
}
);
// @route DELETE api/posts/comment/:id/:comment_id
// @desc Delete comment
// @access Private
router.delete('/comment/:id/:comment_id', auth, async (req, res) => {
try {
const post = await Post.findById(req.params.id);
// Pull out comment
const comment = post.comments.find(
comment => comment.id === req.params.comment_id
);
// Make sure comment exists
if (!comment) {
return res.status(404).json({ msg: 'Comment does not exist' });
}
// Check user
if (comment.user.toString() !== req.user.id) {
return res.status(401).json({ msg: 'User not authorized' });
}
// Get remove index
const removeIndex = post.comments
.map(comment => comment.id)
.indexOf(req.params.comment_id);
post.comments.splice(removeIndex, 1);
await post.save();
res.json(post.comments);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
module.exports = router; |