I'm looking for a review of my backend code on how to keep it more DRY. I see a lot of repetition in validation part of email and password, also I need some advice on if I have used the try-catch and async/await correctly. And most importantly, the thing that I have to clear is the sending of response back. Sometimes, I get this error of Can't set headers after they are sent to the client. I know this arises when I'm trying to send two responses back, but I need a review on how do I structure my response sending so that I don't get this error.
Also, as you can see below code that I've used mostly:
catch (error) {
return next(error)
but when I tried to do the same in getUserPosts, I got the same error that I've described above. So, I had to change it to:
catch (error) {
console.log(error)
I don't know if this a good way to handle errors.
Here's my code with models and controller functions.
models
User.js
const mongoose = require("mongoose")
const bcrypt = require("bcrypt")
const Schema = mongoose.Schema
const userSchema = new Schema({
username: { type: String, required: true },
email: { type: String, reuired: true },
password: { type: String, required: true },
posts:[{ type: Schema.Types.ObjectId, ref: "Post" }]
}, { timestamps: true })
userSchema.methods.confirmPassword = function (password) {
return bcrypt.compareSync(password, this.password)
}
const User = mongoose.model("User", userSchema)
module.exports = User
Post.js
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
var URLSlug = require("mongoose-slug-generator");
mongoose.plugin(URLSlug);
const postSchema = new Schema({
title: { type: String, required: true },
description: { type: String, required: true },
user: { type: Schema.Types.ObjectId, ref: "User" },
slug: { type: String, slug: "title" },
}, { timestamps: true }
)
postSchema.pre("save", function (next) {
this.slug = this.title.split(" ").join("-");
next();
});
const Post = mongoose.model("Post", postSchema);
module.exports = Post;
usersController.js
const User = require("../models/User")
const auth = require("../utils/auth")
const validator = require("validator")
const bcrypt = require("bcrypt")
module.exports = {
registerUser: async (req, res, next) => {
try {
var { username, email, password } = req.body
if (password) {
const salt = bcrypt.genSaltSync(10)
password = bcrypt.hashSync(password, salt)
}
if (!username || !email || !password) {
return res
.status(400)
.json({ message: "Username, email and password are must" })
}
if (!validator.isEmail(email)) {
return res.status(400).json({ message: "Invaid email" })
}
if (password.length < 6) {
return res
.status(400)
.json({ message: "Password should be of at least 6 characters" })
}
const user = await User.create({ username, email, password })
if (!user) {
return res.status(404).json({ error: "No user found " })
}
return res.status(200).json({ user })
} catch (error) {
return next(error)
}
},
loginUser: async (req, res, next) => {
try {
const { email, password } = req.body
if (!email || !password) {
return res.status(400).json({ message: "Email and password are must" })
}
if (!validator.isEmail(email)) {
return res.status(400).json({ message: "Invalid email" })
}
const user = await User.findOne({ email })
if (!user) {
return res.status(404).json({ message: "This email does not exist" })
}
if (!user.confirmPassword(password)) {
return res.status(401).json({ message: "Incorrect password" })
}
const token = auth.signToken({ userId: user._id })
res.status(200).json({ user, token })
} catch (error) {
return next(error)
}
},
identifyUser: async (req, res, next) => {
try {
const userId = req.user.userId
const user = await User.findOne({ _id: userId })
if (!user) {
return res.status(500).json({ error: "No user found " })
}
return res.status(200).json({ user })
} catch (error) {
return next(error)
}
},
getUser: async (req, res, next) => {
try {
const user = await User.findById(req.params.id)
if (!user) {
return res.status(404).json({ message: "User not found" })
}
return res.status(200).json({ user })
} catch (error) {
return next(error)
}
},
listUsers: async (req, res, next) => {
try {
const users = await User.find({})
if (!users) {
return res.status(404).json({ message: "No users found" })
}
return res.status(200).json({ users })
} catch (error) {
return next(error)
}
},
updateUser: async (req, res, next) => {
try {
const userData = {
username: req.body.username,
email: req.body.email,
password: req.body.password,
}
const user = await User.findByIdAndUpdate(req.params.id, userData, {
new: true,
})
if (!user) {
return res.status(400).json({ error: "No user found" })
}
return res.status(200).json({ user })
} catch (error) {
return next(error)
}
},
deleteUser: async (req, res, next) => {
try {
const user = await User.findByIdAndDelete(req.params.id)
if (!user) {
return res.status(200).json({ error: "No user found" })
}
return res.status(200).json({ user })
} catch (error) {
return next(error)
}
},
getUserPosts: async (req, res) => {
try {
const user = await User.findById(req.params.id).populate("posts")
if (!user) {
return res.status(400).json({ error: "No user" })
}
return res.status(200).json({ userPosts: user.posts })
} catch (error) {
console.log(error)
}
}
}
postController.js
const Post = require("../models/Post")
const User = require("../models/User")
const mongoose = require("mongoose")
module.exports = {
newPost: async (req, res, next) => {
try {
const postData = {
title: req.body.title,
description: req.body.description,
user: req.user.userId,
}
const post = await Post.create(postData)
if (!post) {
return res.status(404).json({ error: "No post found" })
}
const user = await User.findById(req.user.userId)
user.posts.push(post._id) //pushing post document's objectid to the user's posts array
user.save().then(() => {
return res.status(200).json({ user })
})
} catch (error) {
return next(error)
}
},
listPosts: async (req, res, next) => {
try {
const posts = await Post.find({}).populate("user")
if (!posts) {
return res.status(404).json({ error: "No posts found" })
}
return res.status(200).json({ posts })
} catch (err) {
return next(error)
}
},
getPost: async (req, res) => {
try {
const post = await Post.findById(req.params.id)
if (!post) {
return res.status(404).json({ message: "No post found " })
}
return res.status(200).json({ post })
} catch (error) {
return next(error)
}
},
editPost: async (req, res, next) => {
try {
const postData = {
title: req.body.title,
description: req.body.description,
}
const post = await Post.findByIdAndUpdate(req.params.id, postData, {
new: true,
})
if (!post) {
return res.status(404).json({ message: "No post found " })
}
return res.status(200).json({ post })
} catch (error) {
return next(error)
}
},
deletePost: async (req, res) => {
try {
const post = await Post.findByIdAndDelete(req.params.id)
if (!post) {
return res.status(200).json({ error: "No post found" })
}
await User.updateOne(
{ _id: mongoose.Types.ObjectId(post.user) },
{ $pull: { posts: mongoose.Types.ObjectId(post._id) } }
)
res.status(200).json({ post })
} catch (error) {
console.log(error)
}
},
}
```