I'm working on an API in express, and I wanted to ask if there's a better way to deal with user input. I use Express 4 and Sequelize, and use Sequelize for the validation. However, my creating route kinda looks like this:
app.route('/cards').post(requireAuthenticated, function (req, res, next) {
// Input validation
Card.create({
name: req.body.name,
caption: req.body.caption,
cost: req.body.cost,
tier: req.body.tier,
cardType: req.body.cardType,
readyTime: req.body.readyTime,
attack: req.body.attack,
health: req.body.health,
movement: req.body.movement,
range: req.body.range,
upkeep: req.body.upkeep,
canAttack: req.body.canAttack,
canDefend: req.body.canDefend,
canMove: req.body.canMove,
targetable: req.body.targetable,
description: req.body.description,
comment: req.body.comment,
user_id: req.user.id
})
.then( function (card) {
res.send(card);
})
.catch( function (e) { next(e); });
});
This works all right, but it leaves a lot to be desired. I omitted the validation of the input, because it's even longer, and this is not even the biggest model in my API.
One problem is obviously that whenever an attribute is not set, the attribute of the model will be set to null instead of default or old value. And then there's the problem of re-using this code.
I feel like I'm doing something wrong here, though. I tried to find an npm module on this, but only found ones for validation, not for proper putting input into the database.
Perhaps someone has a better, more elegant solution?
Regarding the lengthy creation of the record, you could use node's built-in extend function:
var extend = require('util')._extend
var newCard = extend({}, req.body);
newCard.user_id = req.user.id;
Card.create(newCard).then(...);
And it is possible for define defaults for columns using sequelize when you define your models:
sequelize.define('modelName', {
columnA: {
type: Sequelize.BOOLEAN,
defaultValue: true
}
...
});
Related
Hello there, a quick MongoDB mixed with some Discord knowledge question:
So currently, I want a formation for my MongoDB Document similar to the following:
channels: {
utility:{
suggestions: String
},
logging: {
main: String,
channel: {
channelCreate: String,
channelDelete: String,
channelUpdate: String,
},
role: {
roleCreate: String,
roleDelete: String,
roleUpdate: String,
}
}
This saves channel IDs so users can decide where each event will be logged. I have this set up in the schema and all good, but when I do findOneAndUpdate I don't know how to edit a single field; for example, let's say I want to edit roleDelete which is inside channels.logging.role how would I do that? because doing
await doc.updateOne({channels:{logging:{role:{roleDelete: IDHERE}}}});
It does not work. In fact, it screws everything up and replaces everything within channels to the value given, so how would I go around actually updating ONE value without messing with everything else? Thank you so much for your attention and participation.
This is using NodeJS Mongoose NPM Package btw.
you need to use $set operator. you can find details on https://docs.mongodb.com/manual/reference/operator/update/set/index.html
doc.updateOne({ _id: ID }, {
$set: {
channels.logging.role.roleDelete: IDHERE
}
}
So I solved this by doing the following:
await doc.updateOne({ 'channels.logging.role.roleDelete': IDHERE}, { new: true, upsert: true, setDefaultsOnInsert: true });
This updated the value if it existed and created it if it didn't exist. Using the above methods uses $set internally. (Read more here)
Feel free to ask if I didn't make myself clear
Until now, I saved all my data with the following line of code:
saveUser(params) {
let newUser = this.store.createRecord('user', params);
newUser.save();
this.transitionTo('index');
This worked fine, but didn't allow for custom ID's in firebase, so I changed it into:
saveUser(params) {
let newUser = this.store.createRecord('user', {
id: params.loginId,
name: params.name,
nickname: params.nickname,
imageUrl: params.imageUrl,
email: params.email
});
newUser.save();
this.transitionTo('index');
Processes them exactly as I want them to be stored on the Firebase database, so no problem there. I'm wondering though, and not finding any solution on the web, how I can combine the two, so that I don't have to bind every param. It's bound to give problems when I add/remove model properties.
Something I'm looking for would look like this (pseudo, yes I tried it, didn't work!):
let newUser = this.store.createRecord('user', {id: params.loginId}, params);
In short, I'm looking for the dynamic properties of ('model', params), but with the option to manually adjust 1 (or more) records without having to type out all of the params.
Thanks in advance !
You will probably want to customize your serializer to accomplish this. The example in the docs is a good one, so it should be pretty straightforward: https://guides.emberjs.com/v2.13.0/models/customizing-serializers/
I am, of course, assuming you are using Ember Data for your models.
In a failed attempt learning exercise to get validators to work with 'document.update', I came across something I don't understand.
I know now that it doesn't work, but one of the things I tried was setting my options to {runValidators:true, context:'query'}. In my validator function, I tried console.logging (this), with and without the context:"query" option.
There was no difference. I received a large object (is this called the 'query object'?) This seems to go against what I read here.
In the color validation function above, this refers to the document being validated when using document validation. However, when running update validators, the document being updated may not be in the server's memory, so by default the value of this is not defined.
It was not undefined , even without the context option.
I even tried making it an arrow function to see if the lexical this was any different. In that case, this was undefined, but again, changing the context option did not make a difference. (I'm still learning, so I don't know if that part is relevant).
in the model:
let Property = mongoose.model('Property', {
name: {type:String, required:true},
occupancy: {type:String},
maxTenants: Number,
tenants: [{ type:mongoose.Schema.Types.ObjectId, ref: 'Tenant', validate: [checkMaxTenants, "Maximum tenants exceeded for this property. Tenant not added."]}]
});
function checkMaxTenants(val){
console.log("this",this);
// return this.tenants.length <= this.maxTenants;
return true;
}
and in the route:
property.update({$set: {tenants:property.tenants}},{new:true,runValidators:true,context:'query'}, function(err,savedProperty){
Anything to help me better understand the discrepancy between what I think I'm reading and what I see would be great!
At the outset, let's be clear that validators are of two types: document validators and update validators (maybe you know this already, but the snippet you posted updates a document, whereas the issue you mention relates to document validation upon save).
There was no difference. I received a large object (is this called the 'query object'?) This seems to go against what I read here.
Document validators are run when you run save on documents as mentioned in the docs.
Validation is middleware. Mongoose registers validation as a pre('save') hook on every schema by default.
Or you can call it manually with .validate()
You can manually run validation using doc.validate(callback) or doc.validateSync()
Update validators are run for update operations
In the above examples, you learned about document validation. Mongoose also supports validation for update() and findOneAndUpdate() operations.
This can be illustrated with the following snippet. For convenience I have changed the type of tenants to a simple integer array, but that shouldn't matter for the purpose of our discussion.
// "use strict";
const mongoose = require('mongoose');
const assert = require('assert');
const Schema = mongoose.Schema;
let Property = mongoose.model('Property', {
name: { type: String, required: true },
occupancy: { type:String },
maxTenants: Number,
tenants: [
{
type: Number,
ref: 'Tenant',
validate: {
validator: checkMaxTenants,
message: "Maximum tenants exceeded for this property. Tenant not added."
}
}
]
});
function checkMaxTenants (val) {
console.log("this", this);
// return this.tenants.length <= this.maxTenants;
return true;
}
mongoose.Promise = global.Promise;
mongoose.createConnection('mongodb://localhost/myapp', {
useMongoClient: true,
}).then(function(db) {
const property = new Property({ name: 'foo', occupancy: 'bar', tenants: [1] });
property.update(
{ $set: { tenants: [2, 3] } },
{
new: true,
runValidators: true,
// context: 'query'
},
function(err, savedProperty) {
}
)
// property.save();
});
Above code with trigger a update validation not document validation
To see document validation in action uncomment property.save() and comment the update operation.
You'll notice that the value of this will be the property document.
this { name: 'foo',
occupancy: 'bar',
_id: 598e9d72992907120a99a367,
tenants: [ 1 ] }
Comment the save, uncomment back the update operation and you'll see the large object you mentioned.
Now the large object you got, you may not have realised, is the global object when you didn't set context: 'query' and the query object when you set the context.
This can be explained at this line in mongoose's source. When no context was set, mongoose sets the scope to null. And then here the .call is called with the scope.
Now, in non strict mode, when .call is called with null, this is replaced with the global object. So check contents of the large object you got. When context is not set, it would be a global object and not the query object. You can add "use strict"; and see that null will be logged. (The snippet posted can verify this for you). You can verify that you got a query object by running instanceof mongoose.Query against this.
Hope this helps you understand things better.
Is it possible to incorporate data validation rules for Create, Update, and Delete operations when using the Knex.js query builder library, even though Knex does not do this out of the box?
If yes, then:
is it a good idea or bad idea to stay inside Knex for this?
if it is an OK approach, is there a decent example of this someone can point to?
would you be better off and have less friction if you include Bookshelf.js?
Even Bookshelf does not come with a validation engine.
It would be better to use bookshelf since it provides events during the transaction. While bookshelf doesn't come with a built in validation engine, you can use Checkit. It is built by the same author of Knex and Bookshelf. By hooking into the saving event, you can effectively validate your model.
Here's a simple example:
var User = Bookshelf.Model.extend({
tableName: 'users',
initialize: function() {
this.on('saving', this.validate, this);
},
validations: {
email: ['required', 'validEmail'],
username: ['required', 'alphaNumeric'],
age: ['isNumeric']
},
validate: function(model, attrs, options) {
return CheckIt(this.toJSON()).run(this.validations);
}
});
Check out this issue thread on GH for more insight.
I'm using Mongoose ODM to partially validate models before they are stored to MongoDB.
Is it possible to relax Mongoose schemata so that a given part of the document is not validated? I have tried to the following:
var MySchema = new Schema({
user_id: { type: Schema.ObjectId, ref: 'User' },
freeform_data: {},
});
For instance if I set the contents to:
{
user_id: '123456',
freeform_data: {
dataitem1: 'a',
dataitem2: 'b',
items: [ 1, 2, 3, 4 ]
}
}
Then only user_id is stored, which makes perfectly sense security-wise.
How can I disable mongoose's validation for this field?
I am using this application only for prototyping purposes so I don't care about security right now (I just want to prototype).
When you modify the contents of a Mixed field like freeform_data, you need to notify Mongoose that you've changed its value by calling markModified(path) on the modified document or a subsequent save() call won't save it.
For example:
user.freeform_data = { foo: 'bar' };
user.markModified('freeform_data');
user.save();
Mongeese
: a mongoose multi-database helper/hack module
https://github.com/donpark/mongeese
Disclaimer: I was looking to connect to two MongoDB instances in the same app and gave up. So I haven't tried it.