Photo by Markus Winkler on Unsplash
Implementing Authenticated Article Routes.
#4 Article on this series
Introduction.
Hello there and welcome back. Still on the project walkthrough, if you missed the first article on this series, you can find it here. In this article, I will be discussing how articles can be created by a registered user, how to get a published article and a bunch of other things.
Article Model.
Open the model folder and create a new file called articleModel.js. Go ahead and paste the code below into the file.
/models/articleModel.js
const mongoose = require('mongoose')
const schema = mongoose.Schema
const articleSchema = new schema({
title:{
type:String,
required:[true, 'Blog title is required'],
unique:true
},
description:{
type:String
},
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user'
},
state:{
type:String,
default: 'draft',
enum: ['draft', 'published']
},
read_count:{
type:Number,
default: 0
},
reading_time:{
type: String
},
tags:{
type: [String],
},
body:{
type:String,
unique:true,
required:[true, 'Blog body is required']}
,
timestamp:{
type: Date
}
})
module.exports = mongoose.model('article', articleSchema)
This uses a similar pattern to the userModel we create in the previous article.
Creating an article.
Open the controllers folder, create a new file called articleController.js and paste the code below into the file.
/controllers/articleController.js
const articleModel = require("../models/articleModel");
const userModel = require("../models/userModel");
const createArticle = async (req, res, next) => {
try {
const { title, description, tags, body } = req.body;
const user = await userModel.findById(req.user._id);
const totalWordCount = body.split(" ").length;
const wordsPerMinute = totalWordCount / 200;
const reading_time =
Math.round(wordsPerMinute) === 0 ? 1 : Math.round(wordsPerMinute);
const articleObject = {
title,
description,
tags,
author: { _id: req.user._id },
timestamp: Date.now(),
reading_time,
body,
};
const article = new articleModel(articleObject);
const savedArticle = await article.save();
user.article = user.article.concat(savedArticle._id);
await user.save();
return res
.status(201)
.json({ status: true, message: "Article created successfully" });
} catch (err) {
err.status = 404;
next(err);
}
};
module.exports = { createArticle };
First, we import the models files. The createArticle() function handles the creation of the article. We get some fields from the req.body and get the user from the req.user._id. Remember how we saved the user id and email into the req in the signup controller. Then the reading time for the article is created. Now, we save the article into the database and also update the user so that the id of the article can be added to the article array we created in the userModel.
Now, we need to implement the method and path. Create a file in the routes folder and name it articleRoutes.js
/routes/articleRoutes.js
const express = require("express");
const articleRouter = express.Router();
const articleController = require("../controllers/articleController");
articleRouter.post("/article/create", articleController.createArticle);
module.exports = articleRouter;
Import the file into the app.js file and pass the passport authentication middleware in the route. This will protect the route from being accessed by a user who hasn't signed in.
/app.js
const articleRouter = require("./routes/articleRoutes");
app.use(
"/api",
passport.authenticate("jwt", { session: false }),
articleRouter
);
Always pass in the token as a bearer when trying to access the articleRouter. All the paths associated with articleRouter starts with /api
Return articles back to the user
In the articleController.js, paste the code below into the file.
/controllers/articleController.js
async function getArticleByOwner(req, res) {
try {
if (req.query.limit) {
const limit = parseInt(req.query.limit);
const articles = await articleModel
.find({ author: { _id: req.user._id } })
.limit(limit)
.populate("author", "first_name last_name");
res.status(200).send(articles);
} else {
const articles = await articleModel
.find({ author: { _id: req.user._id } })
.populate("author", "first_name last_name");
res.status(200).send(articles);
}
} catch (err) {
err.status = 404;
next(err);
}
}
The getArticleByOwner function finds an article by using the user id. It then limits the articles returned which are then populated using mongoose populate. Make sure the function is exported.
Next, open the articleRoute.js file and paste into it the code below. The path is /api/article/me.
articleRouter.get("/article/me", articleController.getArticleByOwner);
Update user article by ID.
async function updateById(req, res, next) {
try {
const id = req.params.id;
const { body } = req.body;
const article = await articleModel.findById(id);
const totalWordCount = body.split(" ").length;
const wordsPerMinute = totalWordCount / 200;
const reading_time =
Math.round(wordsPerMinute) === 0 ? 1 : Math.round(wordsPerMinute);
if (!article) {
return res
.status(400)
.json({ status: false, message: "Article cant be found" });
}
const owner = article.author.toString();
if (owner === req.user._id) {
article.body = body;
article.reading_time = reading_time;
await article.save();
res.status(200).send(article);
} else {
res.status(403).send({ message: "Unauthorized" });
}
} catch (err) {
next(err);
}
}
Article ID is passed as a parameter. The new update is passed through the request.body. If the article exists and the article belongs to the signed-in user, then the update happens. Export the file and import it into the articleRoutes.js.
articleRouter.patch("/article/edit/:id", articleController.updateById);
Update the state of the article by ID.
/controllers/articleController.js
async function updateStateById(req, res) {
const id = req.params.id;
const state = req.body.state;
if (state == "published") {
const article = await articleModel.findById(id);
if (!article) {
return res
.status(500)
.json({ status: false, message: "Article does not exist" });
}
const owner = article.author.toString();
if (req.user._id === owner) {
article.state = state;
await article.save();
res.status(200).send(article);
} else {
res.status(403).send({ message: "Unauthorized" });
}
} else {
res.status(200).send("Blog has already been published");
}
}
When an article is been created, it is by default in the draft state. This state can be updated into the published state and that is what the code above does.
/routes/articleRoutes
articleRouter.patch("/article/edit/state/:id",
articleController.updateStateById
);
The above code defines the path.
Get a published article by the user
/controllers/articleController.js
const getPublishedArticle = async (req, res) => {
try {
const id = req.params.id;
const article = await articleModel.findById(id);
const owner = article.author.toString();
const user = req.user._id;
if (article.state === "published" && owner === user) {
article.read_count++;
article.save();
res.status(200).send(article);
}
} catch (err) {
res.status(404).send({ message: "Error trying to get an article" });
}
};
The getPublishedArticle() function finds an article with an id which was passed as a parameter. It checks if the article belongs to the logged-in user. It also updates the read count.
/routes/articleRoute.js
articleRouter.get(
"/article/me/publish/:id",
articleController.getPublishedArticle
);
The URL path is /api/article/me/publish/:id
Delete an article by the user.
/controllers/articleController.js
async function deleteById(req, res, next) {
try {
const id = req.params.id;
const article = await articleModel.findById(id);
if(!article){
res.status(400).send({message:"Article does not exist"})
}
const owner = article.author.toString();
const user = await userModel.findById(req.user._id);
if (owner === req.user._id) {
await article.deleteOne();
user.article = user.article.filter((post)=>post.toString()!==article._id.toString())
await user.save();
return res.status(200).send({message:"Article deleted successfully"});
}
} catch (err) {
err.status = 404;
next(err);
}
}
The deleteById()
function finds an article by id which is passed as a parameter and then deletes the article.
articleRouter.delete("/article/:id", articleController.deleteById)
The url path is /api/article/:id
Conclusion.
In this article, we discussed how authenticated users can create articles, get articles, and edit and delete an article.
Thanks for reading.