Best way to validate request params sails.js / node.js - javascript

Currently I'm implementing an application using Sails.JS. Every time REST Request hit's my sails controller, I'm manually (if else checks) checking validations like if variable exist or not / if its valid data type or not.
Is there any standard way (custom middle ware to validate each request against pre defined JSON objects?) to validate parameters as its over heading my controller logic.
How is it handled in other programming languages / frameworks in production use?
Thanks in advance.
Prasad.CH

Sails.js is built on top of Express, so it seems you're looking for something like express-validator that is an express middleware. You can also use it along with the Sails policies, as waza007 suggested.

The best solution for me is https://github.com/epoberezkin/ajv
It's a JSON schema validator
Short example:
var Ajv = require('ajv');
var ajv = Ajv();
var schema = {
"type": "object",
"properties": {
"foo": { "type": "number" },
"bar": { "type": "string" }
},
"required": [ "foo", "bar" ]
};
var data = { "foo": 1 };
var valid = ajv.validate(schema, data);
if (!valid) {
return res.send(400, ajv.errorsText(), ajv.errors);
}

express-validator is one option, but I prefer to do most of my validation directly in my ODM (Mongoose). For example:
var mongoose = require('mongoose');
var validate = require('mongoose-validator');
var nameValidator = [
validate({
validator: 'isLength',
arguments: [3, 50],
message: 'Name should be between {ARGS[0]} and {ARGS[1]} characters',
type: 'isLength'
}),
validate({
validator: 'isAscii',
passIfEmpty: true,
message: 'Name should contain Ascii characters only',
type: 'isAscii'
})
];
var todoSchema = mongoose.Schema({
name: {
type: String,
required: true,
validate: nameValidator
},
completed: {type: Boolean, required: true}
});
Then you can handle the error object such a way:
router.post('/newTodo', handlers.newTodo, validationMonad(handlers.getTodos));
If Mongoose returns a validation error when creating the new document in handlers.newTodo, then it is passed to the validation handler which in turn will parse it, append the parsed error to the response and call the getTodos handler, which will show the errors in res.errors.
exports.validationMonad = function (reqHandler) {
return function (err, req, res, next) {
if (!res.errors) res.errors = [];
switch (err.name) {
case 'ValidationError':
for (field in err.errors) {
switch (err.errors[field].kind) {
case 'required':
res.errors.push({
name: err.errors[field].name,
message: "The field '" + err.errors[field].path + "' cannot be empty.",
kind: err.errors[field].kind,
path: err.errors[field].path
});
break;
default:
res.errors.push({
name: err.errors[field].name,
message: err.errors[field].message,
kind: err.errors[field].kind,
path: err.errors[field].path
});
}
}
if (reqHandler) {
reqHandler(req, res, next);
} else {
// JSON
res.json(res.errors[0])
}
break;
case 'CastError':
// Supplied ID is not an ObjectID
res.errors.push({
name: err.name,
message: err.message,
kind: err.kind,
path: err.path
});
if (reqHandler) {
reqHandler(req, res, next);
} else {
// JSON
res.json(res.errors)
}
break;
default:
return next(err)
}
}
};

Related

How to display Sequelize validation error messages in Express API

