Building A Content Management System Using The MEAN Stack - 3 (Create Controller Modules 2)
Repository
https://github.com/nodejs/node
What Will I Learn
The codebase for this tutorial is based on MEANie an open source content management system by Jason Watmore.
This is the third tutorial in the series of tutorials on building a content management system using the MEAN technology.
In the first two tutorials we created the backend server, some helper and controller modules for the application.
In this tutorial we are going to work on the remaining controller modules for the application features including
- Contact Controller
- Pages Controller
- Posts Controller
- Redirects Controller
- Users Controller
N.B;- LINK TO THE EARLIER TUTORIALS IN THIS SERIES CAN BE FOUND AT THE END OF THIS POST
Requirements
- NodeJS and NPM,
- Angular
- MongoDB
- Text Editor
Difficulty
- Intermediate
Tutorial Contents
Contact Controller
In the application we would need a contact feature so the non-admin users will be able to get in touch with the admin through e-mail.
To create a controller for our contact feature, first of all create a new directory in the controllers directory we created in the last tutorial.
Name the new directory api
. Inside the new directory, add a new file contact.controller.js
.
In the file we have
var config = require('config.json');
var nodemailer = require('nodemailer');
var express = require('express');
var router = express.Router();
// routes
router.post('/', send);
module.exports = router;
function send(req, res) {
// email data and options
var mailOptions = {
from: req.body.email,
to: config.contactEmail,
subject: req.body.subject,
text: req.body.message
};
// send mail
var transporter = nodemailer.createTransport();
transporter.sendMail(mailOptions, function (err) {
if (err) {
console.log('error sending email', err);
return res.status(400).send(err);
}
res.sendStatus(200);
});
}
var nodemailer = require('nodemailer');
will import nodemailer
a popular JavaScript module for sending emails in Node.js applications.
router.post('/', send);
is the route that handles the email body and it has a callback function send
.
In the function send
the first variable mailOptions
is a JavaScript object containing the data required to initialize the email for sending.
from: req.body.email
holds the email address of the sender.
to: config.contactEmail
holds the address the email is sent to, the actual value for this can be found in the config.json
file. To know more about the config.json
check the first tutorial.
subject: req.body.subject
holds the title/header for the email to be sent
text: req.body.message
holds the actual email message.
var transporter = nodemailer.createTransport()
should contain the required details for the email provider account where the email is set to be received.
The value for this section is relative, read more about this from nodemailer documentation.
transporter.sendMail()
will handle the actual sending of the email given the parameter from the variable mailOptions
and a callback function that handles any error that might arise during the email transfer.
Pages Controller
Creata a new file pages.controller.js
, it will contain the following.
var config = require('config.json');
var _ = require('lodash');
var express = require('express');
var jwt = require('express-jwt')({ secret: config.secret });
var router = express.Router();
var pageService = require('services/page.service');
// routes
router.get('/', getAll);
router.get('/slug/:slug', getBySlug);
router.get('/:_id', jwt, getById);
router.post('/', jwt, create);
router.put('/:_id', jwt, update);
router.delete('/:_id', jwt, _delete);
module.exports = router;
function getAll(req, res) {
pageService.getAll()
.then(function (pages) {
// if admin user is logged in return all pages, otherwise return only published pages
if (req.session.token) {
res.send(pages);
} else {
res.send(_.filter(pages, { 'publish': true }));
}
})
.catch(function (err) {
res.status(400).send(err);
});
}
function getBySlug(req, res) {
pageService.getBySlug(req.params.slug)
.then(function (page) {
// return page if it's published or the admin is logged in
if (page.publish || req.session.token) {
res.send(page);
} else {
res.status(404).send('Not found');
}
})
.catch(function (err) {
res.status(400).send(err);
});
}
function getById(req, res) {
pageService.getById(req.params._id)
.then(function (page) {
res.send(page);
})
.catch(function (err) {
res.status(400).send(err);
});
}
function create(req, res) {
pageService.create(req.body)
.then(function () {
res.sendStatus(200);
})
.catch(function (err) {
res.status(400).send(err);
});
}
function update(req, res) {
pageService.update(req.params._id, req.body)
.then(function () {
res.sendStatus(200);
})
.catch(function (err) {
res.status(400).send(err);
});
}
function _delete(req, res) {
pageService.delete(req.params._id)
.then(function () {
res.sendStatus(200);
})
.catch(function (err) {
res.status(400).send(err);
});
}
var jwt = require('express-jwt')({ secret: config.secret });
will import the jsonwebtoken
module and the secret token required for the authentication.
We set routes for all the page operations, the ones with jwt
appearing before its callback function will require authentication before execution.
The first function getAll()
will check if the user is logged in as admin, if that is true the function returns the list of all the created pages from the database.
If the condition is false the function returns the list of published pages only.
The getBySlug()
function gets a page that matches the provided slug, the function checks if the page requested has been published or the admin is logged in, if either returns true the page is sent back as a response else a 404
status is returned.
create()
helps in the addition of a new page, it grabs the body of the request(page contents), if the operation is successful it returns a 200
OK status code.
update()
will update the contents of an existing page, it gets the id
and body
of the requested page, upon successful operation it returns a 200
OK status code.
_delete()
deletes an existing page by grabbing the page id
and returning a 200
status code upon successful operation.
Posts Controller
Create a new file posts.controller.js
in the api
directory to handle operations for blog posts.
The code for the posts controller
var config = require('config.json');
var _ = require('lodash');
var express = require('express');
var jwt = require('express-jwt')({ secret: config.secret });
var router = express.Router();
var postService = require('services/post.service');
// routes
router.get('/', getAll);
router.get('/:year/:month/:day/:slug', getByUrl);
router.get('/:_id', jwt, getById);
router.post('/', jwt, create);
router.put('/:_id', jwt, update);
router.delete('/:_id', jwt, _delete);
module.exports = router;
function getAll(req, res) {
postService.getAll()
.then(function (posts) {
// if admin user is logged in return all posts, otherwise return only published posts
if (req.session.token) {
res.send(posts);
} else {
res.send(_.filter(posts, { 'publish': true }));
}
})
.catch(function (err) {
res.status(400).send(err);
});
}
function getByUrl(req, res) {
postService.getByUrl(req.params.year, req.params.month, req.params.day, req.params.slug)
.then(function (post) {
// return post if it's published or the admin is logged in
if (post.publish || req.session.token) {
res.send(post);
} else {
res.status(404).send('Not found');
}
})
.catch(function (err) {
res.status(400).send(err);
});
}
function getById(req, res) {
postService.getById(req.params._id)
.then(function (post) {
res.send(post);
})
.catch(function (err) {
res.status(400).send(err);
});
}
function create(req, res) {
postService.create(req.body)
.then(function () {
res.sendStatus(200);
})
.catch(function (err) {
res.status(400).send(err);
});
}
function update(req, res) {
postService.update(req.params._id, req.body)
.then(function () {
res.sendStatus(200);
})
.catch(function (err) {
res.status(400).send(err);
});
}
function _delete(req, res) {
postService.delete(req.params._id)
.then(function () {
res.sendStatus(200);
})
.catch(function (err) {
res.status(400).send(err);
});
}
This file also imports the jsonwebtoken
module to handle authentication var jwt = require('express-jwt')({ secret: config.secret });
just like it is with the pages controller module.
getAll()
returns a list of all posts for logged in users(admin), if the user is not logged in it only returns the published posts.
getByUrl()
returns a post matching the provided url. The url for each post in question includes the date and slug associated with the post.
getById()
returns a post matching the provided post id
.
update()
edits and updates an existing post and returns a 200/OK
status code after successfully updating the post.
_delete()
removes an existing post from the list of all available posts. It uses the id
of the post contained in the request to identify the right post to delete.
Redirects Controller
There are instances in the application where users are redirected after completing an operation.
The next block contains code that performs functions pertaining to the redirect controller.
Add a new file in the api
folder redirects.controller.js
Code for the redirects file
var config = require('config.json');
var _ = require('lodash');
var express = require('express');
var jwt = require('express-jwt')({ secret: config.secret });
var router = express.Router();
var redirectService = require('services/redirect.service');
// routes
router.get('/', jwt, getAll);
router.get('/:_id', jwt, getById);
router.post('/', jwt, create);
router.put('/:_id', jwt, update);
router.delete('/:_id', jwt, _delete);
module.exports = router;
function getAll(req, res) {
redirectService.getAll()
.then(function (redirects) {
res.send(redirects);
})
.catch(function (err) {
res.status(400).send(err);
});
}
function getById(req, res) {
redirectService.getById(req.params._id)
.then(function (redirect) {
res.send(redirect);
})
.catch(function (err) {
res.status(400).send(err);
});
}
function create(req, res) {
redirectService.create(req.body)
.then(function () {
res.sendStatus(200);
})
.catch(function (err) {
res.status(400).send(err);
});
}
function update(req, res) {
redirectService.update(req.params._id, req.body)
.then(function () {
res.sendStatus(200);
})
.catch(function (err) {
res.status(400).send(err);
});
}
function _delete(req, res) {
redirectService.delete(req.params._id)
.then(function () {
res.sendStatus(200);
})
.catch(function (err) {
res.status(400).send(err);
});
}
All functions and operations in this file require authentication.
getAll()
gets a list of all available redirects from the database and sends it as a response.
getById
gets a single redirect matching the provided id
from the request and sends it back as a response.
create()
adds a new redirect and returns 200/OK
status code upon successful creation.
update()
edits and updates an existing redirect. The function identifies the redirect to be updated by matching the id
and body
in the request to existing ones in the database.
delete()
removes an existing redirect and returns a 200/OK
status code upon successful removal.
Users Controller
We are going to add a controller file to handle user operations and data.
Create a file in the api
directory, users.controller.js
. Add the following code
var config = require('config.json');
var express = require('express');
var jwt = require('express-jwt')({ secret: config.secret });
var router = express.Router();
var userService = require('services/user.service');
// routes
router.post('/authenticate', authenticateUser);
router.get('/current', jwt, getCurrentUser);
router.get('/:_id', jwt, getById);
router.put('/:_id', jwt, updateUser);
router.delete('/:_id', jwt, deleteUser);
module.exports = router;
function authenticateUser(req, res) {
userService.authenticate(req.body.username, req.body.password)
.then(function (token) {
if (token) {
// authentication successful
res.send({ token: token });
} else {
// authentication failed
res.status(401).send('Username or password is incorrect');
}
})
.catch(function (err) {
res.status(400).send(err);
});
}
function getCurrentUser(req, res) {
userService.getById(req.user.sub)
.then(function (user) {
if (user) {
res.send(user);
} else {
res.sendStatus(404);
}
})
.catch(function (err) {
res.status(400).send(err);
});
}
function getById(req, res) {
userService.getById(req.params._id)
.then(function (user) {
if (user) {
res.send(user);
} else {
res.sendStatus(404);
}
})
.catch(function (err) {
res.status(400).send(err);
});
}
function updateUser(req, res) {
var userId = req.user.sub;
if (req.params._id !== userId) {
// can only update own account
return res.status(401).send('You can only update your own account');
}
userService.update(userId, req.body)
.then(function () {
res.sendStatus(200);
})
.catch(function (err) {
res.status(400).send(err);
});
}
function deleteUser(req, res) {
var userId = req.user.sub;
if (req.params._id !== userId) {
// can only delete own account
return res.status(401).send('You can only delete your own account');
}
userService.delete(userId)
.then(function () {
res.sendStatus(200);
})
.catch(function (err) {
res.status(400).send(err);
});
}
authenticateUser()
requests for a username and a password. If both parameters match one existing in the database the function returns a secret token which will be supplied upon future requests for that session.
If the username or password is incorrect the function returns a 401
status and send a string alongside 'Username or password is incorrect'
.
getCurrentUser()
requests for the details of the current online user and compares it with the details of users existing in the database.
If a user with the details exist then the operation is successful else the function returns a 404/Not Found Error
status.
getById()
request for the user account that matches the provided id
, if the user exists in the database the function returns the user.
updateUser()
will help edit current user details and update it with new details.
The function requests the current user details and stores the value in a variable userId
.
The function the checks the id
from the HTTP request and compares it with the value of userId
, if they don't match then it returns a 401
status with the string 'You can only update your own account'
.
If they do match userService.update()
runs and it updates the account matching userid
with new details provided in the request body.
deleteUser()
will help remove current user details from the database.
The function requests the current user details and stores the value in a variable userId
.
The function the checks the id
from the HTTP request and compares it with the value of userId
, if they don't match then it returns a 401
status with the string 'You can only delete your own account'
.
If they do match userService.delete()
runs and it removes the account matching userid
from the database.
We have come to the end of this tutorial, in the next tutorial we will add all service modules required for communication with the database.
Proof Of Work Done
https://github.com/olatundeee/mean-cms
Thank you for your contribution.
After analyzing your tutorial we suggest the following:
Looking forward to your upcoming tutorials.
Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.
https://review.utopian.io/#modal1
To view those questions and the relevant answers related to your post, click here.
Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]
Thank you for your review, @portugalcoin!
So far this week you've reviewed 16 contributions. Keep up the good work!
Hello! Your post has been resteemed and upvoted by @ilovecoding because we love coding! Keep up good work! Consider upvoting this comment to support the @ilovecoding and increase your future rewards! ^_^ Steem On!
Reply !stop to disable the comment. Thanks!
Hi @gotgame!
Your post was upvoted by @steem-ua, new Steem dApp, using UserAuthority for algorithmic post curation!
Your post is eligible for our upvote, thanks to our collaboration with @utopian-io!
Feel free to join our @steem-ua Discord server
Hey, @gotgame!
Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!
Get higher incentives and support Utopian.io!
Simply set @utopian.pay as a 5% (or higher) payout beneficiary on your contribution post (via SteemPlus or Steeditor).
Want to chat? Join us on Discord https://discord.gg/h52nFrV.
Vote for Utopian Witness!
Congratulations @gotgame! You have completed the following achievement on the Steem blockchain and have been rewarded with new badge(s) :
Award for the number of upvotes
Click on the badge to view your Board of Honor.
If you no longer want to receive notifications, reply to this comment with the word
STOP
Do not miss the last post from @steemitboard: