Before we get into the specifics of validation syntax, please keep the following rules in mind:
- Validation is defined in the SchemaType
- Validation is middleware. Mongoose registers validation as a
pre('save')
hook on every schema by default. - You can disable automatic validation before save by setting the validateBeforeSave option
- You can manually run validation using
doc.validate(callback)
ordoc.validateSync()
- You can manually mark a field as invalid (causing validation to fail) by using
doc.invalidate(...)
- Validators are not run on undefined values. The only exception is the
required
validator. - Validation is asynchronously recursive; when you call Model#save, sub-document validation is executed as well. If an error occurs, your Model#save callback receives it
- Validation is customizable
const schema = new Schema({
name: {
type: String,
required: true
}
});
const Cat = db.model('Cat', schema);
// This cat has no name :(
const cat = new Cat();
let error;
try {
await cat.save();
} catch (err) {
error = err;
}
assert.equal(error.errors['name'].message,
'Path `name` is required.');
error = cat.validateSync();
assert.equal(error.errors['name'].message,
'Path `name` is required.');
- Built-in Validators
- Custom Error Messages
- The
unique
Option is Not a Validator - Custom Validators
- Async Custom Validators
- Validation Errors
- Cast Errors
- Global SchemaType Validation
- Required Validators On Nested Objects
- Update Validators
- Update Validators and
this
- Update Validators Only Run On Updated Paths
- Update Validators Only Run For Some Operations
Built-in Validators
Mongoose has several built-in validators.
- All SchemaTypes have the built-in required validator. The required validator uses the SchemaType’s
checkRequired()
function to determine if the value satisfies the required validator. - Numbers have
min
andmax
validators. - Strings have
enum
,match
,minLength
, andmaxLength
validators.
Each of the validator links above provide more information about how to enable them and customize their error messages.
const breakfastSchema = new Schema({
eggs: {
type: Number,
min: [6, 'Too few eggs'],
max: 12
},
bacon: {
type: Number,
required: [true, 'Why no bacon?']
},
drink: {
type: String,
enum: ['Coffee', 'Tea'],
required: function() {
return this.bacon > 3;
}
}
});
const Breakfast = db.model('Breakfast', breakfastSchema);
const badBreakfast = new Breakfast({
eggs: 2,
bacon: 0,
drink: 'Milk'
});
let error = badBreakfast.validateSync();
assert.equal(error.errors['eggs'].message,
'Too few eggs');
assert.ok(!error.errors['bacon']);
assert.equal(error.errors['drink'].message,
'`Milk` is not a valid enum value for path `drink`.');
badBreakfast.bacon = 5;
badBreakfast.drink = null;
error = badBreakfast.validateSync();
assert.equal(error.errors['drink'].message, 'Path `drink` is required.');
badBreakfast.bacon = null;
error = badBreakfast.validateSync();
assert.equal(error.errors['bacon'].message, 'Why no bacon?');
Custom Error Messages
You can configure the error message for individual validators in your schema. There are two equivalent
ways to set the validator error message:
- Array syntax:
min: [6, 'Must be at least 6, got {VALUE}']
- Object syntax:
enum: { values: ['Coffee', 'Tea'], message: '{VALUE} is not supported' }
Mongoose also supports rudimentary templating for error messages.
Mongoose replaces {VALUE}
with the value being validated.
const breakfastSchema = new Schema({
eggs: {
type: Number,
min: [6, 'Must be at least 6, got {VALUE}'],
max: 12
},
drink: {
type: String,
enum: {
values: ['Coffee', 'Tea'],
message: '{VALUE} is not supported'
}
}
});
const Breakfast = db.model('Breakfast', breakfastSchema);
const badBreakfast = new Breakfast({
eggs: 2,
drink: 'Milk'
});
const error = badBreakfast.validateSync();
assert.equal(error.errors['eggs'].message,
'Must be at least 6, got 2');
assert.equal(error.errors['drink'].message, 'Milk is not supported');
The unique
Option is Not a Validator
A common gotcha for beginners is that the unique
option for schemas
is not a validator. It’s a convenient helper for building MongoDB unique indexes.
See the FAQ for more information.
const uniqueUsernameSchema = new Schema({
username: {
type: String,
unique: true
}
});
const U1 = db.model('U1', uniqueUsernameSchema);
const U2 = db.model('U2', uniqueUsernameSchema);
const dup = [{ username: 'Val' }, { username: 'Val' }];
U1.create(dup, err => {
// Race condition! This may save successfully, depending on whether
// MongoDB built the index before writing the 2 docs.
});
// You need to wait for Mongoose to finish building the `unique`
// index before writing. You only need to build indexes once for
// a given collection, so you normally don't need to do this
// in production. But, if you drop the database between tests,
// you will need to use `init()` to wait for the index build to finish.
U2.init().
then(() => U2.create(dup)).
catch(error => {
// Will error, but will *not* be a mongoose validation error, it will be
// a duplicate key error.
// See: https://masteringjs.io/tutorials/mongoose/e11000-duplicate-key
assert.ok(error);
assert.ok(!error.errors);
assert.ok(error.message.indexOf('duplicate key error') !== -1);
});
Custom Validators
If the built-in validators aren’t enough, you can define custom validators
to suit your needs.
Custom validation is declared by passing a validation function.
You can find detailed instructions on how to do this in the
SchemaType#validate()
API docs.
const userSchema = new Schema({
phone: {
type: String,
validate: {
validator: function(v) {
return /d{3}-d{3}-d{4}/.test(v);
},
message: props => `${props.value} is not a valid phone number!`
},
required: [true, 'User phone number required']
}
});
const User = db.model('user', userSchema);
const user = new User();
let error;
user.phone = '555.0123';
error = user.validateSync();
assert.equal(error.errors['phone'].message,
'555.0123 is not a valid phone number!');
user.phone = '';
error = user.validateSync();
assert.equal(error.errors['phone'].message,
'User phone number required');
user.phone = '201-555-0123';
// Validation succeeds! Phone number is defined
// and fits `DDD-DDD-DDDD`
error = user.validateSync();
assert.equal(error, null);
Async Custom Validators
Custom validators can also be asynchronous. If your validator function
returns a promise (like an async
function), mongoose will wait for that
promise to settle. If the returned promise rejects, or fulfills with
the value false
, Mongoose will consider that a validation error.
const userSchema = new Schema({
name: {
type: String,
// You can also make a validator async by returning a promise.
validate: () => Promise.reject(new Error('Oops!'))
},
email: {
type: String,
// There are two ways for an promise-based async validator to fail:
// 1) If the promise rejects, Mongoose assumes the validator failed with the given error.
// 2) If the promise resolves to `false`, Mongoose assumes the validator failed and creates an error with the given `message`.
validate: {
validator: () => Promise.resolve(false),
message: 'Email validation failed'
}
}
});
const User = db.model('User', userSchema);
const user = new User();
user.email = 'test@test.co';
user.name = 'test';
let error;
try {
await user.validate();
} catch (err) {
error = err;
}
assert.ok(error);
assert.equal(error.errors['name'].message, 'Oops!');
assert.equal(error.errors['email'].message, 'Email validation failed');
Validation Errors
Errors returned after failed validation contain an errors
object
whose values are ValidatorError
objects. Each
ValidatorError has kind
, path
,
value
, and message
properties.
A ValidatorError also may have a reason
property. If an error was
thrown in the validator, this property will contain the error that was
thrown.
const toySchema = new Schema({
color: String,
name: String
});
const validator = function(value) {
return /red|white|gold/i.test(value);
};
toySchema.path('color').validate(validator,
'Color `{VALUE}` not valid', 'Invalid color');
toySchema.path('name').validate(function(v) {
if (v !== 'Turbo Man') {
throw new Error('Need to get a Turbo Man for Christmas');
}
return true;
}, 'Name `{VALUE}` is not valid');
const Toy = db.model('Toy', toySchema);
const toy = new Toy({ color: 'Green', name: 'Power Ranger' });
let error;
try {
await toy.save();
} catch(err) {
error = err;
}
// `error` is a ValidationError object
// `error.errors.color` is a ValidatorError object
assert.equal(error.errors.color.message, 'Color `Green` not valid');
assert.equal(error.errors.color.kind, 'Invalid color');
assert.equal(error.errors.color.path, 'color');
assert.equal(error.errors.color.value, 'Green');
// If your validator throws an exception, mongoose will use the error
// message. If your validator returns `false`,
// mongoose will use the 'Name `Power Ranger` is not valid' message.
assert.equal(error.errors.name.message,
'Need to get a Turbo Man for Christmas');
assert.equal(error.errors.name.value, 'Power Ranger');
// If your validator threw an error, the `reason` property will contain
// the original error thrown, including the original stack trace.
assert.equal(error.errors.name.reason.message,
'Need to get a Turbo Man for Christmas');
assert.equal(error.name, 'ValidationError');
Cast Errors
Before running validators, Mongoose attempts to coerce values to the
correct type. This process is called casting the document. If
casting fails for a given path, the error.errors
object will contain
a CastError
object.
Casting runs before validation, and validation does not run if casting
fails. That means your custom validators may assume v
is null
,
undefined
, or an instance of the type specified in your schema.
const vehicleSchema = new mongoose.Schema({
numWheels: { type: Number, max: 18 }
});
const Vehicle = db.model('Vehicle', vehicleSchema);
const doc = new Vehicle({ numWheels: 'not a number' });
const err = doc.validateSync();
err.errors['numWheels'].name; // 'CastError'
// 'Cast to Number failed for value "not a number" at path "numWheels"'
err.errors['numWheels'].message;
Global SchemaType Validation
In addition to defining custom validators on individual schema paths, you can also configure a custom validator to run on every instance of a given SchemaType
.
For example, the following code demonstrates how to make empty string ''
an invalid value for all string paths.
// Add a custom validator to all strings
mongoose.Schema.Types.String.set('validate', v => v == null || v > 0);
const userSchema = new Schema({
name: String,
email: String
});
const User = db.model('User', userSchema);
const user = new User({ name: '', email: '' });
const err = await user.validate().then(() => null, err => err);
err.errors['name']; // ValidatorError
err.errors['email']; // ValidatorError
Required Validators On Nested Objects
Defining validators on nested objects in mongoose is tricky, because
nested objects are not fully fledged paths.
let personSchema = new Schema({
name: {
first: String,
last: String
}
});
assert.throws(function() {
// This throws an error, because 'name' isn't a full fledged path
personSchema.path('name').required(true);
}, /Cannot.*'required'/);
// To make a nested object required, use a single nested schema
const nameSchema = new Schema({
first: String,
last: String
});
personSchema = new Schema({
name: {
type: nameSchema,
required: true
}
});
const Person = db.model('Person', personSchema);
const person = new Person();
const error = person.validateSync();
assert.ok(error.errors['name']);
Update Validators
In the above examples, you learned about document validation. Mongoose also
supports validation for update()
,
updateOne()
,
updateMany()
,
and findOneAndUpdate()
operations.
Update validators are off by default — you need to specify
the runValidators
option.
To turn on update validators, set the runValidators
option for
update()
, updateOne()
, updateMany()
, or findOneAndUpdate()
.
Be careful: update validators are off by default because they have several
caveats.
const toySchema = new Schema({
color: String,
name: String
});
const Toy = db.model('Toys', toySchema);
Toy.schema.path('color').validate(function(value) {
return /red|green|blue/i.test(value);
}, 'Invalid color');
const opts = { runValidators: true };
let error;
try {
await Toy.updateOne({}, { color: 'not a color' }, opts);
} catch (err) {
error = err;
}
assert.equal(error.errors.color.message, 'Invalid color');
Update Validators and this
There are a couple of key differences between update validators and
document validators. In the color validation function below, this
refers
to the document being validated when using document validation.
However, when running update validators, this
refers to the query object instead of the document.
Because queries have a neat .get()
function, you can get the updated value of the property you want.
const toySchema = new Schema({
color: String,
name: String
});
toySchema.path('color').validate(function(value) {
// When running in `validate()` or `validateSync()`, the
// validator can access the document using `this`.
// When running with update validators, `this` is the Query,
// **not** the document being updated!
// Queries have a `get()` method that lets you get the
// updated value.
if (this.get('name') && this.get('name').toLowerCase().indexOf('red') !== -1) {
return value === 'red';
}
return true;
});
const Toy = db.model('ActionFigure', toySchema);
const toy = new Toy({ color: 'green', name: 'Red Power Ranger' });
// Validation failed: color: Validator failed for path `color` with value `green`
let error = toy.validateSync();
assert.ok(error.errors['color']);
const update = { color: 'green', name: 'Red Power Ranger' };
const opts = { runValidators: true };
error = null;
try {
await Toy.updateOne({}, update, opts);
} catch (err) {
error = err;
}
// Validation failed: color: Validator failed for path `color` with value `green`
assert.ok(error);
Update Validators Only Run On Updated Paths
The other key difference is that update validators only run on the paths
specified in the update. For instance, in the below example, because
‘name’ is not specified in the update operation, update validation will
succeed.
When using update validators, required
validators only fail when
you try to explicitly $unset
the key.
const kittenSchema = new Schema({
name: { type: String, required: true },
age: Number
});
const Kitten = db.model('Kitten', kittenSchema);
const update = { color: 'blue' };
const opts = { runValidators: true };
Kitten.updateOne({}, update, opts, function() {
// Operation succeeds despite the fact that 'name' is not specified
});
const unset = { $unset: { name: 1 } };
Kitten.updateOne({}, unset, opts, function(err) {
// Operation fails because 'name' is required
assert.ok(err);
assert.ok(err.errors['name']);
});
Update Validators Only Run For Some Operations
One final detail worth noting: update validators only run on the
following update operators:
$set
$unset
$push
$addToSet
$pull
$pullAll
For instance, the below update will succeed, regardless of the value of
number
, because update validators ignore $inc
.
Also, $push
, $addToSet
, $pull
, and $pullAll
validation does
not run any validation on the array itself, only individual elements
of the array.
const testSchema = new Schema({
number: { type: Number, max: 0 },
arr: [{ message: { type: String, maxlength: 10 } }]
});
// Update validators won't check this, so you can still `$push` 2 elements
// onto the array, so long as they don't have a `message` that's too long.
testSchema.path('arr').validate(function(v) {
return v.length < 2;
});
const Test = db.model('Test', testSchema);
let update = { $inc: { number: 1 } };
const opts = { runValidators: true };
// There will never be a validation error here
await Test.updateOne({}, update, opts);
// This will never error either even though the array will have at
// least 2 elements.
update = { $push: [{ message: 'hello' }, { message: 'world' }] };
await Test.updateOne({}, update, opts);
Next Up
Now that we’ve covered Validation
, let’s take a look at Middleware.
Before we get into the specifics of validation syntax, please keep the following rules in mind:
- Validation is defined in the SchemaType
- Validation is middleware. Mongoose registers validation as a
pre('save')
hook on every schema by default. - You can disable automatic validation before save by setting the validateBeforeSave option
- You can manually run validation using
doc.validate(callback)
ordoc.validateSync()
- You can manually mark a field as invalid (causing validation to fail) by using
doc.invalidate(...)
- Validators are not run on undefined values. The only exception is the
required
validator. - Validation is asynchronously recursive; when you call Model#save, sub-document validation is executed as well. If an error occurs, your Model#save callback receives it
- Validation is customizable
const schema = new Schema({
name: {
type: String,
required: true
}
});
const Cat = db.model('Cat', schema);
// This cat has no name :(
const cat = new Cat();
cat.save(function(error) {
assert.equal(error.errors['name'].message,
'Path `name` is required.');
error = cat.validateSync();
assert.equal(error.errors['name'].message,
'Path `name` is required.');
});
Built-in Validators
Mongoose has several built-in validators.
- All SchemaTypes have the built-in required validator. The required validator uses the SchemaType’s
checkRequired()
function to determine if the value satisfies the required validator. - Numbers have
min
andmax
validators. - Strings have
enum
,match
,minLength
, andmaxLength
validators.
Each of the validator links above provide more information about how to enable them and customize their error messages.
const breakfastSchema = new Schema({
eggs: {
type: Number,
min: [6, 'Too few eggs'],
max: 12
},
bacon: {
type: Number,
required: [true, 'Why no bacon?']
},
drink: {
type: String,
enum: ['Coffee', 'Tea'],
required: function() {
return this.bacon > 3;
}
}
});
const Breakfast = db.model('Breakfast', breakfastSchema);
const badBreakfast = new Breakfast({
eggs: 2,
bacon: 0,
drink: 'Milk'
});
let error = badBreakfast.validateSync();
assert.equal(error.errors['eggs'].message,
'Too few eggs');
assert.ok(!error.errors['bacon']);
assert.equal(error.errors['drink'].message,
'`Milk` is not a valid enum value for path `drink`.');
badBreakfast.bacon = 5;
badBreakfast.drink = null;
error = badBreakfast.validateSync();
assert.equal(error.errors['drink'].message, 'Path `drink` is required.');
badBreakfast.bacon = null;
error = badBreakfast.validateSync();
assert.equal(error.errors['bacon'].message, 'Why no bacon?');
Custom Error Messages
You can configure the error message for individual validators in your schema. There are two equivalent
ways to set the validator error message:
- Array syntax:
min: [6, 'Must be at least 6, got {VALUE}']
- Object syntax:
enum: { values: ['Coffee', 'Tea'], message: '{VALUE} is not supported' }
Mongoose also supports rudimentary templating for error messages.
Mongoose replaces {VALUE}
with the value being validated.
const breakfastSchema = new Schema({
eggs: {
type: Number,
min: [6, 'Must be at least 6, got {VALUE}'],
max: 12
},
drink: {
type: String,
enum: {
values: ['Coffee', 'Tea'],
message: '{VALUE} is not supported'
}
}
});
const Breakfast = db.model('Breakfast', breakfastSchema);
const badBreakfast = new Breakfast({
eggs: 2,
drink: 'Milk'
});
let error = badBreakfast.validateSync();
assert.equal(error.errors['eggs'].message,
'Must be at least 6, got 2');
assert.equal(error.errors['drink'].message, 'Milk is not supported');
The unique
Option is Not a Validator
A common gotcha for beginners is that the unique
option for schemas
is not a validator. It’s a convenient helper for building MongoDB unique indexes.
See the FAQ for more information.
const uniqueUsernameSchema = new Schema({
username: {
type: String,
unique: true
}
});
const U1 = db.model('U1', uniqueUsernameSchema);
const U2 = db.model('U2', uniqueUsernameSchema);
const dup = [{ username: 'Val' }, { username: 'Val' }];
U1.create(dup, err => {
// Race condition! This may save successfully, depending on whether
// MongoDB built the index before writing the 2 docs.
});
// You need to wait for Mongoose to finish building the `unique`
// index before writing. You only need to build indexes once for
// a given collection, so you normally don't need to do this
// in production. But, if you drop the database between tests,
// you will need to use `init()` to wait for the index build to finish.
U2.init().
then(() => U2.create(dup)).
catch(error => {
// Will error, but will *not* be a mongoose validation error, it will be
// a duplicate key error.
// See: https://masteringjs.io/tutorials/mongoose/e11000-duplicate-key
assert.ok(error);
assert.ok(!error.errors);
assert.ok(error.message.indexOf('duplicate key error') !== -1);
});
Custom Validators
If the built-in validators aren’t enough, you can define custom validators
to suit your needs.
Custom validation is declared by passing a validation function.
You can find detailed instructions on how to do this in the
SchemaType#validate()
API docs.
const userSchema = new Schema({
phone: {
type: String,
validate: {
validator: function(v) {
return /d{3}-d{3}-d{4}/.test(v);
},
message: props => `${props.value} is not a valid phone number!`
},
required: [true, 'User phone number required']
}
});
const User = db.model('user', userSchema);
const user = new User();
let error;
user.phone = '555.0123';
error = user.validateSync();
assert.equal(error.errors['phone'].message,
'555.0123 is not a valid phone number!');
user.phone = '';
error = user.validateSync();
assert.equal(error.errors['phone'].message,
'User phone number required');
user.phone = '201-555-0123';
// Validation succeeds! Phone number is defined
// and fits `DDD-DDD-DDDD`
error = user.validateSync();
assert.equal(error, null);
Async Custom Validators
Custom validators can also be asynchronous. If your validator function
returns a promise (like an async
function), mongoose will wait for that
promise to settle. If the returned promise rejects, or fulfills with
the value false
, Mongoose will consider that a validation error.
const userSchema = new Schema({
name: {
type: String,
// You can also make a validator async by returning a promise.
validate: () => Promise.reject(new Error('Oops!'))
},
email: {
type: String,
// There are two ways for an promise-based async validator to fail:
// 1) If the promise rejects, Mongoose assumes the validator failed with the given error.
// 2) If the promise resolves to `false`, Mongoose assumes the validator failed and creates an error with the given `message`.
validate: {
validator: () => Promise.resolve(false),
message: 'Email validation failed'
}
}
});
const User = db.model('User', userSchema);
const user = new User();
user.email = 'test@test.co';
user.name = 'test';
user.validate().catch(error => {
assert.ok(error);
assert.equal(error.errors['name'].message, 'Oops!');
assert.equal(error.errors['email'].message, 'Email validation failed');
});
Validation Errors
Errors returned after failed validation contain an errors
object
whose values are ValidatorError
objects. Each
ValidatorError has kind
, path
,
value
, and message
properties.
A ValidatorError also may have a reason
property. If an error was
thrown in the validator, this property will contain the error that was
thrown.
const toySchema = new Schema({
color: String,
name: String
});
const validator = function(value) {
return /red|white|gold/i.test(value);
};
toySchema.path('color').validate(validator,
'Color `{VALUE}` not valid', 'Invalid color');
toySchema.path('name').validate(function(v) {
if (v !== 'Turbo Man') {
throw new Error('Need to get a Turbo Man for Christmas');
}
return true;
}, 'Name `{VALUE}` is not valid');
const Toy = db.model('Toy', toySchema);
const toy = new Toy({ color: 'Green', name: 'Power Ranger' });
toy.save(function(err) {
// `err` is a ValidationError object
// `err.errors.color` is a ValidatorError object
assert.equal(err.errors.color.message, 'Color `Green` not valid');
assert.equal(err.errors.color.kind, 'Invalid color');
assert.equal(err.errors.color.path, 'color');
assert.equal(err.errors.color.value, 'Green');
// This is new in mongoose 5. If your validator throws an exception,
// mongoose will use that message. If your validator returns `false`,
// mongoose will use the 'Name `Power Ranger` is not valid' message.
assert.equal(err.errors.name.message,
'Need to get a Turbo Man for Christmas');
assert.equal(err.errors.name.value, 'Power Ranger');
// If your validator threw an error, the `reason` property will contain
// the original error thrown, including the original stack trace.
assert.equal(err.errors.name.reason.message,
'Need to get a Turbo Man for Christmas');
assert.equal(err.name, 'ValidationError');
});
Cast Errors
Before running validators, Mongoose attempts to coerce values to the
correct type. This process is called casting the document. If
casting fails for a given path, the error.errors
object will contain
a CastError
object.
Casting runs before validation, and validation does not run if casting
fails. That means your custom validators may assume v
is null
,
undefined
, or an instance of the type specified in your schema.
const vehicleSchema = new mongoose.Schema({
numWheels: { type: Number, max: 18 }
});
const Vehicle = db.model('Vehicle', vehicleSchema);
const doc = new Vehicle({ numWheels: 'not a number' });
const err = doc.validateSync();
err.errors['numWheels'].name; // 'CastError'
// 'Cast to Number failed for value "not a number" at path "numWheels"'
err.errors['numWheels'].message;
Required Validators On Nested Objects
Defining validators on nested objects in mongoose is tricky, because
nested objects are not fully fledged paths.
let personSchema = new Schema({
name: {
first: String,
last: String
}
});
assert.throws(function() {
// This throws an error, because 'name' isn't a full fledged path
personSchema.path('name').required(true);
}, /Cannot.*'required'/);
// To make a nested object required, use a single nested schema
const nameSchema = new Schema({
first: String,
last: String
});
personSchema = new Schema({
name: {
type: nameSchema,
required: true
}
});
const Person = db.model('Person', personSchema);
const person = new Person();
const error = person.validateSync();
assert.ok(error.errors['name']);
Update Validators
In the above examples, you learned about document validation. Mongoose also
supports validation for update()
,
updateOne()
,
updateMany()
,
and findOneAndUpdate()
operations.
Update validators are off by default — you need to specify
the runValidators
option.
To turn on update validators, set the runValidators
option for
update()
, updateOne()
, updateMany()
, or findOneAndUpdate()
.
Be careful: update validators are off by default because they have several
caveats.
const toySchema = new Schema({
color: String,
name: String
});
const Toy = db.model('Toys', toySchema);
Toy.schema.path('color').validate(function(value) {
return /red|green|blue/i.test(value);
}, 'Invalid color');
const opts = { runValidators: true };
Toy.updateOne({}, { color: 'not a color' }, opts, function(err) {
assert.equal(err.errors.color.message,
'Invalid color');
});
Update Validators and this
There are a couple of key differences between update validators and
document validators. 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.
const toySchema = new Schema({
color: String,
name: String
});
toySchema.path('color').validate(function(value) {
// When running in `validate()` or `validateSync()`, the
// validator can access the document using `this`.
// Does **not** work with update validators.
if (this.name.toLowerCase().indexOf('red') !== -1) {
return value !== 'red';
}
return true;
});
const Toy = db.model('ActionFigure', toySchema);
const toy = new Toy({ color: 'red', name: 'Red Power Ranger' });
const error = toy.validateSync();
assert.ok(error.errors['color']);
const update = { color: 'red', name: 'Red Power Ranger' };
const opts = { runValidators: true };
Toy.updateOne({}, update, opts, function(error) {
// The update validator throws an error:
// "TypeError: Cannot read property 'toLowerCase' of undefined",
// because `this` is **not** the document being updated when using
// update validators
assert.ok(error);
});
The context
option
The context
option lets you set the value of this
in update validators
to the underlying query.
toySchema.path('color').validate(function(value) {
// When running update validators with the `context` option set to
// 'query', `this` refers to the query object.
if (this.getUpdate().$set.name.toLowerCase().indexOf('red') !== -1) {
return value === 'red';
}
return true;
});
const Toy = db.model('Figure', toySchema);
const update = { color: 'blue', name: 'Red Power Ranger' };
// Note the context option
const opts = { runValidators: true, context: 'query' };
Toy.updateOne({}, update, opts, function(error) {
assert.ok(error.errors['color']);
});
Update Validators Only Run On Updated Paths
The other key difference is that update validators only run on the paths
specified in the update. For instance, in the below example, because
‘name’ is not specified in the update operation, update validation will
succeed.
When using update validators, required
validators only fail when
you try to explicitly $unset
the key.
const kittenSchema = new Schema({
name: { type: String, required: true },
age: Number
});
const Kitten = db.model('Kitten', kittenSchema);
const update = { color: 'blue' };
const opts = { runValidators: true };
Kitten.updateOne({}, update, opts, function() {
// Operation succeeds despite the fact that 'name' is not specified
});
const unset = { $unset: { name: 1 } };
Kitten.updateOne({}, unset, opts, function(err) {
// Operation fails because 'name' is required
assert.ok(err);
assert.ok(err.errors['name']);
});
Update Validators Only Run For Some Operations
One final detail worth noting: update validators only run on the
following update operators:
$set
$unset
$push
(>= 4.8.0)$addToSet
(>= 4.8.0)$pull
(>= 4.12.0)$pullAll
(>= 4.12.0)
For instance, the below update will succeed, regardless of the value of
number
, because update validators ignore $inc
.
Also, $push
, $addToSet
, $pull
, and $pullAll
validation does
not run any validation on the array itself, only individual elements
of the array.
const testSchema = new Schema({
number: { type: Number, max: 0 },
arr: [{ message: { type: String, maxlength: 10 } }]
});
// Update validators won't check this, so you can still `$push` 2 elements
// onto the array, so long as they don't have a `message` that's too long.
testSchema.path('arr').validate(function(v) {
return v.length < 2;
});
const Test = db.model('Test', testSchema);
let update = { $inc: { number: 1 } };
const opts = { runValidators: true };
Test.updateOne({}, update, opts, function() {
// There will never be a validation error here
update = { $push: [{ message: 'hello' }, { message: 'world' }] };
Test.updateOne({}, update, opts, function(error) {
// This will never error either even though the array will have at
// least 2 elements.
});
});
Validation
Перед тем,как мы углубимся в специфику синтаксиса валидации,пожалуйста,помните о следующих правилах:
- Проверка определяется в SchemaType
- Проверка — это промежуточное ПО . По умолчанию Mongoose регистрирует проверку как
pre('save')
перехватчик для каждой схемы. - Вы можете вручную запустить проверку с помощью
doc.validate(callback)
илиdoc.validateSync()
- Валидаторы не работают с неопределенными значениями. Единственное исключение —
required
валидатор . - Проверка выполняется асинхронно рекурсивно; когда вы вызываете Model # save , также выполняется проверка вложенного документа. Если возникает ошибка, ваш обратный вызов Model # save получает ее.
- Проверка настраивается
var schema = new Schema({ name: { type: String, required: true } }); var Cat = db.model('Cat', schema); var cat = new Cat(); cat.save(function(error) { assert.equal(error.errors['name'].message, 'Path `name` is required.'); error = cat.validateSync(); assert.equal(error.errors['name'].message, 'Path `name` is required.'); });
Built-in Validators
В Mongoose есть несколько встроенных валидаторов.
- Все типы SchemaTypes имеют встроенный обязательный валидатор. Требуемое валидатор использует в SchemaType в
checkRequired()
функцию , чтобы определить , если значение удовлетворяет требуемый валидатор. -
Числа имеют
min
иmax
валидаторы. -
Строки имеют валидаторы
enum
,match
,minlength
иmaxlength
.
Каждая из ссылок на валидатор выше предоставляет дополнительную информацию о том,как включить их и настроить их сообщения об ошибках.
var breakfastSchema = new Schema({ eggs: { type: Number, min: [6, 'Too few eggs'], max: 12 }, bacon: { type: Number, required: [true, 'Why no bacon?'] }, drink: { type: String, enum: ['Coffee', 'Tea'], required: function() { return this.bacon > 3; } } }); var Breakfast = db.model('Breakfast', breakfastSchema); var badBreakfast = new Breakfast({ eggs: 2, bacon: 0, drink: 'Milk' }); var error = badBreakfast.validateSync(); assert.equal(error.errors['eggs'].message, 'Too few eggs'); assert.ok(!error.errors['bacon']); assert.equal(error.errors['drink'].message, '`Milk` is not a valid enum value for path `drink`.'); badBreakfast.bacon = 5; badBreakfast.drink = null; error = badBreakfast.validateSync(); assert.equal(error.errors['drink'].message, 'Path `drink` is required.'); badBreakfast.bacon = null; error = badBreakfast.validateSync(); assert.equal(error.errors['bacon'].message, 'Why no bacon?');
unique
опция не валидатор
Обычная проблема для новичков заключается в том, что unique
опция для схем — это не валидатор. Это удобный помощник для создания уникальных индексов MongoDB . См. FAQ для получения дополнительной информации.
var uniqueUsernameSchema = new Schema({ username: { type: String, unique: true } }); var U1 = db.model('U1', uniqueUsernameSchema); var U2 = db.model('U2', uniqueUsernameSchema); var dup = [{ username: 'Val' }, { username: 'Val' }]; U1.create(dup, function(error) { }); U2.once('index', function(error) { assert.ifError(error); U2.create(dup, function(error) { assert.ok(error); assert.ok(!error.errors); assert.ok(error.message.indexOf('duplicate key error') !== -1); }); }); U2.init().then(function() { U2.create(dup, function(error) { assert.ok(error); assert.ok(!error.errors); assert.ok(error.message.indexOf('duplicate key error') !== -1); }); });
Custom Validators
Если встроенных валидаторов недостаточно,вы можете определить пользовательские валидаторы в соответствии с вашими потребностями.
Пользовательская проверка объявляется путем передачи функции проверки. Подробные инструкции о том, как это сделать, можно найти в документации API SchemaType#validate()
.
var userSchema = new Schema({ phone: { type: String, validate: { validator: function(v) { return /d{3}-d{3}-d{4}/.test(v); }, message: props => `${props.value} is not a valid phone number!` }, required: [true, 'User phone number required'] } }); var User = db.model('user', userSchema); var user = new User(); var error; user.phone = '555.0123'; error = user.validateSync(); assert.equal(error.errors['phone'].message, '555.0123 is not a valid phone number!'); user.phone = ''; error = user.validateSync(); assert.equal(error.errors['phone'].message, 'User phone number required'); user.phone = '201-555-0123'; error = user.validateSync(); assert.equal(error, null);
Async Пользовательские Валидаторы
Пользовательские валидаторы также могут быть асинхронными. Если ваша функция валидатора возвращает обещание (например, async
функция), мангуст будет ждать, пока это обещание не исполнится. Если возвращенное обещание отклоняется или выполняется со значением false
, Mongoose сочтет это ошибкой проверки.
const userSchema = new Schema({ name: { type: String, validate: () => Promise.reject(new Error('Oops!')) }, email: { type: String, validate: { validator: () => Promise.resolve(false), message: 'Email validation failed' } } }); const User = db.model('User', userSchema); const user = new User(); user.email = 'test@test.co'; user.name = 'test'; user.validate().catch(error => { assert.ok(error); assert.equal(error.errors['name'].message, 'Oops!'); assert.equal(error.errors['email'].message, 'Email validation failed'); });
Validation Errors
Ошибки, возвращаемые после неудачной проверки, содержат объект errors
, значения которого являются объектами ValidatorError
. У каждой ValidatorError есть свойства kind
, path
, value
и message
. ValidatorError также может иметь свойство reason
. Если в валидаторе возникла ошибка, это свойство будет содержать возникшую ошибку.
var toySchema = new Schema({ color: String, name: String }); var validator = function(value) { return /red|white|gold/i.test(value); }; toySchema.path('color').validate(validator, 'Color `{VALUE}` not valid', 'Invalid color'); toySchema.path('name').validate(function(v) { if (v !== 'Turbo Man') { throw new Error('Need to get a Turbo Man for Christmas'); } return true; }, 'Name `{VALUE}` is not valid'); var Toy = db.model('Toy', toySchema); var toy = new Toy({ color: 'Green', name: 'Power Ranger' }); toy.save(function (err) { assert.equal(err.errors.color.message, 'Color `Green` not valid'); assert.equal(err.errors.color.kind, 'Invalid color'); assert.equal(err.errors.color.path, 'color'); assert.equal(err.errors.color.value, 'Green'); assert.equal(err.errors.name.message, 'Need to get a Turbo Man for Christmas'); assert.equal(err.errors.name.value, 'Power Ranger'); assert.equal(err.errors.name.reason.message, 'Need to get a Turbo Man for Christmas'); assert.equal(err.name, 'ValidationError'); });
Cast Errors
Перед запуском валидаторов Mongoose пытается привести значения к правильному типу. Этот процесс называется преобразованием документа. Если приведение для заданного пути error.errors
объект error.errors будет содержать объект CastError
.
Приведение выполняется до проверки, и проверка не выполняется, если приведение не выполняется. Это означает, что ваши настраиваемые валидаторы могут предполагать, что v
имеет значение null
, undefined
или экземпляр типа, указанного в вашей схеме.
const vehicleSchema = new mongoose.Schema({ numWheels: { type: Number, max: 18 } }); const Vehicle = db.model('Vehicle', vehicleSchema); const doc = new Vehicle({ numWheels: 'not a number' }); const err = doc.validateSync(); err.errors['numWheels'].name; err.errors['numWheels'].message;
Требуемые валидаторы на вложенных объектах
Определить валидаторы на вложенных объектах в мангусте сложно,так как вложенные объекты не являются полноценными путями.
var personSchema = new Schema({ name: { first: String, last: String } }); assert.throws(function() { personSchema.path('name').required(true); }, /Cannot.*'required'/); var nameSchema = new Schema({ first: String, last: String }); personSchema = new Schema({ name: { type: nameSchema, required: true } }); var Person = db.model('Person', personSchema); var person = new Person(); var error = person.validateSync(); assert.ok(error.errors['name']);
© 2010 LearnBoost
Licensed under the MIT License.
https://mongoosejs.com/docs/validation.html
Mongoose
5.7
-
Быстрые запросы мангуста с наклоном.
-
Query Casting
-
Mongoose Virtuals
-
Update Validators
Validation
Before we get into the specifics of validation syntax, please keep the following rules in mind:
- Validation is defined in the SchemaType
- Validation is middleware. Mongoose registers validation as a
pre('save')
hook on every schema by default. - You can manually run validation using
doc.validate(callback)
ordoc.validateSync()
- Validators are not run on undefined values. The only exception is the
required
validator. - Validation is asynchronously recursive; when you call Model#save, sub-document validation is executed as well. If an error occurs, your Model#save callback receives it
- Validation is customizable
var schema = new Schema({ name: { type: String, required: true } }); var Cat = db.model('Cat', schema); var cat = new Cat(); cat.save(function(error) { assert.equal(error.errors['name'].message, 'Path `name` is required.'); error = cat.validateSync(); assert.equal(error.errors['name'].message, 'Path `name` is required.'); });
Built-in Validators
Mongoose has several built-in validators.
- All SchemaTypes have the built-in required validator. The required validator uses the SchemaType’s
checkRequired()
function to determine if the value satisfies the required validator. -
Numbers have
min
andmax
validators. -
Strings have
enum
,match
,minlength
, andmaxlength
validators.
Each of the validator links above provide more information about how to enable them and customize their error messages.
var breakfastSchema = new Schema({ eggs: { type: Number, min: [6, 'Too few eggs'], max: 12 }, bacon: { type: Number, required: [true, 'Why no bacon?'] }, drink: { type: String, enum: ['Coffee', 'Tea'], required: function() { return this.bacon > 3; } } }); var Breakfast = db.model('Breakfast', breakfastSchema); var badBreakfast = new Breakfast({ eggs: 2, bacon: 0, drink: 'Milk' }); var error = badBreakfast.validateSync(); assert.equal(error.errors['eggs'].message, 'Too few eggs'); assert.ok(!error.errors['bacon']); assert.equal(error.errors['drink'].message, '`Milk` is not a valid enum value for path `drink`.'); badBreakfast.bacon = 5; badBreakfast.drink = null; error = badBreakfast.validateSync(); assert.equal(error.errors['drink'].message, 'Path `drink` is required.'); badBreakfast.bacon = null; error = badBreakfast.validateSync(); assert.equal(error.errors['bacon'].message, 'Why no bacon?');
The unique
Option is Not a Validator
A common gotcha for beginners is that the unique
option for schemas is not a validator. It’s a convenient helper for building MongoDB unique indexes. See the FAQ for more information.
var uniqueUsernameSchema = new Schema({ username: { type: String, unique: true } }); var U1 = db.model('U1', uniqueUsernameSchema); var U2 = db.model('U2', uniqueUsernameSchema); var dup = [{ username: 'Val' }, { username: 'Val' }]; U1.create(dup, function(error) { }); U2.once('index', function(error) { assert.ifError(error); U2.create(dup, function(error) { assert.ok(error); assert.ok(!error.errors); assert.ok(error.message.indexOf('duplicate key error') !== -1); }); }); U2.init().then(function() { U2.create(dup, function(error) { assert.ok(error); assert.ok(!error.errors); assert.ok(error.message.indexOf('duplicate key error') !== -1); }); });
Custom Validators
If the built-in validators aren’t enough, you can define custom validators to suit your needs.
Custom validation is declared by passing a validation function. You can find detailed instructions on how to do this in the SchemaType#validate()
API docs.
var userSchema = new Schema({ phone: { type: String, validate: { validator: function(v) { return /d{3}-d{3}-d{4}/.test(v); }, message: props => `${props.value} is not a valid phone number!` }, required: [true, 'User phone number required'] } }); var User = db.model('user', userSchema); var user = new User(); var error; user.phone = '555.0123'; error = user.validateSync(); assert.equal(error.errors['phone'].message, '555.0123 is not a valid phone number!'); user.phone = ''; error = user.validateSync(); assert.equal(error.errors['phone'].message, 'User phone number required'); user.phone = '201-555-0123'; error = user.validateSync(); assert.equal(error, null);
Async Custom Validators
Custom validators can also be asynchronous. If your validator function returns a promise (like an async
function), mongoose will wait for that promise to settle. If the returned promise rejects, or fulfills with the value false
, Mongoose will consider that a validation error.
const userSchema = new Schema({ name: { type: String, validate: () => Promise.reject(new Error('Oops!')) }, email: { type: String, validate: { validator: () => Promise.resolve(false), message: 'Email validation failed' } } }); const User = db.model('User', userSchema); const user = new User(); user.email = '[email protected]'; user.name = 'test'; user.validate().catch(error => { assert.ok(error); assert.equal(error.errors['name'].message, 'Oops!'); assert.equal(error.errors['email'].message, 'Email validation failed'); });
Validation Errors
Errors returned after failed validation contain an errors
object whose values are ValidatorError
objects. Each ValidatorError has kind
, path
, value
, and message
properties. A ValidatorError also may have a reason
property. If an error was thrown in the validator, this property will contain the error that was thrown.
var toySchema = new Schema({ color: String, name: String }); var validator = function(value) { return /red|white|gold/i.test(value); }; toySchema.path('color').validate(validator, 'Color `{VALUE}` not valid', 'Invalid color'); toySchema.path('name').validate(function(v) { if (v !== 'Turbo Man') { throw new Error('Need to get a Turbo Man for Christmas'); } return true; }, 'Name `{VALUE}` is not valid'); var Toy = db.model('Toy', toySchema); var toy = new Toy({ color: 'Green', name: 'Power Ranger' }); toy.save(function (err) { assert.equal(err.errors.color.message, 'Color `Green` not valid'); assert.equal(err.errors.color.kind, 'Invalid color'); assert.equal(err.errors.color.path, 'color'); assert.equal(err.errors.color.value, 'Green'); assert.equal(err.errors.name.message, 'Need to get a Turbo Man for Christmas'); assert.equal(err.errors.name.value, 'Power Ranger'); assert.equal(err.errors.name.reason.message, 'Need to get a Turbo Man for Christmas'); assert.equal(err.name, 'ValidationError'); });
Cast Errors
Before running validators, Mongoose attempts to coerce values to the correct type. This process is called casting the document. If casting fails for a given path, the error.errors
object will contain a CastError
object.
Casting runs before validation, and validation does not run if casting fails. That means your custom validators may assume v
is null
, undefined
, or an instance of the type specified in your schema.
const vehicleSchema = new mongoose.Schema({ numWheels: { type: Number, max: 18 } }); const Vehicle = db.model('Vehicle', vehicleSchema); const doc = new Vehicle({ numWheels: 'not a number' }); const err = doc.validateSync(); err.errors['numWheels'].name; err.errors['numWheels'].message;
Required Validators On Nested Objects
Defining validators on nested objects in mongoose is tricky, because nested objects are not fully fledged paths.
var personSchema = new Schema({ name: { first: String, last: String } }); assert.throws(function() { personSchema.path('name').required(true); }, /Cannot.*'required'/); var nameSchema = new Schema({ first: String, last: String }); personSchema = new Schema({ name: { type: nameSchema, required: true } }); var Person = db.model('Person', personSchema); var person = new Person(); var error = person.validateSync(); assert.ok(error.errors['name']);
Update Validators
In the above examples, you learned about document validation. Mongoose also supports validation for update()
, updateOne()
, updateMany()
, and findOneAndUpdate()
operations. Update validators are off by default — you need to specify the runValidators
option.
To turn on update validators, set the runValidators
option for update()
, updateOne()
, updateMany()
, or findOneAndUpdate()
. Be careful: update validators are off by default because they have several caveats.
var toySchema = new Schema({ color: String, name: String }); var Toy = db.model('Toys', toySchema); Toy.schema.path('color').validate(function (value) { return /red|green|blue/i.test(value); }, 'Invalid color'); var opts = { runValidators: true }; Toy.updateOne({}, { color: 'not a color' }, opts, function (err) { assert.equal(err.errors.color.message, 'Invalid color'); });
Update Validators and this
There are a couple of key differences between update validators and document validators. 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.
var toySchema = new Schema({ color: String, name: String }); toySchema.path('color').validate(function(value) { if (this.name.toLowerCase().indexOf('red') !== -1) { return value !== 'red'; } return true; }); var Toy = db.model('ActionFigure', toySchema); var toy = new Toy({ color: 'red', name: 'Red Power Ranger' }); var error = toy.validateSync(); assert.ok(error.errors['color']); var update = { color: 'red', name: 'Red Power Ranger' }; var opts = { runValidators: true }; Toy.updateOne({}, update, opts, function(error) { assert.ok(error); });
The context
option
The context
option lets you set the value of this
in update validators to the underlying query.
toySchema.path('color').validate(function(value) { if (this.getUpdate().$set.name.toLowerCase().indexOf('red') !== -1) { return value === 'red'; } return true; }); var Toy = db.model('Figure', toySchema); var update = { color: 'blue', name: 'Red Power Ranger' }; var opts = { runValidators: true, context: 'query' }; Toy.updateOne({}, update, opts, function(error) { assert.ok(error.errors['color']); });
Update Validators Only Run On Updated Paths
The other key difference that update validators only run on the paths specified in the update. For instance, in the below example, because ‘name’ is not specified in the update operation, update validation will succeed.
When using update validators, required
validators only fail when you try to explicitly $unset
the key.
var kittenSchema = new Schema({ name: { type: String, required: true }, age: Number }); var Kitten = db.model('Kitten', kittenSchema); var update = { color: 'blue' }; var opts = { runValidators: true }; Kitten.updateOne({}, update, opts, function(err) { }); var unset = { $unset: { name: 1 } }; Kitten.updateOne({}, unset, opts, function(err) { assert.ok(err); assert.ok(err.errors['name']); });
Update Validators Only Run For Some Operations
One final detail worth noting: update validators only run on the following update operators:
$set
$unset
-
$push
(>= 4.8.0) -
$addToSet
(>= 4.8.0) -
$pull
(>= 4.12.0) -
$pullAll
(>= 4.12.0)
For instance, the below update will succeed, regardless of the value of number
, because update validators ignore $inc
.
Also, $push
, $addToSet
, $pull
, and $pullAll
validation does not run any validation on the array itself, only individual elements of the array.
var testSchema = new Schema({ number: { type: Number, max: 0 }, arr: [{ message: { type: String, maxlength: 10 } }] }); testSchema.path('arr').validate(function(v) { return v.length < 2; }); var Test = db.model('Test', testSchema); var update = { $inc: { number: 1 } }; var opts = { runValidators: true }; Test.updateOne({}, update, opts, function(error) { update = { $push: [{ message: 'hello' }, { message: 'world' }] }; Test.updateOne({}, update, opts, function(error) { }); });
On $push and $addToSet
New in 4.8.0: update validators also run on $push
and $addToSet
var testSchema = new Schema({ numbers: [{ type: Number, max: 0 }], docs: [{ name: { type: String, required: true } }] }); var Test = db.model('TestPush', testSchema); var update = { $push: { numbers: 1, docs: { name: null } } }; var opts = { runValidators: true }; Test.updateOne({}, update, opts, function(error) { assert.ok(error.errors['numbers']); assert.ok(error.errors['docs']); });
Before we get into the specifics of validation syntax, please keep the following rules in mind:
- Validation is defined in the SchemaType
- Validation is middleware. Mongoose registers validation as a
pre('save')
hook on every schema by default. - You can manually run validation using
doc.validate(callback)
ordoc.validateSync()
- Validators are not run on undefined values. The only exception is the
required
validator. - Validation is asynchronously recursive; when you call Model#save, sub-document validation is executed as well. If an error occurs, your Model#save callback receives it
- Validation is customizable
var schema = new Schema({
name: {
type: String,
required: true
}
});
var Cat = db.model('Cat', schema);
// This cat has no name :(
var cat = new Cat();
cat.save(function(error) {
assert.equal(error.errors['name'].message,
'Path `name` is required.');
error = cat.validateSync();
assert.equal(error.errors['name'].message,
'Path `name` is required.');
});
Built-in Validators
Mongoose has several built-in validators.
- All SchemaTypes have the built-in required validator. The required validator uses the SchemaType’s
checkRequired()
function to determine if the value satisfies the required validator. - Numbers have min and max validators.
- Strings have enum, match, maxlength and minlength validators.
Each of the validator links above provide more information about how to enable them and customize their error messages.
var breakfastSchema = new Schema({
eggs: {
type: Number,
min: [6, 'Too few eggs'],
max: 12
},
bacon: {
type: Number,
required: [true, 'Why no bacon?']
},
drink: {
type: String,
enum: ['Coffee', 'Tea']
}
});
var Breakfast = db.model('Breakfast', breakfastSchema);
var badBreakfast = new Breakfast({
eggs: 2,
bacon: 0,
drink: 'Milk'
});
var error = badBreakfast.validateSync();
assert.equal(error.errors['eggs'].message,
'Too few eggs');
assert.ok(!error.errors['bacon']);
assert.equal(error.errors['drink'].message,
'`Milk` is not a valid enum value for path `drink`.');
badBreakfast.bacon = null;
error = badBreakfast.validateSync();
assert.equal(error.errors['bacon'].message, 'Why no bacon?');
Custom Validators
If the built-in validators aren’t enough, you can define custom validators
to suit your needs.
Custom validation is declared by passing a validation function.
You can find detailed instructions on how to do this in the
SchemaType#validate()
API docs.
var userSchema = new Schema({
phone: {
type: String,
validate: {
validator: function(v) {
return /d{3}-d{3}-d{4}/.test(v);
},
message: '{VALUE} is not a valid phone number!'
},
required: [true, 'User phone number required']
}
});
var User = db.model('user', userSchema);
var user = new User();
var error;
user.phone = '555.0123';
error = user.validateSync();
assert.equal(error.errors['phone'].message,
'555.0123 is not a valid phone number!');
user.phone = '';
error = user.validateSync();
assert.equal(error.errors['phone'].message,
'User phone number required');
user.phone = '201-555-0123';
// Validation succeeds! Phone number is defined
// and fits `DDD-DDD-DDDD`
error = user.validateSync();
assert.equal(error, null);
Async Custom Validators
Custom validators can also be asynchronous. If your validator function
takes 2 arguments, mongoose will assume the 2nd argument is a callback.
Even if you don’t want to use asynchronous validators, be careful,
because mongoose 4 will assume that all functions that take 2
arguments are asynchronous, like the
validator.isEmail
function
var userSchema = new Schema({
phone: {
type: String,
validate: {
validator: function(v, cb) {
setTimeout(function() {
cb(/d{3}-d{3}-d{4}/.test(v));
}, 5);
},
message: '{VALUE} is not a valid phone number!'
},
required: [true, 'User phone number required']
}
});
var User = db.model('User', userSchema);
var user = new User();
var error;
user.phone = '555.0123';
user.validate(function(error) {
assert.ok(error);
assert.equal(error.errors['phone'].message,
'555.0123 is not a valid phone number!');
});
Validation Errors
Errors returned after failed validation contain an errors
object
holding the actual ValidatorError
objects. Each
ValidatorError has kind
, path
,
value
, and message
properties.
var toySchema = new Schema({
color: String,
name: String
});
var Toy = db.model('Toy', toySchema);
var validator = function (value) {
return /blue|green|white|red|orange|periwinkle/i.test(value);
};
Toy.schema.path('color').validate(validator,
'Color `{VALUE}` not valid', 'Invalid color');
var toy = new Toy({ color: 'grease'});
toy.save(function (err) {
// err is our ValidationError object
// err.errors.color is a ValidatorError object
assert.equal(err.errors.color.message, 'Color `grease` not valid');
assert.equal(err.errors.color.kind, 'Invalid color');
assert.equal(err.errors.color.path, 'color');
assert.equal(err.errors.color.value, 'grease');
assert.equal(err.name, 'ValidationError');
});
Required Validators On Nested Objects
Defining validators on nested objects in mongoose is tricky, because
nested objects are not fully fledged paths.
var personSchema = new Schema({
name: {
first: String,
last: String
}
});
assert.throws(function() {
// This throws an error, because 'name' isn't a full fledged path
personSchema.path('name').required(true);
}, /Cannot.*'required'/);
// To make a nested object required, use a single nested schema
var nameSchema = new Schema({
first: String,
last: String
});
personSchema = new Schema({
name: {
type: nameSchema,
required: true
}
});
var Person = db.model('Person', personSchema);
var person = new Person();
var error = person.validateSync();
assert.ok(error.errors['name']);
Update Validators
In the above examples, you learned about document validation. Mongoose also
supports validation for update()
and findOneAndUpdate()
operations.
In Mongoose 4.x, update validators are off by default — you need to specify
the runValidators
option.
To turn on update validators, set the runValidators
option for
update()
or findOneAndUpdate()
. Be careful: update validators
are off by default because they have several caveats.
var toySchema = new Schema({
color: String,
name: String
});
var Toy = db.model('Toys', toySchema);
Toy.schema.path('color').validate(function (value) {
return /blue|green|white|red|orange|periwinkle/i.test(value);
}, 'Invalid color');
var opts = { runValidators: true };
Toy.update({}, { color: 'bacon' }, opts, function (err) {
assert.equal(err.errors.color.message,
'Invalid color');
});
Update Validators and this
There are a couple of key differences between update validators and
document validators. 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.
var toySchema = new Schema({
color: String,
name: String
});
toySchema.path('color').validate(function(value) {
// When running in `validate()` or `validateSync()`, the
// validator can access the document using `this`.
// Does **not** work with update validators.
if (this.name.toLowerCase().indexOf('red') !== -1) {
return value !== 'red';
}
return true;
});
var Toy = db.model('ActionFigure', toySchema);
var toy = new Toy({ color: 'red', name: 'Red Power Ranger' });
var error = toy.validateSync();
assert.ok(error.errors['color']);
var update = { color: 'red', name: 'Red Power Ranger' };
var opts = { runValidators: true };
Toy.update({}, update, opts, function(error) {
// The update validator throws an error:
// "TypeError: Cannot read property 'toLowerCase' of undefined",
// because `this` is **not** the document being updated when using
// update validators
assert.ok(error);
});
The context
option
The context
option lets you set the value of this
in update validators
to the underlying query.
toySchema.path('color').validate(function(value) {
// When running update validators with the `context` option set to
// 'query', `this` refers to the query object.
if (this.getUpdate().$set.name.toLowerCase().indexOf('red') !== -1) {
return value === 'red';
}
return true;
});
var Toy = db.model('Figure', toySchema);
var update = { color: 'blue', name: 'Red Power Ranger' };
// Note the context option
var opts = { runValidators: true, context: 'query' };
Toy.update({}, update, opts, function(error) {
assert.ok(error.errors['color']);
});
Update Validator Paths
The other key difference that update validators only run on the paths
specified in the update. For instance, in the below example, because
‘name’ is not specified in the update operation, update validation will
succeed.
When using update validators, required
validators only fail when
you try to explicitly $unset
the key.
var kittenSchema = new Schema({
name: { type: String, required: true },
age: Number
});
var Kitten = db.model('Kitten', kittenSchema);
var update = { color: 'blue' };
var opts = { runValidators: true };
Kitten.update({}, update, opts, function(err) {
// Operation succeeds despite the fact that 'name' is not specified
});
var unset = { $unset: { name: 1 } };
Kitten.update({}, unset, opts, function(err) {
// Operation fails because 'name' is required
assert.ok(err);
assert.ok(err.errors['name']);
});
Update Validators Only Run On Specified Paths
One final detail worth noting: update validators only run on $set
and $unset
operations. For instance, the below update will succeed,
regardless of the value of number
.
var testSchema = new Schema({
number: { type: Number, max: 0 },
});
var Test = db.model('Test', testSchema);
var update = { $inc: { number: 1 } };
var opts = { runValidators: true };
Test.update({}, update, opts, function(error) {
// There will never be a validation error here
});
Just like all other frameworks, Mongoose provides a way to validate data before you save that data to a database. Data validation is important to make sure that “bad” data does not get persisted in your application. A benefit of using Mongoose when inserting data into MongoDB is its built-in support for data schemas, and the automatic validation of data when it is persisted. You would not get this without Mongoose. Mongoose’s validators are easy to configure. When defining the schema, a developer can add extra options to the property that should be validated. Let’s look at some basic examples of validation in Mongoose now.
Getting Started With required
Right now we have a schema in Mongoose which has no validation. In other words, all the properties defined below are optional. If you provide each property when creating a document, great! If not, that’s great too!
const gameSchema = new mongoose.Schema({
title: String,
publisher: String,
tags: [String],
date: { type: Date, default: Date.now },
onSale: Boolean,
price: Number
});
Of course it is almost always necessary to Validate any data you want to persist. We can modify the schema to add validation like so. In the snippet below, we are making the title
of the game mandatory. It is no longer optional. We can do this with the required
property.
const gameSchema = new mongoose.Schema({
title: { type: String, required: true },
publisher: String,
tags: [String],
date: { type: Date, default: Date.now },
onSale: Boolean,
price: Number
});
With our validation in place for the title of the game, let’s try to save a game to the database without specifying a title.
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/mongo-games')
.then(() => console.log('Now connected to MongoDB!'))
.catch(err => console.error('Something went wrong', err));
const gameSchema = new mongoose.Schema({
title: { type: String, required: true },
publisher: String,
tags: [String],
date: { type: Date, default: Date.now },
onSale: Boolean,
price: Number
});
const Game = mongoose.model('Game', gameSchema);
async function saveGame() {
const game = new Game({
publisher: "Nintendo",
tags: ["adventure", "action"],
onSale: false,
price: 59.99,
});
const result = await game.save();
console.log(result);
}
saveGame();
We can run index.js using node index.js
at the terminal to test this out.
mongo-crud $node index.js (node:10176) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): ValidationError: Game validation failed: title: Path `title` is required. (node:10176) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Interesting. We get a lot of error information about an unhandled promise rejection. We can fix this by updating our logic in the saveGame()
function. The good thing however is that within the information listed we do see that the validation worked as Game validation failed: title: Path `title` is required tells us so. Let’s update the code to handle the promise correctly by implementing a try/catch block.
async function saveGame() {
const game = new Game({
publisher: "Nintendo",
tags: ["adventure", "action"],
onSale: false,
price: 59.99,
});
try {
const result = await game.save();
console.log(result);
} catch (err) {
console.log(err.message)
}
}
saveGame();
Running the index.js file now give us an easier to read message.
mongo-crud $node index.js Game validation failed: title: Path `title` is required.
Great! Validation is working. It is important to note that this example of validation in Mongoose is just that, validation in Mongoose. This has nothing to do with data validation at the database, or MongoDb level. Another thing to note is that this type of validation in Mongoose is complimentary to a validation package like Joi which we used in the node rest api tutorial. By using both validation at the REST layer and the Mongoose layer, you can ensure that faulty documents will not be persisted to the database.
More About Built In Validators
In the section above we saw how to use the required
property to make it mandatory that a user provide the title of a game when persisting to the database. You can also use a function with required
to conditionally require something. In the snippet below, we are saying that if the game is on sale, then the price is required.
const gameSchema = new mongoose.Schema({
title: { type: String, required: true },
publisher: String,
tags: [String],
date: { type: Date, default: Date.now },
onSale: Boolean,
price: {
type: Number,
required: function () { return this.onSale }
}
});
Now we can try to save a game, but we will omit the title and price to see if our validation rules are still working. Our logic to insert the game is here.
const Game = mongoose.model('Game', gameSchema);
async function saveGame() {
const game = new Game({
publisher: "Nintendo",
tags: ["adventure", "action"],
onSale: true,
});
try {
const result = await game.save();
console.log(result);
} catch (err) {
console.log(err.message)
}
}
saveGame();
Running the program shows that these rules are working well. Both price and title are required, so the game is not persisted.
mongo-crud $node index.js Game validation failed: price: Path `price` is required., title: Path `title` is required.
minlength and maxlength
In addition to making a string required, you can also specify the minimum length and maximum length it should be. Consider this schema.
const gameSchema = new mongoose.Schema({
title: {
type: String,
required: true,
minlength: 4,
maxlength: 200
},
publisher: String,
tags: [String],
date: { type: Date, default: Date.now },
onSale: Boolean,
price: {
type: Number,
required: function () { return this.onSale }
}
});
Now, lets provide a title, but with only 3 characters and see what happens.
const Game = mongoose.model('Game', gameSchema);
async function saveGame() {
const game = new Game({
title: "Pac",
publisher: "Nintendo",
tags: ["adventure", "action"],
onSale: true,
});
try {
const result = await game.save();
console.log(result);
} catch (err) {
console.log(err.message)
}
}
saveGame();
When we run the program the validator tells us that our title is too short.
mongo-crud $node index.js Game validation failed: price: Path `price` is required., title: Path `title` (`Pac`) is shorter than the minimum allowed length (4).
enum validation
When creating a game, we are assigning some tags to it. Using enum validation, we can specify the available tags one could use. Below we are saying that the tags for a game must be any of sports, racing, action, or rpg.
const gameSchema = new mongoose.Schema({
title: {
type: String,
required: true,
minlength: 4,
maxlength: 200
},
publisher: String,
tags: {
type: [String],
required: true,
enum: ['sports', 'racing', 'action', 'rpg']
},
date: { type: Date, default: Date.now },
onSale: Boolean,
price: {
type: Number,
required: function () { return this.onSale }
}
});
Now we try to save a game using a tag we have not accounted for in the enum validation, adventure.
const Game = mongoose.model('Game', gameSchema);
async function saveGame() {
const game = new Game({
title: "Pacman",
publisher: "Nintendo",
tags: ["adventure", "action"],
onSale: true,
price: 29.99
});
try {
const result = await game.save();
console.log(result);
} catch (err) {
console.log(err.message)
}
}
saveGame();
Sure enough, trying to insert that game into the database fails and we get the error that `adventure` is not a valid enum value for path `tags`.
mongo-crud $node index.js Game validation failed: tags.0: `adventure` is not a valid enum value for path `tags`.
Custom Validators
You may also set up a custom validator in Mongoose. Here we will modify the validation for tags such that a user must provide more than one.
const gameSchema = new mongoose.Schema({
title: {
type: String,
required: true,
minlength: 4,
maxlength: 200
},
publisher: String,
tags: {
type: [String],
validate: {
validator: function (v) {
return v.length > 1
},
message: 'You must provide more than 1 tag.'
}
},
date: { type: Date, default: Date.now },
onSale: Boolean,
price: {
type: Number,
required: function () { return this.onSale }
}
});
Now we try to save a game and only provide one tag.
const Game = mongoose.model('Game', gameSchema);
async function saveGame() {
const game = new Game({
title: "Pacman",
publisher: "Nintendo",
tags: ["arcade"],
onSale: true,
price: 29.99
});
try {
const result = await game.save();
console.log(result);
} catch (err) {
console.log(err.message)
}
}
saveGame();
Running the program gives us the validation error we expect.
mongo-crud $node index.js Game validation failed: tags: You must provide more than 1 tag.
Async Valicators
Async validation comes into play when you need to fetch some remote data, or perform some other type of asynchronous task before persisting to the database. For this we can use an async validator. Let’s have a look at one. We’ll simulate asynchronous work with the setTimeout() function.
const gameSchema = new mongoose.Schema({
title: {
type: String,
required: true,
minlength: 4,
maxlength: 200
},
publisher: String,
tags: {
type: [String],
validate: {
isAsync: true,
validator: function (v, callback) {
// Complete async task
setTimeout(() => {
const result = v.length > 1;
callback(result);
}, 2000);
},
message: 'You must provide more than 1 tag.'
}
},
date: { type: Date, default: Date.now },
onSale: Boolean,
price: {
type: Number,
required: function () { return this.onSale }
}
});
To enable asynchronous validation, all you need to do is add the isAsync property to the validate object and set it to true
. Then you can do your async work whether that be fetching remote data, reading from the filesystem, or working with a database, and the validation will still work properly.
Mongoose Validation Examples Summary
In this tutorial on Mongoose Validation we learned that when defining a schema, you can set the type of a property to a SchemaType object. You use this object to define the validation requirements for the given property. We can add validation with code like this.
new mongoose.Schema({
name: { type: String, required: true }
})
Validation logic is executed by Mongoose before a document can be saved to the database. It is also possible to trigger it manually by calling the validate() method. Some of the Built-in validators include:
- Strings:
minlength
,maxlength
,match
,enum
- Numbers:
min
,max
- Dates:
min
,max
- All types:
required
To set up custom validation, you may set up the validate object and use a function in the validate property.
tags: [
type: Array,
validate: {
validator: function (v) { return v && v.length > 0; },
message: 'A game should have at least 1 tag.'
}
]
When talking to a database or a remote service to perform the validation, it is required that you use an async validator. You enable this with the isAsync
property set to true
.
validate: {
isAsync: true
validator: function(v, callback) {
// Do the validation, when the result is ready, call the callback
callback(isValid);
}
}
Some other useful SchemaType properties include:
- Strings:
lowercase
,uppercase
,trim
- All types:
get
,set
(to define a custom getter/setter)
Happy Validating!
- Назад
- Обзор: Express Nodejs
- Далее
В этой статье даётся краткое введение в базы данных, и методика их использования в приложениях Node/Express. Затем мы покажем, как можно использовать Mongoose для доступа к базе данных веб-сайта LocalLibrary. Мы объясним, как объявляются схемы и модели объектов, укажем основные типы полей, и методику базовой валидации. В статье также кратко показаны основные методы доступа к данным модели.
Предварительные сведения: | Express Tutorial Part 2: Creating a skeleton website |
---|---|
Цель: | Уметь спроектировать и создать свои модели, используя Mongoose. |
Обзор
Сотрудники библиотеки будут использовать сайт Local Library для хранения информации о книгах и абонентах, а абоненты библиотеки будут использовать его для просмотра и поиска книг, для получения информации о доступных копиях, для резервирования или одалживания книг. Чтобы эффективно хранить и извлекать информацию, мы будем хранить её в базе данных.
Express-приложения могут использовать различные базы данных, и есть несколько подходов, которые можно использовать для выполнения операций Create, Read, Update and Delete (CRUD) (создать, прочесть, обновить, удалить). В руководстве дан краткий обзор некоторых доступных опций, и детально рассмотрены некоторые механизмы работы.
Какие базы данных можно использовать?
*Express-*приложение может использовать любые базы данных, поддерживаемые Node (сам по себе Express не определяет каких-либо конкретных дополнительных свойств и требований для управления базами данных). Есть много популярных вариантов — PostgreSQL, MySQL, Redis, SQLite, и MongoDB.
При выборе базы данных следует учитывать такие факторы как время разработки, время обучения, простота репликации и копирования, расходы, поддержка сообщества и т. д. Хотя нет единственной «лучшей» базы данных, почти любое из популярных решений будет приемлемым для сайта малого и среднего размера, такого как наша Local Library.
Более подробно о вариантах смотрите в: Database integration (Express docs).
Каков наилучший способ взаимодействия с базой данных?
Существует два подхода при работе с базой данных:
- Использование родного языка запросов баз данных (т.е. SQL)
- Использование объектной модели данных (ODM) или объектно-реляционной модели (ORM). ODM / ORM представляют данные веб-сайта как объекты JavaScript, которые затем отображаются на поддерживающую базу данных. Некоторые ORM привязаны к определённой базе данных, тогда как другие не зависят от конкретной базы данных.
Наилучшую производительность можно получить с помощью SQL или другого языка запросов, поддерживаемого базой данных. Объектные модели (ODM) часто медленнее, потому что требуют перевода объектов в формат базы данных, при этом не обязательно будут использованы наиболее эффективные запросы к базе данных (особенно, если ODM предназначена для различных баз данных и должна идти на большие компромиссы в смысле поддержки тех или иных функций базы данных).
Преимущество применения ORM состоит в том, что программисты могут сосредоточиться на объектах JavaScript, а не на семантике базы данных — особенно, если требуется работать с разными базами данных (на одном или разных веб-сайтах). Они также дают очевидное место для валидации и проверки данных.
Примечание: Совет: Применение ODM / ORMs часто приводит к снижению затрат на разработку и обслуживание! Если вы не очень хорошо знакомы с языком запросов базы данных или если производительность не имеет первостепенного значения, следует серьёзно рассмотреть возможность применения ODM.
Какую модель ORM/ODM следует использовать?
Есть много ODM/ORM доступных решений на сайте менеджера пакетов NPM (проверьте теги по подгруппе odm и orm).
Популярные решения на момент написания статьи:
- Mongoose: — это средство моделирование объектов базы данных MongoDB, предназначенное для асинхронной работы.
- Waterline: ORM фреймворка Sails (основан на Express) . Она предоставляет единый API для доступа к множеству баз данных, в том числе Redis, mySQL, LDAP, MongoDB, и Postgres.
- Bookshelf: поддерживает как promise- так и традиционные callback- интерфейсы, поддержка транзакций, eager/nested-eager relation loading, полиморфные ассоциации, и поддержка, один к одному, один ко многим, и многие ко многим. Работает с PostgreSQL, MySQL, и SQLite3.
- Objection: Делает настолько лёгким, насколько возможно, использование всей мощи SQL и движка базы данных ( поддерживает SQLite3, Postgres, и MySQL).
- Sequelize: Основанная на промисах ORM для Node.js и io.js. Поддерживает диалекты PostgreSQL, MySQL, MariaDB, SQLite и MSSQL, обладает надёжной поддержкой транзакций, отношений, чтения копий и т.д.
- Node ORM2 — это OR менеджер для NodeJS. Поддерживает MySQL, SQLite и Progress, помогает работать с БД, используя объектный подход.
- JugglingDB — это кросс-ДБ ORM для NodeJS, обеспечивающая общий интерфейс для доступа к наиболее популярным форматам БД. Поддерживает MySQL, SQLite3, Postgres, MongoDB, Redis и хранение данных в памяти js (собственный движок, только для тестирования).
Как правило, при выборе решения следует учитывать как предоставляемые функции, так и «деятельность сообщества» ( загрузки, вклад, отчёты об ошибках, качество документации, и т.д. ) . На момент написания статьи Mongoose являлась очень популярной ORM, и разумно, если вы выбрали MongoDB.
Применение Mongoose и MongoDb для LocalLibrary
В примере LocalLibrary (и до конца раздела) мы будем использовать Mongoose ODM для доступа к данным нашей библиотеки. Mongoose является интерфейсом для MongoDB, NoSQL-базы данных с открытым исходным кодом, в которой использована документов-ориентированная модель данных. В MongoDB «коллекции» и «документы» — это аналоги «таблиц» и «строк» в реляционных БД.
Это сочетание ODM и БД весьма популярно в сообществе Node, частично потому, что система хранения документов и запросов очень похожа на JSON и поэтому знакома разработчикам JavaScript.
Примечание: Совет: Не обязательно знать MongoDB, чтобы использовать Mongoose, хотя документацию Mongoose легче использовать и понимать, если вы уже знакомы с MongoDB.
Далее показано, как определить и получить доступ к схеме и моделям Mongoose для примера веб-сайта LocalLibrary.
Проектирование моделей LocalLibrary
Прежде чем начинать писать код моделей, стоит обдумать, какие данные нам нужно хранить, и каковы отношения между разными объектами.
Мы знаем, что нужно хранить информацию о книгах (название, резюме (краткое описание), автор, жанр, ISBN (Международный стандартный книжный номер) ) и что может быть несколько доступных экземпляров (с уникальными идентификаторами, статусом наличия и т. д.). Может потребоваться хранить больше информации об авторе (не только имя, т.к. могут быть авторы с одинаковыми или похожими именами). Мы хотим иметь возможность сортировать данные по названиям книг, по авторам, по жанрам и категориям.
При проектировании моделей имеет смысл иметь отдельные модели для каждого «объекта» (группы связанных данных). В этом случае очевидными объектами являются книги, экземпляры книг и авторы.
Можно также использовать модели для представления параметров списка выбора (например, как выпадающий список вариантов), вместо жёсткого кодирования выбора на самом веб-сайте — рекомендуется, когда не все параметры известны или могут быть изменены. Явный кандидат для модели такого типа — это жанр книги (например, «Научная фантастика», «Французская поэзия» и т.д.),
Как только мы определились с моделями и полями, следует подумать об отношениях между ними.
С учётом сказанного, UML-диаграмма связей (см. ниже) показывает модели, которые представлены как прямоугольники. Мы решили, что создадим модели для книги (общие сведения о книге), для экземпляра книги (состояние отдельных физических копий книги, доступных в системе) и для автора. Кроме того, у нас будет модель для жанра, чтобы эти значения можно было создавать динамически. Решено не создавать модель для BookInstance:status
— мы пропишем в коде необходимые значения, потому что не ожидаем их изменения. На элементах диаграммы показаны имя модели, имена и типы полей, имена методов и типы их результатов .
Также показаны отношения между моделями, включая множественные отношения. Числа на линиях связи показывают максимум и минимум моделей, участвующих отношении. Например, линия между Book
и Genre
показывает, что Book
и Genre
связаны. Числа на этой линии рядом с моделью Book
показывают, что жанр может быть связан с любым количеством книг, а числа на другом конце линии рядом с Genre
отмечают, что книга может быть связана с любым количеством жанров.
Примечание: Как показано в Учебнике по Mongoose ниже, часто лучше иметь поле, определяющее отношение между документами (моделями), только в одной модели (обратное отношение можно найти по присвоенному идентификатору _id
в другой модели). Ниже мы предпочли задать отношения между Book/Genre и между Book/Author в схеме Book, а отношение между Book/BookInstance — в схеме BookInstance. Этот выбор в некотором смысле был произвольным — таким же хорошим мог бы быть выбор другого поля в другой схеме.
Примечание: В следующем разделе дан базовый учебник, в котором объясняется, как задавать и как использовать модели. При чтении обратите внимание, как будут создаваться модели, приведённые на диаграмме.
Учебник по Mongoose
В этом разделе кратко описано как подключиться к базе MongoDB с помощью Mongoose, как определить схемы и модели, как сформировать основные запросы.
**Примечание:**На этот учебник значительно повлияло руководство Mongoose quick start на npm и официальная документация.
Установка Mongoose и MongoDB
Mongoose устанавливается в ваш проект (package.json) как и другие зависимости — при помощи NPM. Команда установки (выполняется из каталога проекта):
Установка Mongoose добавит все зависимости, включая драйвер MongoDB, но не установит саму базу данных. При желании установить сервер MongoDB локально, можно скачать установочный файл здесь для своей операционной системы и установить его. Также можно использовать облако MongoDB.
Примечание: В примере для хранения базы данных мы используем облачный сервис sandbox tier («песочницу»). Это удобно для разработки и имеет смысл для руководства, потому что такой подход делает «установку» базы данных независимой от операционной системы (база данных как веб-сервис — это также подход, который вы можете использовать для своей базы данных, находящейся в реальной эксплуатации).
Подключение к MongoDB
Mongoose требует подключение к MongoDB. Вы можете использовать require() и подключится к локальной БД при помощи mongoose.connect(),
как показано ниже.
// Импортировать модуль mongoose
var mongoose = require('mongoose');
// Установим подключение по умолчанию
var mongoDB = 'mongodb://127.0.0.1/my_database';
mongoose.connect(mongoDB);
// Позволим Mongoose использовать глобальную библиотеку промисов
mongoose.Promise = global.Promise;
// Получение подключения по умолчанию
var db = mongoose.connection;
// Привязать подключение к событию ошибки (получать сообщения об ошибках подключения)
db.on('error', console.error.bind(console, 'MongoDB connection error:'));
При помощи mongoose.connection
можно получить стандартный объект Connection
. После подключения в экземпляре Connection
возникает событие open (открыт).
Примечание: Tip: Если необходимо создать дополнительные подключения, можно использовать mongoose.createConnection()
. При этом будут применены те же БД URI (хост, БД, порт, опции и т.д.), что и в connect()
и будет возвращён объект Connection
.
Определение и создание моделей
Модели можно создать при помощи интерфейса Schema
. Schema позволяет указать поля, которые будут в каждом документе, значения полей по умолчанию и требования по валидации. Кроме того, можно задать статические методы и методы-хелперы (от help), облегчающие работу с вашими типами данных, а также задать виртуальные свойства, которые можно использовать как и обычные поля, но без влияния на данные в самой базе данных.
Схемы «компилируются » в окончательную модель методом mongoose.model()
. После создания модели её можно использовать для поиска, создания, обновления и удаления объектов данного типа.
Примечание: Каждой модели соответствует коллекция документов в ДБ MongoDB. Документы будут содержать поля тех типов, которые заданы в модели Schema
.
Определение схем данных
Код ниже показывает, как можно задать простую схему. Сначала при помощи require()
создаётся объект mongoose, затем конструктор Schema создаёт новый экземпляр схемы, при этом различные поля задаются как параметры конструктора.
//Требуется Mongoose
var mongoose = require('mongoose');
//Определяем схему
var Schema = mongoose.Schema;
var SomeModelSchema = new Schema({
a_string: String,
a_date: Date
});
В примере созданы два поля, типа String и типа Date. В следующем разделе будут примеры полей других типов, их валидации и примеры других методов.
Создание модели
Модели создаются из схем методом mongoose.model()
:
// Определяем схему
var Schema = mongoose.Schema;
var SomeModelSchema = new Schema({
a_string: String,
a_date: Date
});
// Компилируем модель из схемы
var SomeModel = mongoose.model('SomeModel', SomeModelSchema );
Первый аргумент — уникальное имя создаваемой для модели коллекции(Mongoose создаст коллекцию для модели SomeModel), второй аргумент — схема, которая используется для создания модели.
Примечание: После создания классов модели они могут применяться для создания, обновления или удаления записей в базе, для выполнения запросов по всем записям или по их подмножествам. Как это делать, будет показано в разделе Использование моделей, и когда будут создаваться представления.
Типы схемы (поля)
Схема может содержать любое количество полей, причём каждое поле будет полем документа, хранимого в БД MongoDB. Схема-пример содержит определения многих широко используемых типов полей.
var schema = new Schema(
{
name: String,
binary: Buffer,
living: Boolean,
updated: { type: Date, default: Date.now },
age: { type: Number, min: 18, max: 65, required: true },
mixed: Schema.Types.Mixed,
_someId: Schema.Types.ObjectId,
array: [],
ofString: [String], // You can also have an array of each of the other types too.
nested: { stuff: { type: String, lowercase: true, trim: true } }
})
Большинство типов в SchemaTypes (указаны после “type:” или после имён полей) достаточно очевидны. Исключения:
ObjectId
: Представляет отдельный экземпляр модели в БД. Например, book может ссылаться на объект- автора. Поле будет содержать уникальный идентификатор (_id
) отдельного объекта. При необходимости использования этой информации применяют методpopulate()
.- Mixed: Произвольный тип в схеме.
- []: Массив элементов. В таких моделях можно выполнять JavaScript-операции для массивов (push, pop, unshift, etc.). Выше показан пример массивы объектов неопределённого типа и массив строк, но можно использовать массив объектов любого типа.
Код содержит также два способа объявления полей:
- Имя и типполя как пара «ключ-значение» (поля
name
,binary и
living
). - Имя поля, после которого указывается объект, определяющий тип и другие возможности поля, такие как:
- значения по умолчанию.
- встроенные валидаторы (например, значения max и min) и функции-валидаторы пользователя.
- Является ли поле обязательным
- Должны ли строковые поля автоматически преобразовываться в нижний или верхний регистр, удалять ли ведущие и хвостовые пробелы (
пример:
{ type: String, lowercase: true, trim: true }
)
Дополнительная информация — в SchemaTypes (документация Mongoose).
Валидация (проверка допустимости)
Mongoose предусматривает встроенные валидаторы, валидаторы пользователя, синхронные и асинхронные валидаторы. Во всех случаях можно задать допустимые диапазоны или значения, а также сообщения об ошибках при нарушении условий валидации.
Встроенные валидаторы включают:
- Все SchemaTypes имеют встроенный валидатор required, который определяет, должно ли поле быть заданным перед сохранением документа.
- Числа имеют валидаторы min и max.
- Строки имеют:
- enum (перечисления): задают множество допустимых для поля значений.
- match (соответствия)): задают регулярное выражение, которому должна соответствовать строка.
- maxlength, minlength -максимальная и минимальная длина строки.
Пример ниже (с небольшими изменениями из документации Mongoose) показывает, как задать некоторые валидаторы и сообщения об ошибках:
var breakfastSchema = new Schema({
eggs: {
type: Number,
min: [6, 'Too few eggs'],
max: 12
required: [true, 'Why no eggs?']
},
drink: {
type: String,
enum: ['Coffee', 'Tea', 'Water',]
}
});
Подробная информация по валидации полей — в разделе Validation (документация Mongoose).
Виртуальные свойства
Виртуальные свойства — это свойства документа, которые можно читать (get) и задавать (set), но которые не хранятся в MongoDB. Методы «геттеры» полезны для форматирования и соединения полей, а «сеттеры» применяют для декомпозиции отдельных значений на несколько частей перед сохранением в БД. Пример из документации собирает (и разбирает) виртуальное свойство «полное имя» из полей «имя» и «фамилия», что удобнее, чем конструировать полное имя каждый раз, когда оно используется в шаблоне.
Примечание: Замечание: В библиотеке виртуальное свойство будет применено для определения уникального URL каждой записи в модели по пути и по значению _id
записи.
Подробная информация — в разделе Virtuals (документация Mongoose).
Методы и помощники запросов
В схеме можно также задать методы экземпляра (instance methods), статические (static) методы и помощники запросов. Статические методы и методы экземпляра аналогичны, но различаются тем, что методы экземпляра связаны с конкретной записью и имеют доступ к текущему объекту. Помощники запросов позволяют расширить API построителя цепочек запросов (например, можно добавить запрос «byName» («по имени») в дополнение к методам find()
, findOne()
и findById()
).
Применение моделей
Подготовленную схему можно использовать для создания моделей. Модель представляет коллекцию документов в базе данных, в которой можно выполнять поиск, тогда как экземпляры модели представляют отдельные документы, которые можно сохранять и извлекать.
Ниже предлагается краткий обзор. Более подробно смотрите в Models (документация Mongoose).
Создание и изменение документов
Чтобы создать запись, следует определить экземпляр модели и вызвать метод save()
. В примере ниже SomeModel — это модель с единственным полем «name», которую мы создадим из нашей схемы.
// Создать экземпляр модели SomeModel
var awesome_instance = new SomeModel({ name: 'awesome' });
// Сохранить новый экземпляр, передав callback
awesome_instance.save(function (err) {
if (err) return handleError(err);
// сохранили!
});
Создание записей (а также обновления, удаления и запросы) — это асинхронные операции, поэтому следует предусмотреть колбэк-функцию, которая будет вызвана при завершении операции. В API используется соглашение о первом аргументе, согласно которому первый аргумент колбэк-функции должен быть значением ошибки (или null). Если API возвращает некоторый результат, он должен быть вторым аргументом.
Можно использовать метод create()
для создании экземпляра модели при его сохранении. Тогда колбэк-функция вернёт ошибку (или null) как первый аргумент и только что созданный экземпляр как второй аргумент.
SomeModel.create({ name: 'also_awesome' }, function (err, awesome_instance) {
if (err) return handleError(err);
// сохранили!
});
Каждая модель ассоциирована с соединением (с соединением по умолчанию, если используется mongoose.model()
). Следует создать новое соединение и вызвать для него .model()
, чтобы создать документ в другой базе данных.
Поля в новой записи могут быть получены и изменены с применением dot (точка)-синтаксиса. Для сохранения изменений служат методы save()
и update()
.
// Доступ к полям модели в dot-нотации
console.log(awesome_instance.name); //вывод в консоль 'also_awesome'
// Изменить запись, модифицируя поля, потом вызвать save().
awesome_instance.name="New cool name";
awesome_instance.save(function (err) {
if (err) return handleError(err); // сохранили!
});
Поиск записей
При поиске записей методами запросов, условия поиска следует задавать как документ JSON. Приведённый фрагмент кода (ниже) показывает, как в БД найти имена (name) и возраст (age) всех спортсменов-теннисистов. Соответствие будет определяться по одному полю (sport), но можно добавить критерии поиска, задав, например, регулярное выражение, или удалить все критерии, чтобы получить список всех спортсменов.
var Athlete = mongoose.model('Athlete', yourSchema);
// найти всех теннисистов, выбирать поля 'name' и 'age'
Athlete.find({ 'sport': 'Tennis' }, 'name age', function (err, athletes) {
if (err) return handleError(err);
// 'athletes' содержит список спортсменов, соответствующих критерию.
})
Если задать колбэк-функцию так, как показано выше, запрос будет выполнен немедленно. Однако колбэк-функция будет вызвана только после завершения поиска.
Примечание: Все колбэк-функции в Mongoose используют образец callback(error, result)
. Если при выполнении запроса возникает ошибка, параметр error
будет содержать объект error, а result
будет null. При успешном запросе параметр error
будет null, а result
будет содержать результат запроса.
Если не задать колбэк-функцию, API вернёт переменную типа Query. Можно использовать объект запроса, чтобы создать и выполнить свой запрос (с колбэк-функцией) позже, при помощи метода exec()
.
// найти всех теннисистов
var query = Athlete.find({ 'sport': 'Tennis' });
// выбрать поля 'name' и 'age'
query.select('name age');
// ограничить результат 5 элементами
query.limit(5);
// сортировать по возрасту
query.sort({ age: -1 });
// выполнить запрос позже
query.exec(function (err, athletes) {
if (err) return handleError(err);
// athletes содержит упорядоченный список из 5 теннисистов
})
Выше условия поиска были заданы в методе find()
. Можно также использовать функцию where()
, кроме того, можно соединить все части в одном запросе применением оператора dot (.) вместо того, чтобы выполнять их раздельно. Фрагмент кода (см. ниже) выполняет тот же запрос, что и предыдущий фрагмент, но с дополнительным условием для возраста.
Athlete. find(). where('sport').equals('Tennis'). where('age').gt(17).lt(50). //Дополнительное условие limit(5). sort({ age: -1 }). select('name age'). exec(callback); // callback - имя нашей колбэк-функции.
Метод find() находит все записи, удовлетворяющие условию, но часто требуется найти только одну из таких записей. Вот методы для поиска одной записи:
findById()
: Находит документ по заданному идентификаторуid
(каждый документ имеет уникальный идентификаторid
).findOne()
: Находит один документ, удовлетворяющий заданному критерию.findByIdAndRemove()
,findByIdAndUpdate()
,findOneAndRemove()
,findOneAndUpdate()
: Находит один документ поid
или по критерию, а затем или обновляет, или удаляет его. Эти методы весьма полезны для обновления или удаления записей.
Примечание: Есть также метод count()
, который определяет количество записей, соответствующих условию. Он полезен при выполнении подсчётов без фактического извлечения записей.
Запросы полезны и во многих других случаях. Дополнительная информация — в Queries (документация Mongoose).
Работа со связанными документами — загрузка
Один документ (экземпляр модели) может ссылаться на другой документ при помощи поля ObjectId
схемы, или на много других документов, используя массив идентификаторов ObjectIds
. Идентификатор соответствующей модели хранится в поле. При необходимости получить действительное содержимое связанного документа, следует использовать в запросе метод populate()
, который заменит идентификатор в запросе действительными данными.
Например, в следующей схеме определены авторы и рассказы. У каждого автора может быть несколько рассказов, которые представим массивом ссылок of ObjectId
. У каждого рассказа может быть только один автор. Ссылка «ref» (выделена жирным) указывает в схеме, какая модель должна быть связана с этим полем.
var mongoose = require('mongoose')
, Schema = mongoose.Schema
var authorSchema = Schema({
name : String,
stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
var storySchema = Schema({
author : { type: Schema.Types.ObjectId, ref: 'Author' },
title : String
});
var Story = mongoose.model('Story', storySchema);
var Author = mongoose.model('Author', authorSchema);
Можно сохранить ссылки в связанном документе, используя значение идентификатора _id
. Ниже создаётся автор, затем рассказ, и значение идентификатора id автора сохраняется в поле «author» рассказа.
var bob = new Author({ name: 'Bob Smith' });
bob.save(function (err) {
if (err) return handleError(err);
//автор Bob создан, создаём рассказ
var story = new Story({
title: "Bob goes sledding",
author: bob._id // присваиваем полю значение идентификатора Боба. Идентификатор создаётся по умолчанию!
});
story.save(function (err) {
if (err) return handleError(err);
// У Боба теперь есть рассказ!
});
});
Теперь документ «story» ссылается на автора по идентификатору документа «author». Для получения информации об авторе применяется метод populate()
(показано ниже).
Story
.findOne({ title: 'Bob goes sledding' })
.populate('author') //подменяет идентификатор автора информацией об авторе!
.exec(function (err, story) {
if (err) return handleError(err);
console.log('The author is %s', story.author.name);
// выводит "The author is Bob Smith"
});
Примечание: Внимательные читатели заметили, что автор добавлен к рассказу, но ничего не сделано, чтобы добавить рассказ к массиву рассказов stories
автора. Как же тогда получить список всех рассказов конкретного автора? Один из возможных вариантов — добавить автора в массив рассказов, но при этом пришлось бы хранить данные об авторах и рассказах в двух местах и поддерживать их актуальность.
Лучше получить _id
нашего автора author, и применить find()
для поиска этого идентификатора в поле «author» всех рассказов.
Story
.find({ author : bob._id })
.exec(function (err, stories) {
if (err) return handleError(err);
// возвращает все рассказы, у которых идентификатор Боба.
});
Это почти все, что следует знать для работы со связанными данными в нашем руководстве. Более полную информацию можно найти в Population (документация Mongoose).
Одна схема (модель) — один файл
Можно использовать любую структуру файлов при создании схем и моделей, однако мы настоятельно рекомендуем определять каждую схему модели в отдельном модуле (файле), экспортируя метод для создания модели. Пример приведён ниже:
// Файл: ./models/somemodel.js
//Требуется Mongoose
var mongoose = require('mongoose');
//Определяем схему
var Schema = mongoose.Schema;
var SomeModelSchema = new Schema({
a_string : String,
a_date : Date,
});
//экспортируется функция для содания класса модели "SomeModel"
module.exports = mongoose.model('SomeModel', SomeModelSchema );
You can then require and use the model immediately in other files. Below we show how you might use it to get all instances of the model.
//Создаём модель SomeModel просто вызовом модуля из файла
var SomeModel = require('../models/somemodel')
// Используем объект SomeModel (модель) для поиска всех записей в SomeModel
SomeModel.find(callback_function);
Установка базы данных MongoDB
Мы уже немного понимаем, что может делать Mongoose и как следует проектировать модели. Теперь самое время начать работу над сайтом LocalLibrary. Самое первое, что мы должны сделать — установить базу данных MongoDb, в которой будут храниться данные нашей библиотеки.
В этом руководстве мы будем использовать базу данных в «песочнице» («sandbox») — бесплатный облачный сервис, предоставляемый mLab. Такая база не очень подходит для промышленных веб-сайтов, поскольку не имеет избыточности, но она очень удобна для разработки и прототипирования. Мы используем её, так как она бесплатна, её легко установить, и потому что mLab — популярный поставщик базы данных как сервиса, и это может быть разумным выбором для промышленной базы данных (на данный момент другие известные возможности включают Compose, ScaleGrid и MongoDB Atlas).
Примечание: При желании можно установить БД MongoDb локально, загрузив и установив подходящие для вашей системы двоичные файлы. В этом случае приводимые ниже инструкции не изменятся, за исключением URL базы данных, который нужно будет задать для установки соединения.
Первым делом надо создать аккаунт на mLab (это бесплатно, требует только основных контактных данных и ознакомления с условиями обслуживания).
После входа в систему вы увидите главную страницу home:
- Щёлкните Create New в разделе MongoDB Deployments для создания новой БД.
-
Откроется экран Cloud Provider Selection — раздела провайдера облака.
- Выберите план SANDBOX (Free) из раздела Plan Type (тип плана).
- Выберите любого провайдера в разделе Cloud Provider (провайдер облака). Разные провайдеры обслуживают разные регионы (показаны под выбранным типом плана).
- Щёлкните кнопку Continue.
-
Откроется экран выбора региона Select Region.
- Выберите ближайший к вам регион и щёлкните кнопку Continue.
- Откроется экран Final Details для ввода названия БД.
- Введите имя новой базы —
local_library
и нажмите Continue.
- Введите имя новой базы —
-
Откроется экран подтверждения заказа Order Confirmation.
- Щёлкните Submit Order (подтвердить заказ), чтобы создать БД.
-
Вы вернётесь на главный (home) экран. Щёлкните по вновь созданной базе, чтобы открыть экран с детальной информацией. Как видно, в БД нет коллекций (данных).
На форме выше обведён URL для соединения с вашей БДthat you need to use to access your database is displayed on the form above (shown for this database circled above). Чтобы его использовать, необходимо создать пользователя БД, который позже введёт этот URL. - Щёлкните по вкладке Users и выберите кнопку Add database user (добавить пользователя БД).
-
Введите имя пользователя и пароль (дважды), затем нажмите Create (создать). Не отмечайте Make read only (только для чтения)!
Теперь БД создана, и для доступа к ней есть URL, имя пользователя и пароль. Это должно выглядеть примерно так: mongodb://your_user_namer:your_password@ds119748.mlab.com:19748/local_library
.
Установка Mongoose
Откройте окно команд и перейдите в каталог, в котором создан каркас веб-сайта Local Library. Введите команду install, чтобы установить Mongoose (и её зависимости), а также добавьте её в файл package.json, если вы ещё не сделали этого ранее, при чтении Учебника по Mongoose.
Подключение к MongoDB
Откройте /app.js (в корне проекта) и скопируйте приведённый ниже текст, в котором объявляется объект приложения Express (после строки var app = express();
). Замените строку url БД (‘insert_your_database_url_here‘) тем URL, который представляет вашу БД (т.е. используйте информацию, полученную от mLab).
//Устанавливаем соединение с mongoose
var mongoose = require('mongoose');
var mongoDB = 'insert_your_database_url_here';//замените url!!!
mongoose.connect(mongoDB);
mongoose.Promise = global.Promise;
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'MongoDB connection error:'));
Как указано ранее в Учебнике по Mongoose, этот код задаёт соединение по умолчанию с привязкой события ошибки error (так что ошибки будут выведены в консоль).
Определение схемы LocalLibrary
Мы определим отдельный модуль для каждой модели как уже обсуждалось выше. Начнём с создания каталога для моделей в корне проекта (/models), после чего создадим отдельные файлы для каждой модели:
/express-locallibrary-tutorial //the project root /models author.js book.js bookinstance.js genre.js
Скопируйте код схемы автора Author
(приведён ниже) в файл ./models/author.js . В схеме определено, что у автора есть обязательные поля имени и фамилии типа String
длиной не более 100 символов, и поля типа Date
дата рождения и дата смерти.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var AuthorSchema = new Schema(
{
first_name: {type: String, required: true, max: 100},
family_name: {type: String, required: true, max: 100},
date_of_birth: {type: Date},
date_of_death: {type: Date},
}
);
// Виртуальное свойство для полного имени автора
AuthorSchema
.virtual('name')
.get(function () {
return this.family_name + ', ' + this.first_name;
});
// Виртуальное свойство - URL автора
AuthorSchema
.virtual('url')
.get(function () {
return '/catalog/author/' + this._id;
});
//Export model
module.exports = mongoose.model('Author', AuthorSchema);
Мы объявим также в схеме AuthorSchema виртуальное свойство «url» , которое позволит получить абсолютный URL конкретного экземпляра модели — используем это свойство в шаблонах, если потребуется получить связь с конкретным автором.
Примечание: Объявить в схеме URL как виртуальные свойства — хорошая идея, т.к. URL отдельного элемента при необходимости изменения требует коррекции только в одном месте.
Сейчас связь при помощи этого URL ещё не работает, так как у нас ещё нет кода, поддерживающего маршруты для экземпляров модели. Мы построим его в следующей статье!
В конце модуля экспортируется модель.
Модель книги Book
Скопируйте код схемы Book
(приведён ниже) в файл ./models/book.js. Большая часть кода подобна коду для модели автора — объявляется схема с рядом строковых полей, с виртуальным свойством URL для получения URL конкретных книг, затем модель экспортируется.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var BookSchema = new Schema(
{
title: {type: String, required: true},
author: {type: Schema.ObjectId, ref: 'Author', required: true},
summary: {type: String, required: true},
isbn: {type: String, required: true},
genre: [{type: Schema.ObjectId, ref: 'Genre'}]
}
);
// Virtual for book's URL
BookSchema
.virtual('url')
.get(function () {
return '/catalog/book/' + this._id;
});
//Export model
module.exports = mongoose.model('Book', BookSchema);
Основное отличие в том, что созданы две ссылки на другие модели:
- author — это ссылка на единственный объект модели
Author
, обязательный элемент. - genre (жанр) — ссылка на массив объектов модели
Genre
. Эта модель ещё не объявлена!
Модель экземпляра книги BookInstance
Наконец, скопируйте код схемы BookInstance
(приведён ниже) в файл ./models/bookinstance.js. Схема BookInstance
представляет конкретный экземпляр книги, которую можно одолжить на время, и содержит информацию о доступности экземпляров книги, о дате возврата одолженной книги, о деталях версии или печатного экземпляра.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var BookInstanceSchema = new Schema(
{
book: { type: Schema.ObjectId, ref: 'Book', required: true }, //ссылка на книгу
imprint: {type: String, required: true},
status: {type: String, required: true, enum: ['Available', 'Maintenance', 'Loaned', 'Reserved'], default: 'Maintenance'},
due_back: {type: Date, default: Date.now}
}
);
// Virtual for bookinstance's URL
BookInstanceSchema
.virtual('url')
.get(function () {
return '/catalog/bookinstance/' + this._id;
});
//Export model
module.exports = mongoose.model('BookInstance', BookInstanceSchema);
В этой схеме новыми являются опции поля:
enum
: Позволяет указать допустимые значения строки. В нашем случае используются, чтобы задать статус доступности книги (применение enum (перечисления) означает, что мы ходим предотвратить ошибочное написание и произвольные значения статуса)default
: определяет значение статуса по умолчанию (maintenance) при создании экземпляра книги, и датуdue_back
возврата книги (now,
сейчас). Отметьте, как используется функция Date при установке даты!
Все остальное знакомо по предыдущим схемам.
Модель жанра Genre — проверьте себя!
Откройте файл ./models/genre.js и создайте схему для хранения жанра (категории книги, т.е. художественная или научная, романтика или военная история и т.д.).
Определение будет подобно другим моделям:
- В модели должно быть поле
name
типаString
для указания жанра. - Это поле должно быть обязательным, допустимый размер — от 3 до 100 символов.
- Объявите виртуальное (virtual) свойство с именем
url
для URL жанра. - Экспортируйте модель.
Тестирование — создаём элементы БД
Вот так. У нас теперь есть все модели для создания сайта!
Для тестирования моделей (и для создания примеров книг и других элементов, которые потребуются в следующих статьях) выполним независимый скрипт, который создаст элементы каждого типа:
- Загрузите (или создайте) файл populatedb.js в каталоге express-locallibrary-tutorial (на том же уровне, что и
package.json
).Примечание: Не обязательно понимать, как работает populatedb.js; он просто помещает некоторые данные в базу данных.
- Введите в корне проекта команду для установки модуля async, который потребуется скрипту populatedb.js (роль async обсудим в следующих руководствах)
- Запустите скрипт из командной строки, используя node. В качестве параметра укажите URL вашей БД MongoDB (тот самый, которым вы ранее заменили insert_your_database_url_here в файле
app.js
):node populatedb <your mongodb url>
- Скрипт должен выполниться до конца, выводя в терминал создаваемые элементы.
Примечание: Совет: Откройте свою базу на Lab. Вы сможете увидеть коллекции Book, Author, Genre, BookInstance (книги, авторы, жанры, экземпляры книг) и просмотреть содержащиеся в них документы.
Итог
В этой статье мы познакомились с БД и ОРМ (объектно-реляционными моделями) в системе Node/Express, узнали, как определяются схемы и модели Mongoose. Мы применили эти знания при проектировании и реализации моделей Book
, BookInstance
, Author
и Genre
для веб-сайта LocalLibrary.
В конце мы испытали свои модели путём создания ряда элементов (при помощи автономного скрипта). В следующей статье мы рассмотрим создание страниц, на которых будут показаны эти элементы.
Смотрите также
- Database integration Интеграция БД (документация Express)
- Mongoose website Веб-сайт Mongoose (документация Mongoose)
- Mongoose Guide Справочник Mongoose (документация Mongoose)
- Validation Валидация (документация Mongoose)
- Schema Types Типы в схемах (документация Mongoose)
- Models Модели (документация Mongoose)
- Queries Запросы (документация Mongoose)
- Population Пополнение БД (документация Mongoose)
- Назад
- Обзор: Express Nodejs
- Далее