I am building REST API using node, express and MongoDB(using mongoose) i want to add validation to post requests how can I do that I have defined schema like this
var CategorySchema = new Schema({
name: {
type: String,
lowercase: true,
default: '',
trim: true,
unique: [true, 'Category name already exists'],
required: [true, 'Category Name cannot be blank'],
minlength: [4, 'Minimum 4 characters required'],
maxlength: [20, 'Category name cannot be That long']
},
parentCategory: {
type: String,
lowercase: true,
default: '',
trim: true
},
description: {
type: String,
lowercase: true,
default: '',
trim: true,
required: [true, 'description cannot be blank'],
minlength: [10, 'Very short description']
},
slug: {
type: String,
lowercase: true,
unique: [true, 'Slug must be unique'],
required: true,
minlength: [4, "Minimum 4 Charater required"],
maxlength: [20, "Slug cannot be that long"]
},
imageUrl: {
type: String,
default: '',
trim: true
},
created: {
type: Date,
default: Date.now
},
updated: {
type: Date
}
});
module.exports = mongoose.model('Category', CategorySchema);
i am insert data using mongoose models like this
exports.createCategory = function (request, response) {
var newCategory = {
"name": request.body.categoryName,
"parentCategory": request.body.parentCategory,
"description": request.body.description,
"slug": request.body.slug,
"imageUrl": request.body.categoryImage,
"updated": new Date()
}
var category = new Category(newCategory);
category.save()
.then(function (category) {
sendResponse(response, 201, "success", category);
})
.catch(function (error) {
sendResponse(response, 400, "error", error);
});
};
but I want to add validation to the post request. I have to make sure that fields that are defined in a database are there in a request and values must be required as well I am really confused how to validate key in a JSON object inside request body. I have already added some validation using mongoose.
You can use Middlewares for this purpose like (If you are using express framework) :
app.use(function (req, res, next) {
var validationErrors = [];
validationErrors = some_function_to_validate(req); // Returns array
if(validationErrors.length > 0) {
// Send Custom Response with Validation Error
}
else {
next();
}
});
Note : This middleware will be executed for all of your requests (If added before all the routes registration).
For more please refer : http://expressjs.com/en/guide/using-middleware.html
Try following code to get the valid fields. It will return false if any field i.e. not required is coming with the req. Hope this will help.
function validateReq(req)
{
if(req)
{
var prop = ['name','parentCategory','description'] //Add more property name here
var found = false;
for(var key in req.body)
{
if (prop[key] && (prop[key] !== null))
{
found = true;
}
else
{
return false;
}
}
}
else
{
return false;
}
}
exports.createCategory = function (request, response) {
var valid = validateReq(request);
alert(valid);
if(valid){
var newCategory = {
"name": request.body.categoryName,
"parentCategory": request.body.parentCategory,
"description": request.body.description,
"slug": request.body.slug,
"imageUrl": request.body.categoryImage,
"updated": new Date()
}
var category = new Category(newCategory);
category.save()
.then(function (category) {
sendResponse(response, 201, "success", category);
})
.catch(function (error) {
sendResponse(response, 400, "error", error);
});
}
else
{
//Error handling code
}
};
My answer seems to be too late, but hopefully it will help others in future. I think you can try express-validator, here is an article explains how to use it in detail.
Its basic idea is to add a middleware, and put all the validations inside, which can be invoked in subsequent route functions. This way can keep the business logic code clean.
below is an example from official docs
// ...rest of the initial code omitted for simplicity.
const { check, validationResult } = require('express-validator');
app.post('/user', [
// username must be an email
check('username').isEmail(),
// password must be at least 5 chars long
check('password').isLength({ min: 5 })
], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
User.create({
username: req.body.username,
password: req.body.password
}).then(user => res.json(user));
});
Related
I need to save an array of sub-documents (Categories) on each Item, however. The problem relates to processing an array passed in a request.
The main issue I have is how to successfully save the array of categories in the item.
I have two models in MongoDB, namely:
Item
Category
The Item schema:
const Item = mongoose.model(
'Items',
new mongoose.Schema({
name: {
type: String,
required: true,
trim: true,
minlength: 2,
maxlength: 255
},
category: [categorySchema],
numInStock: {
type: Number,
required: true,
min: 0,
max: 255
},
unitPrice: {
type: Number,
required: true,
min: 0,
max: 255
}
})
);
The Category schema:
const categorySchema = new mongoose.Schema({
name: {
type: String,
required: true,
minlength: 2,
maxlength: 50
},
user_id: {
type: ObjectId,
required: false
}
});
What I have so far for the Express route for POST to Items:
router.post('/', [auth], async (req, res) => {
const { error } = validate(req.body);
if (error) return res.status(400).send({ data: error.details[0].message });
const category = await Category.findById(req.body.categoryId);
if (!category) return res.status(400).send({ data: 'Invalid category.' });
console.log('LOG: categoryIds: ', req.body.categoryId[0]);
const item = new Item({
name: req.body.name,
category: [
{
_id: req.body.categoryId._id,
name: req.body.categoryId.name
}
],
numInStock: req.body.numInStock,
unitPrice: req.body.unitPrice,
publishDate: moment().toJSON()
});
await item.save();
res.send({ data: item });
});
The body of the POST looks like:
{
"name": "AnotherNew item",
"categoryId": [{"name": "Vegetables", "_id": "5de43de8805f4fac35f691c0"},{"name": "Seafood", "_id": "5de43de8805f4fac35f691c4"}],
"numInStock": 0,
"unitPrice": 0
}
The error I receive:
error: Items validation failed: category.0.name: Path `name` is required. message=Items validation failed: category.0.name: Path `name` is required., message=Path `name` is required., name=ValidatorError, validator=function(v) {
const cachedRequired = get(this, '$__.cachedRequired');
// no validation when this path wasn't selected in the query.
if (cachedRequired != null && !this.isSelected(_this.path) && !this.isModified(_this.path)) {
return true;
}
// `$cachedRequired` gets set in `_evaluateRequiredFunctions()` so we
// don't call required functions multiple times in one validate call
// See gh-6801
if (cachedRequired != null && _this.path in cachedRequired) {
const res = cachedRequired[_this.path] ?
_this.checkRequired(v, this) :
true;
delete cachedRequired[_this.path];
return res;
} else if (typeof required === 'function') {
return required.apply(this) ? _this.checkRequired(v, this) : true;
}
return _this.checkRequired(v, this);
}, message=Path `name` is required., type=required, path=name, value=undefined, kind=required, path=name, value=undefined, reason=undefined, _message=Items validation failed, stack=ValidationError: Items validation failed: category.0.name: Path `name` is required
I solved this by passing the array req.body.categoryId from the request to category on the new instance of Item.
I appreciate any feedback on my changes.
My changes to Item POST route are as follows:
router.post('/', [auth], async (req, res) => {
const { error } = validate(req.body);
if (error) return res.status(400).send({ data: error.details[0].message });
const category = await Category.findById(req.body.categoryId);
if (!category) return res.status(400).send({ data: 'Invalid category.' });
console.log('LOG: categoryIds: ', req.body.categoryId);
const item = new Item({
name: req.body.name,
category: req.body.categoryId,
numInStock: req.body.numInStock,
unitPrice: req.body.unitPrice,
publishDate: moment().toJSON()
});
await item.save();
res.send({ data: item });
});
I'm trying to build a user model, but I want to make sure that username and email are unique. When I created the first user everything was ok, but when I try to create the second user with the same information, I got the some error that I can handle in when I will save, but the duplicate key wasn't there to handle it.
This is my schema file code:
const UserSchema = new Schema({
// this username with SchemaType of string
username: {
type: String,
lowercase: true,
required: [true, "username is required"],
unique: true,
trim: true,
minlength: [4, "try to user longer name"],
maxlength: [60, "your name is way too long"],
},
// virtual name
name: {
// name have two properties
// first is first and refer to first-name
// second is last and refer to last-name
first: {
type: String,
lowercase: true,
trim: true,
minlength: 4,
maxlength: 20
},
last: {
type: String,
lowercase: true,
trim: true,
minlength: 4,
maxlength: 20
}
},
password: {
type: String,
required: [true, "password is required"]
},
email: {
type: String,
required: [true, "email is required"],
unique: true
},
active: {
type: Boolean,
default: true
},
admin: {
type: Boolean,
default: false
},
meta: {
update: {
type: Date,
default: Date.now()
},
timestamp: {
type: Date,
default: Date.now()
}
}
});
UserSchema.virtual("fullname").get(function () {
// return the concatenation of first and last
return this.name.first + " " + this.name.last;
});
// Create User Model
const User = mongoose.model("User", UserSchema);
module.exports = User;
And this is my router code where I tried to handle it:
router.post("/register", (request, response) => {
const user = {
username: request.body.username,
email: request.body.email,
password: request.body.password
};
if (!user.email && !user.username && !user.password) {
return response.json({
"message": "please fill the whole information"
});
}
// put user info in model
const newUser = new User({
username: user.username,
email: user.email,
password: user.password
})
newUser.validate((err) => {
console.log(err);
});
// save User in model
newUser.save()
// return response with info
return response.status(201).json(user);
})
I think the explanation here is quite a simple one. You are specifying the unique attribute in your schema for multiple fields, so mongo will not allow you to create multiple entries with the same information. This is quite obvious.
Also, I noticed a bit of irregularity in your code. The save method you are calling returns a promise, which means the event loop will not block your code and the response will be returned immediately. For this, you either need to handle your response inside the then block or use async await throughout your code.
I would suggest the following changes:
router.post("/register", (request, response) => {
const user = {
username: request.body.username,
email: request.body.email,
password: request.body.password
};
if (!user.email && !user.username && !user.password) {
return response.json({
"message": "please fill the whole information"
});
}
// put user info in model
const newUser = new User({
username: user.username,
email: user.email,
password: user.password
})
newUser.validate((err) => {
if(err) {
response.status(403).json({ message: 'Your custom error message' });
}
newUser.save().then(res => {
return response.status(201).json(user);
}).catch(e => {
return response.status(500).json({ message: 'Your custom error message' });
})
});
})
I have the following Mongoose Schema:
const UserSchema = new Schema({
email: {
type: String,
minlength: 1,
required: true,
trim: true,
unique: true,
validate: {
validator: isEmail,
message: '{VALUE} is not a valid email.',
},
},
emailPhrase: {
type: String,
},
tokens: [
{
access: {
type: String,
required: true,
},
token: {
type: String,
required: true,
},
},
],
});
And the following pre-hook:
UserSchema.pre('save', function (next) {
const user = this;
if (!user.toObject().tokens[0].token) {
// do something
next();
} else {
// do something else
next();
}
});
The issue is, even when the tokens property is completely empty, the first case (do something) doesn't run. What am I doing wrong here?
You need to change your if in the pre hook method:
UserSchema.pre('save', function (next) {
const user = this;
if (user.toObject().tokens && user.toObject().tokens.length > 0) {
console.log('do something');
next();
} else {
console.log(' do something else');
next();
}
});
There are a lot of ways for validate a array, here one link with some options:
Check is Array Null or Empty
I'm a newbie in to node.js and mongodb. Im using express for rest-api.
I'm trying to create an API where user can post and like. I'm able to create post API and the issue is with like API where the user can click the like button and it will be counted and when he clicks the like button again it will take as unlike and update the same in the backend server (express and mongodb) in my case.
Schema Definition
module.exports = mongoose.model('posts', new Schema({
post: { type: String, required: true },
date: { type: Date, required: true, default: moment.now() },
author: { type: String, required: true },
authorId: {type: String, required: true},
likes: {type: Object, required: true, default: []},
likesCount: { type: Number, required: true, default: 0},
dislikes: { type: Object, required: true, default: [] },
dislikesCount: { type: Number, required: true, default: 0 },
comments: { type: Object, required: true, default: [] },
commentsCount: { type: Number, required: true, default: 0 }
}, { timestamps : { createdAt: 'created_at' , updatedAt: 'updated_at' } }));
Code:
router.post('/post/likes', function(req, res) {
if (!_.isEmpty(req.body.postId) && !_.isEmpty(req.body.user) && !_.isEmpty(req.body.userId)) {
//Finding the post by Id
Posts.findById(req.body.postId, function(err, posts) {
if (err) {
return res.send(500).json({
success: false,
msg: err
});
}
if (posts !== null) {
var user = {
userId: req.body.userId,
user: req.body.user
};
//Returns the matched obj if true
//Returns undefined if false
var alreadyLiked = _.find(posts.likes, function(obj) {
if (obj.userId === user.userId && obj.user === user.user) {
return true;
} else {
return false;
}
});
if (alreadyLiked === undefined) {
posts.likes.push(user);
posts.likesCount = posts.likes.length;
var updatedPost = new Posts(posts);
updatedPost.save(function(err, data) {
if (err) {
return err;
}
res.status(200).send({
success: true,
data: data,
message: 'You have liked the post!'
});
});
} else {
//Removing the already liked user object from posts.likes
posts.likes = _.without(posts.likes, _.findWhere(posts.likes, user));
posts.likesCount = posts.likes.length;
posts.markModified('likesCount');
var reupdated = new Posts(posts);
reupdated.save(function(err, data) {
if (err) {
return err;
}
res.status(200).send({
success: true,
data: data,
message: 'You have unliked the post!'
});
});
}
} else {
res.status(200).send({
success: false,
message: 'No post found!'
});
}
});
} else {
res.status(400).send({
success: false,
message: 'Bad request value'
});
}
});
The issue is when I like the post for first time it works perfect and returns the expected response.
img: liked-response-img
when i unlike the post by calling the same API it returns some unexpected result. The user who unliked is removed from the likes property but the count still remains '1' as shown in img below. I can't figure out why? Can someone please point out where and what I'm doing wrong. Thanks in advance!
img: unliked-response-img
how about just making posts.save() or posts.save().exec() instead of creating a new "copied" post and saving that.
I have built a mean app but am having an issue with it posting a number value. I'm not sure if it is a mongoose validation error but for some reason mongoose can not upsert the number value but will when it is a string.
Here's the route:
//Edit A Site
router.put('/api/sites/:site_id', function(req, res) {
Site.findById(req.params.site_id, function(err, site) {
if (err) {
res.send(err);
} else {
if(req.body.ip) site.ip = req.body.ip;
if(req.body.domain) site.domain = req.body.domain;
if(req.body.wp) site.wp = req.body.wp;
if(req.body.host_name) site.host_name = req.body.host_name;
if(req.body.hosted) site.hosted = req.body.hosted;
console.log(req.body);
// save the site
site.save(function(err) {
if (err)
res.send(err);
res.json(site);
});
}
});
});
The console.log has the full request body:
{ hosted: 1, host_name: 'sup', wp: 'n/a' }
But this is the mongoose response: Mongoose: sites.update({ _id: ObjectId("57a16c4a7f7e5b7a7e1f5ad1") }, { '$set': { host_name: 'sup', wp: 'n/a' } })
Schema:
// grab the things we need
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
// create a schema
var sitesEntrySchema = new Schema({
ip: {
type: String,
required: true,
trim: true
},
domain: {
type: String,
required: true,
trim: true
},
wp: {
type: String,
required: true,
trim: true
},
host_name: {
type: String,
required: true
},
hosted: {
type: Number,
min: 0,
max: 1,
required: true
}
});
// make this available to our users in our Node applications
var Site = mongoose.model('Site', sitesEntrySchema);
module.exports = Site;
EDIT:
I believe I found the solution. When checking for the req.body.hosted, because it is a number it fails. I had to update to check for undefined:
if(req.body.hosted != undefined) site.hosted = req.body.hosted;