I have this Organization model used in a Node.js/Express API with Sequelize ORM running MySQL. When I violate the 2-100 character rule under validation in the first code example below I get the classic err item from the catch block in the second code example, which doesn't contain any information about the validation error.
I would like instead to display the validation error message you can see under validation { len: { msg: ...}} in the model. At least console.log it, and then later display it to the end user.
However, the Sequelize manual and any other information I can find don't explain how I make use of this custom error message. So my question is how can I make use of it and display it.
Model:
'use strict'
const { Sequelize, DataTypes } = require('sequelize');
const db = require('./../config/db.js')
const Organization = db.define('organizations', {
id: {
type: DataTypes.UUID,
defaultValue: Sequelize.UUIDV4,
primaryKey: true,
allowNull: false,
unique: true,
validate: {
isUUID: {
args: 4,
msg: 'The ID must be a UUID4 string'
}
}
},
name: {
type: DataTypes.STRING,
required: true,
allowNull: false,
validate: {
len: {
args: [2, 100],
msg: 'The name must contain between 2 and 100 characters.' // Error message I want to display
}
}
},
created_at: {
type: DataTypes.DATE,
required: true,
allowNull: false
},
updated_at: {
type: DataTypes.DATE,
required: true,
allowNull: false
},
deleted_at: {
type: DataTypes.DATE
}
},
{
underscored: true,
paranoid: true,
tableName: 'organizations',
updatedAt: 'updated_at',
createdAt: 'created_at',
deletedAt: 'deleted_at'
})
module.exports = Organization
Controller:
/**
* #description Create new organization
* #route POST /api/v1/organizations
*/
exports.createOrganization = async (req, res, next) => {
try {
const org = await Organization.create(
req.body,
{
fields: ['name', 'type']
})
return res.status(200).json({
success: true,
data: {
id: org.id,
name: org.name
},
msg: `${org.name} has been successfully created.`
})
} catch (err) {
next(new ErrorResponse(`Sorry, could not save the new organization`, 404))
// ^ This is the message I get if I violate the validation rule ^
}
}
The Sequelize documentation for validation and constraints is found here: https://sequelize.org/master/manual/validations-and-constraints.html
The validation is built on Validatorjs (https://github.com/validatorjs/validator.js) which unfortunately also lacks practical info on the use of the validation object. I guess that means it must be self explanatory, but as I'm a noob I am lost.
I tried your same validation on my local project on firstName field and I could get sequelize error like this
console.log('err.name', err.name);
console.log('err.message', err.message);
console.log('err.errors', err.errors);
err.errors.map(e => console.log(e.message)) // The name must contain between 2 and 100 characters.
as you can see you can check if err.name is SequelizeValidationError and then loop over err.errors array and get message for field on path and rest other properties are also there.
Error Display example:
const errObj = {};
err.errors.map( er => {
errObj[er.path] = er.message;
})
console.log(errObj);
you'll get an object like
{
firstName: 'The firstName must contain between 2 and 100 characters.',
lastName: 'The lastName must contain between 2 and 100 characters.'
}
After getting help from #Rohit Ambre my catch block is like this:
} catch (err) {
if (err.name === 'SequelizeValidationError') {
return res.status(400).json({
success: false,
msg: err.errors.map(e => e.message)
})
} else {
next(new ErrorResponse(`Sorry, could not save ${req.body.name}`, 404))
}
}
API response:
{
"success": false,
"msg": [
"The name must contain between 2 and 100 characters.",
"The organization type is not valid."
]
}
It would maybe helpful to add a key for each item, something like this, but whatever I do in the catch block it seems to give me an UnhandledPromiseRejectionWarning:
catch (err) {
if (err.name === 'SequelizeValidationError') {
const errors = err.errors
const errorList = errors.map(e => {
let obj = {}
obj[e] = e.message
return obj;
})
return res.status(400).json({
success: false,
msg: errorList
})
} else {
next(new ErrorResponse(`Sorry, could not save ${req.body.name}`, 404))
}
}
Any tips, por favor?
you can execute a simple flag --debug in the migration
npx sequelize-cli db:migrate --debug
backend
response.err(res, err.errors[0].message ?? 'tidak berhasil menambahkan data', 500);
frontend
.catch(error => {
....
this.fetchLoading = false
this.$swal({
title: 'Error!',
text: `${error.response.data.message ?? 'except your message'}`,
"sequelize": "^6.6.5",
"sequelize-cli": "^6.2.0",

Submitting multiple instances of data in ONE post request in Mongo

I have this module I'm working on, its a post form that takes data for an expense (name, date, amount, an image, and description). I've figured out how to get the post data into mongo for ONE row of data, but I want a user to be able to submit multiple rows (i.e multiple expenses). Unfortunately, when I try to submit the form with multiple instances of this data, I get an error!
ERROR: Expense validation failed: date: Cast to Date failed for value "[ '2018-07-01', '2018-07-09' ]" at path "date"
Any thoughts on how to correctly submit multiple rows of the aforementioned data?
INFO:
I'm using express for the server
MongoDB for database
ExpenseForm.js --> (using pug template engine)
button#submitExpense(type='submit' form='addExpenseForm').btn Submit
// ADD EXPENSE FORM
form#addExpenseForm(method='POST' action='/expenses').row
div.col-md-12.add-expense-row-wrapper
div#expenseRow.row.form-group
input.form-control.col-md-2(type='text' name='name' placeholder='Enter Name*')
input.form-control.col-md-2(type='date' name='date')
input.form-control.col-md-2(type='number' name='amount' placeholder='Enter Amount*')
input.form-control.col-md-2(type='text' name='description' placeholder='Description*')
input.col-md-3(type='file' name='file' id='files' placeholder='Upload receipt' multiple)
button#deleteExpenseRow.col-md-1.btn.delete-expense-row(type='button' )
i.fas.fa-trash-alt
div.row.add-expense-button-wrapper
button#addExpenseRow.btn(type='button')
i.far.fa-plus-square
Also forgot my Schema
expense.js -->
const ExpenseSchema = new mongoose.Schema({
employee: {
type: String,
required: true,
trim: true
},
date: {
type: Date,
required: true,
default: Date.now
},
amount: {
type: Number,
required: true,
validate: {
validator: Number.isInteger,
message: '{VALUE} is not an integer value'
}
},
description: {
type: String,
required: true
},
file: {
type: Buffer,
required: true
}
});
var Expense = mongoose.model('Expense', ExpenseSchema);
module.exports = Expense;
AND THE POST ROUTE
index.js -->
// POST /expenses
router.post('/expenses', function(req, res, next) {
// Expense.getExpenses({
// name: req.body.name,
// date: req.body.date,
// amount: req.body.amount,
// description: req.body.description,
// file: req.body.file
// }, 10, function(err, post) {
// if (err) {
// return next(err);
// }
// console.log(req.body);
// res.json(post);
// });
// create object with form input
var expenseData = {
employee: req.body.name,
date: req.body.date,
amount: req.body.amount,
description: req.body.description,
file: req.body.file
};
// store data from from into mongo
Expense.create(expenseData, function(error) {
if (error) {
return next (error);
} else {
console.log(expenseData);
return res.redirect('/dashboard');
}
});
});
So, data will come as array. name will contain array of all names and so on. You can iterate over all and save in database and then return after saving last item.
var employee = req.body.name;
var date = req.body.date;
var amount = req.body.amount;
var description = req.body.description;
var file = req.body.file;
var expenseData = {};
employee.forEach(function(element, index, array) {
expenseData = {
employee: employee[index],
date: date[index],
amount: amount[index],
description: description[index],
file: file[index]
};
Expense.create(expenseData, function(error) {
if (error) {
return next(error);
} else {
if (index === employee.length - 1) {
return res.redirect('/dashboard');
}
}
});
});
The steps are as follows:
You need indeed to collect your data in an array - something that will look like this: expenseArr = [{row1Data}, {row2Data}, ...].
Inside your post route you need to import your expenses model - something that will look like this: const expenses = require('thePathAndNameOfYourSchemaExportFile')
Call expenses.insertMany(expenseArr) method inside your post route and mongoose will do what's necessary. In this last step, don't forget that you need to wait for the insertMany to complete (recommended that you have a callback in case something goes wrong).

fastify and ajv schema validation

I am trying to validate the querystring parameter 'hccid' as shown below. Seems like the validation is not working for me. Can some one see what I am missing?
const fastify = require('fastify')({
ajv: {
removeAdditional: true,
useDefaults: true,
coerceTypes: true
}
});
const schema = {
querystring: {
hccid: { type: 'string' }
}
};
// Declare a route
fastify.get('/hello', {schema}, function (request, reply) {
const hccid = request.query.hccid;
reply.send({ hello: 'world' })
});
// Run the server!
fastify.listen(3000, function (err) {
if (err) throw err
console.log(`server listening on ${fastify.server.address().port}`)
});
So with that code, I should get a schema validation exception when I call the service with a total new queryparam abc just like I shown below
http://localhost:3000/hello?abc=1
but there was no error. I got the response back {"hello":"world"}
I also tried removing the queryparam all together http://localhost:3000/hello
and I still got {"hello":"world"}
so obviously the validation is not working. What is missing in my code? any help would be appreciated.
this schema structure solved my problem. Just in case if someone wants to check it out if they run into similar issue.
const querySchema = {
schema: {
querystring: {
type: 'object',
properties: {
hccid: {
type: 'string'
}
},
required: ['hccid']
}
}
}
According to the docs, you can use the querystring or query to validate the query string and params to validate the route params.
Params would be:
/api/garage/:id where id is the param accessible request.params.id
/api/garage/:plate where plate is the param accessible at request.params.plate
Example for param validation would be:
const getItems = (req, reply) => {
const { plate } = req.params;
delete req.params.plate;
reply.send(plate);
};
const getItemsOpts = {
schema: {
description: "Get information about a particular vehicle present in garage.",
params: {
type: "object",
properties: {
plate: {
type: "string",
pattern: "^[A-Za-z0-9]{7}$",
},
},
},
response: {
200: {
type: "array",
},
},
},
handler: getItems,
};
fastify.get("/garage/:plate", getItemsOpts);
done();
Query / Querystring would be:
/api/garage/id?color=white&size=small where color and size are the two querystrings accessible at request.query.color or request.query.size .
Please refer to the above answer as an example fro query validation.
Validation
The route validation internally relies upon Ajv v6 which is a
high-performance JSON Schema validator. Validating the input is very
easy: just add the fields that you need inside the route schema, and
you are done!
The supported validations are:
body: validates the body of the request if it is a POST, PUT, or PATCH method.
querystring or query: validates the query string.
params: validates the route params.
headers: validates the request headers.
[1] Fastify Validation: https://www.fastify.io/docs/latest/Validation-and-Serialization/#validation
[2] Ajv#v6: https://www.npmjs.com/package/ajv/v/6.12.6
[3] Fastify Request: https://www.fastify.io/docs/latest/Request/

Adding JSON array to a Mongoose schema (JavaScript)

I'm creating an Android App (A baseball app) where I'm using MongoDB to store my data. the way I want my JSON data to be stored into the database is like this.
{"email#domain.com":{
"coachName": "Smith"
players:[
player1:{
"throws_":"n\/a",
"position":"position not set",
"number":"1",
"playerNum":"H8E83NxRo6",
"bats":"n\/a",
"team_name":"Team",
"name":"Name"}
player2:{
"throws_":"n\/a",
"position":"position not set",
"number":"1",
"playerNum":"H8E83NxRo6",
"bats":"n\/a",
"team_name":"Team",
"name":"Name"}
]
}
sorry if there is any syntax error, but essentially that is the layout i want for the JSON. Where the mongo page "id" is the persons email. and where "players" is an array of the list of players the coach has.
My question is, how can I
properly setup the Mongoose schema to look like this?
when the app sends the JSON data, how can I parse it to store the data?
and if possible (ill try and figure this part on my own if no one can) if multiple players are being added at once, how can i store them if there's already players in the array?
All of this is backend/server side, I have the android working properly, i just need help with storing it to look like this in JavaScript.
You dont want to use a dynamic variable as a field name. I'm talking about the email you have "email#domain.com". This wouldn't be good because how will you find the object. Its not common to search for object by there fields, you use the field name to describe what it is your looking for. You will need 2 models. One for player and one for coach. Coach refrences a Player in its Players array field.
If you format your JSON correctly you wont have to do any parsing, just make sure the JSON you are sending is correctly formatted.
Look at the addPlayer function.
Controller file (Answer for questions 2 and 3)
create = function(req, res) {
var coach = new Coach(req.body);
coach.user = req.user;
coach.save(function(err) {
if (err) {
return res.status(400).send({
// Put your error message here
});
} else {
res.json(coach);
}
});
};
read = function(req, res) {
res.json(req.coach);
};
addPlayer = function(req, res) {
var coach = req.coach;
console.log('updating coach', req.coach);
var player = new Player(req.newPlayer);
coach.players.push(newPlayer);
coach.save(function(err) {
if (err) {
return res.status(400).send({
// Put your error message here
});
} else {
res.json(coach);
}
});
};
Player
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
/**
* Player Schema
*/
var PlayerSchema = new Schema({
created: {
type: Date,
default: Date.now
},
throws: {
type: Number,
},
name: {
type: String,
default: '',
trim: true
},
position: {
type: String,
default: '',
trim: true
},
playerNum: {
type: String,
default: '',
trim: true
},
position: {
type: Number,
default: 0,
trim: true
},
teamName: {
type: String,
default: '',
trim: true
}
});
mongoose.model('Player', PlayerSchema);
Coach Schema
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
/**
* Coach Schema
*/
var CoachSchema = new Schema({
created: {
type: Date,
default: Date.now
},
email: {
type: String,
unique: true,
lowercase: true,
trim: true,
default: ''
},
name: {
type: String,
default: '',
trim: true
},
players: [{
type: Schema.ObjectId,
ref: 'Player'
}
});
mongoose.model('Coach', CoachSchema);

When the modifier option is true, validation object must have at least one operator - Collection 2 package - Meteor

I get the following error when trying to create a user :
Exception while invoking method 'createUser' Error: When the modifier option
is true, validation object must have at least one operator
Here is how I am calling the createUser method :
Template.register.events({
"submit #register_form": function(event) {
event.preventDefault();
var $form = $("#register_form");
var attr = {};
var profile = {};
// We gather form values, selecting all named inputs
$("input[name]", $form).each(function(idx) {
var $input = $(this);
var name = $input.attr("name");
if (name == "email" || name == "password") {
attr[ name ] = $input.val();
}
else if (name != "repeat_password") {
profile[ name ] = $input.val();
}
});
attr.profile = profile;
// Creating user account (Cf Accounts module)
Accounts.createUser(attr, function(error) {
console.log(error);
});
},
});
attr has the following value :
And here is how I define the users schema :
// Setting up Simple Schema (cf module)
var profile_schema = new SimpleSchema({
firstname: {
type: String,
regEx: /^[a-z0-9A-Z_]{3,15}$/
},
lastname: {
type: String,
regEx: /^[a-z0-9A-Z_]{3,15}$/
},
celular: {
type: String,
optional: true,
},
});
var schema = new SimpleSchema({
emails: {
type: [Object],
},
"emails.$.address": {
type: String,
regEx: SimpleSchema.RegEx.Email
},
"emails.$.verified": {
type: Boolean
},
profile: {
type: profile_schema,
optional: true,
},
createdAt: {
type: Date
},
});
// Attaching Simple Schema to the users collection (Cf collection2 module)
Meteor.users.attachSchema(schema);
I can't find what is not right. Any suggestion is most welcomed!
Adding my comment as an answer for visibility
You need to add the services object to your user schema as follows
services: {
type: Object,
blackbox: true
}
You need to add this even though you are not using any oAuth because Meteor adds a "service" for email login. You should always have it.

Categories

Resources