Best practice for validating input in Express/Mongoose app (controllers and models) - node.js

I'm new to node/express/mongoose and backend in general, and I'm building an API from the ground up.
I split my code into controllers and DAOs (mongoose models) and I'm not sure where validation should be taking place, what exactly should the controller and model each be doing?
For example, for the route GET /users/:id, I want it to:
Return 400 if the id given is not a valid ObjectId
Return 404 if the id is a valid ObjectId, but no documents exist with this id
Return 200 if a document is found, and remove some fields (password, __v, and _id (because I made a virtual field "id" without underscore)) before sending the response
Return 500 otherwise
Here's what I tried. It's currently doing everything I want above, but I'm not sure if it's the best implementation:
users.controller.js
const UserModel = require('../models/users.model')
const mongoose = require('mongoose')
exports.getById = async (req, res) => {
// Check that the id is a valid ObjectId
if (!mongoose.Types.ObjectId.isValid(req.params.id)) {
return res.status(400).json({ error: 'Invalid ObjectID' })
}
try {
const user = await UserModel.findById(req.params.id)
if (!user) return res.status(404).json({ error: 'No user with this ID' })
res.status(200).send(user)
} catch (err) {
res.status(500).send(err)
}
}
users.model.js
exports.findById = async (id) => {
let user = await User.findById(id)
if (!user) {
return null
}
user = user.toJSON()
delete user._id
delete user.__v
delete user.password
return user
}
Is this the best way to structure things? Please critique and suggest any improvements and best practices.

I prefer to do all validation and logic in Controllers file and helpers classes in separate small js files.
Also trying to keep the models as lean as possible. Actually you can create different middleware functions to validate some inputs.
It is also helpful to create ErrorResponse middleware so you can do such simple and easy to read validations as:
if (!userFromDB) throw new ErrorResponse({code: 404, msg: "User doesn't exist"})
than catch it and handle in separate file with different options of the answer.
Backend has 100+ ways of implementing it. You need to choose you own 😁

Related

Mongoose: how to add new schema only if field does not exist?

I need help with my code.
Trying to figure what is the best way to give some errors if specific fields already exist.
Add if statement maybe on the client side if the user trying to save with a name that already exists.
Or add a statement on the server side, something I'm trying to do right now...
Here is my app.post code;
app.post('/save-file', async(req, res) => {
// Test nr3;
const saveExists = await savefile.exists({
saveFileID: req.body.saveFileID
})
if (saveExists) console.log("Save file exists")
// Note: This will log if field exist. But I need to stop somehow...
// My original code;
const SaveDB = new savefile(req.body)
try {
await SaveDB.save()
res.status(201).json({
status: 'Success',
data: {
SaveDB
}
})
} catch (error) {
res.status(500).json({
status: 'Failed',
message: error
})
}
})
Example, if my "savefiles" on my mongoDB have field --> saveFileID = "game01"
and the user writes that name, then that code should stop and give some error,
else add a new schema.

MongoDB: findOne not working as expected?

I'm creating an API to my database of coins and I don't want to add duplicate coins to my database so I've created this line of code in my function
const addCoin = async (req, res) => {
const { coinId, seconds, bidAmount } = req.body;
console.log(coinId);
const coinExists = await Coin.findOne({ coinId });
console.log(coinExists);
if (coinExists) {
res.status(400).send({ message: 'Coin Exists Bad Request' }); // bad request
throw new Error('Coin already exists');
}
In my Postman I am inputting a different coinId in the request body and I am getting the error that Coin Exists?
Is findOne here not working as expected?
Confused.
Thanks
Ahh after some documentation reading
I have to specify which property in the Coin Model I want it to find specifically
so in this case
I did
Coin.find({ itemId: coinId})
Since in my model its defined as itemId but user input is coinId

FindOne NODEJS mongoDB

I have this code that check for me if the token provided is valid or not, the code is working but the problem that it is returning wrong id, => it returns the first Id that he found in the database
the code that I'm using is this
const checkToken = async (req, res ,next) => {
const token= req.body.token //the token is mix of number and letters lenght (6)
User.findOne(token,
(err, user) => {
if (err) {
const error = new Error('Token not Found');
error.status= 406;
return next(err, error);
}else
{
res.send('/api/users/'+ user.id +'/update') // u need to mention user.id from DB
}
})
this is my image of the database :
I don't want to use the id to search the token , what I want is use the provided token and search in DB if it is found so I retrieve Id
According to MongoDB documentation that is the usual behavior of findOne method. When multiple documents satisfy the condition the function returns the first document that is written first. Reference
To check use find() and see all the documents it returns for the correct id and to use findOne make sure the token is unique for all the document.

Sequelize - Creating Multiple Tables Using Id of First Table Created

I'm doing a project using Express, & Sequelize and have run into an issue:
I'm trying to create an api route that, on a button click, will create a row in my 'member' & 'memberinstrument' tables. Those tables have an association in my models that: member 'hasMany' memberinstruments & memberinstruments 'belongsTo' member.
This is what I have right now:
router.post("/api/individual/signup", async (req, res) => {
try {
const member = await db.Member.create({
memberName: req.body.memberName,
location: `${req.body.city}, ${req.body.state}`,
profilePicture: req.body.profilePicture,
UserId: req.user.id
})
const memberInstrument = await db.MemberInstrument.create({
instrument: req.body.instrument,
MemberId: member.id
});
res.json({ member, memberInstrument });
} catch (error) {
console.log(error.message);
res.status(500).send('Sever Error');
}
})
I'm testing it out in postman and it simply says I have a 'server error' (which doesn't happen if I delete the whole memberInstrument posting section) and in node I get "Cannot read property 'id' of undefined". I know it must have something to do with the timing of trying to create member, and then trying to get that member id for memberinstrument, but I can't figure out how to resolve this.
Any help would be much appreciated!
(edit:
I commented out both UserId & MemberId and it successfully posts.
uncommenting just UserId creates the same error:
UserId is a nullable field so I don't know why it's doing a server error if I don't define it (or maybe I have to define it as null but I do not know how to do that in postman since it's coming from .user instead of .body
uncommenting just MemberId creates the same error)
Since I'm doing this in Postman, and don't know how to send a req.body with that, I was getting that id error. I changed req.user.id to req.body.id which will be populated with information from a front end state and it is now working correctly.
I think it's unnecessary for you to include all the fields in your .create()
try this
router.post("/api/individual/signup", async (req, res) => {
try {
const data = req.body;
const member = await db.Member.create(data);
const [registration, created] = member;
if(created){
//insert more of your code here on creating **MemberInstrument**, this is just a sample.
await db.MemberInstrument.create(registration.member_id);
}
res.json({
registration,
created
});
} catch (error) {
console.log(error);
res.status(500);
}
}
On your postman request, you will input the body or fields in json format, except the ID, IDs should not be included on the postman body.

Data is mutated but not updated in the database

I have a sever connected to a mongodb database. When I add a first level data and then save that, it works.
For example :
// this works fine
router.post('/user/addsomedata', async (req,res)=>{
try {
const user = await User.findOne({email : req.body.email})
user.username = req.body.username
await user.save()
res.send()
} catch(e) {
res.status(404).send(e)
}
})
BUT if I try to save the object with deeper level data, it's not getting saved. I guess the update is not detected and hence the user didn't get replaced.
Example :
router.post('/user/addtask', auth ,async (req,res)=>{
const task = new Task({
name : req.body.name,
timing : new Date(),
state : false,
})
try {
const day = await req.user.days.find((day)=> day.day == req.body.day)
// day is found with no problem
req.user.days[req.user.days.indexOf(day)].tasks.push(task)
// console.log(req.user) returns exactly the expected results
await req.user.save(function(error,res){
console.log(res)
// console.log(res) returns exactly the expected results with the data filled
// and the tasks array is populated
// but on the database there is nothing
})
res.status(201).send(req.user)
} catch(e) {
res.status(400).send(e)
}
})
So I get the tasks array populated on the console even after the save callback but nothing on the db image showing empty tasks array
You're working on the user from the request, while you should first find the user from the DB like in your first example (User.findOne) and then update and save that model.
Use .lean() with your find queries whenever you are about to update the results returned by mongoose. Mongoose by default return instance objects which are immutable by nature. lean() method with find returns normal js objects which can be modified/updated.
eg. of using lean()
const user = await User.findOne({email : req.body.email}).lean();
You can read more about lean here
Hope this helps :)

Resources