0
# Validation and Error Handling
1
2
JSON Schema validation system and comprehensive error types for database constraints.
3
4
## Capabilities
5
6
### Validation Error
7
8
Error thrown during model validation with detailed error information.
9
10
```javascript { .api }
11
/**
12
* Validation error with detailed error information
13
*/
14
class ValidationError extends Error {
15
constructor(args: CreateValidationErrorArgs);
16
17
/** HTTP status code (typically 400) */
18
statusCode: number;
19
20
/** Error message */
21
message: string;
22
23
/** Detailed validation error data */
24
data?: ErrorHash | any;
25
26
/** Error type identifier */
27
type: ValidationErrorType | string;
28
29
/** Model class that failed validation */
30
modelClass?: typeof Model;
31
}
32
33
interface CreateValidationErrorArgs {
34
statusCode?: number;
35
message?: string;
36
data?: ErrorHash | any;
37
type: ValidationErrorType | string;
38
}
39
40
type ValidationErrorType =
41
| 'ModelValidation'
42
| 'RelationExpression'
43
| 'UnallowedRelation'
44
| 'InvalidGraph';
45
46
interface ErrorHash {
47
[propertyName: string]: ValidationErrorItem[];
48
}
49
50
interface ValidationErrorItem {
51
message: string;
52
keyword: string;
53
params: object;
54
}
55
```
56
57
**Usage Examples:**
58
59
```javascript
60
const { ValidationError } = require('objection');
61
62
try {
63
await Person.query().insert({
64
firstName: '', // Required field
65
age: -5, // Invalid age
66
email: 'invalid-email' // Invalid format
67
});
68
} catch (error) {
69
if (error instanceof ValidationError) {
70
console.log('Validation failed:', error.message);
71
console.log('Status code:', error.statusCode); // 400
72
console.log('Error type:', error.type); // 'ModelValidation'
73
console.log('Field errors:', error.data);
74
// {
75
// firstName: [{ message: 'should have required property firstName', keyword: 'required', params: {} }],
76
// age: [{ message: 'should be >= 0', keyword: 'minimum', params: { limit: 0 } }],
77
// email: [{ message: 'should match format "email"', keyword: 'format', params: { format: 'email' } }]
78
// }
79
}
80
}
81
```
82
83
### Not Found Error
84
85
Error thrown when queries return no results but results are expected.
86
87
```javascript { .api }
88
/**
89
* Error for operations that expect to find records but don't
90
*/
91
class NotFoundError extends Error {
92
constructor(args: CreateNotFoundErrorArgs);
93
94
/** HTTP status code (typically 404) */
95
statusCode: number;
96
97
/** Additional error data */
98
data?: any;
99
100
/** Error type identifier */
101
type: 'NotFound';
102
103
/** Model class that was not found */
104
modelClass?: typeof Model;
105
}
106
107
interface CreateNotFoundErrorArgs {
108
statusCode?: number;
109
message?: string;
110
data?: any;
111
[key: string]: any;
112
}
113
```
114
115
**Usage Examples:**
116
117
```javascript
118
const { NotFoundError } = require('objection');
119
120
try {
121
const person = await Person.query()
122
.findById(999)
123
.throwIfNotFound();
124
} catch (error) {
125
if (error instanceof NotFoundError) {
126
console.log('Person not found');
127
console.log('Status code:', error.statusCode); // 404
128
console.log('Error type:', error.type); // 'NotFound'
129
}
130
}
131
132
// Custom not found handling
133
try {
134
const person = await Person.query()
135
.where('email', 'nonexistent@example.com')
136
.first()
137
.throwIfNotFound({ message: 'User with that email not found' });
138
} catch (error) {
139
console.log(error.message); // 'User with that email not found'
140
}
141
```
142
143
### AJV Validator
144
145
JSON Schema validator using the AJV library for comprehensive validation.
146
147
```javascript { .api }
148
/**
149
* JSON Schema validator using AJV
150
*/
151
class AjvValidator extends Validator {
152
constructor(config: AjvConfig);
153
154
/** Validate model data against JSON schema */
155
validate(args: ValidatorArgs): object;
156
157
/** Pre-validation hook */
158
beforeValidate(args: ValidatorArgs): void;
159
160
/** Post-validation hook */
161
afterValidate(args: ValidatorArgs): void;
162
}
163
164
interface AjvConfig {
165
onCreateAjv?: (ajv: Ajv) => void;
166
options?: AjvOptions;
167
}
168
169
interface ValidatorArgs {
170
ctx: ValidatorContext;
171
model: Model;
172
json: object;
173
options: ModelOptions;
174
}
175
176
interface ValidatorContext {
177
[key: string]: any;
178
}
179
```
180
181
**Usage Examples:**
182
183
```javascript
184
const { AjvValidator } = require('objection');
185
186
// Custom validator configuration
187
class Person extends Model {
188
static get tableName() {
189
return 'persons';
190
}
191
192
static createValidator() {
193
return new AjvValidator({
194
onCreateAjv: (ajv) => {
195
// Add custom formats
196
ajv.addFormat('phone', /^\d{3}-\d{3}-\d{4}$/);
197
},
198
options: {
199
allErrors: true,
200
verbose: true
201
}
202
});
203
}
204
205
static get jsonSchema() {
206
return {
207
type: 'object',
208
required: ['firstName', 'lastName', 'phone'],
209
properties: {
210
id: { type: 'integer' },
211
firstName: { type: 'string', minLength: 1, maxLength: 255 },
212
lastName: { type: 'string', minLength: 1, maxLength: 255 },
213
phone: { type: 'string', format: 'phone' },
214
age: { type: 'integer', minimum: 0, maximum: 200 },
215
email: { type: 'string', format: 'email' }
216
}
217
};
218
}
219
}
220
```
221
222
### Base Validator
223
224
Base validator class for custom validation implementations.
225
226
```javascript { .api }
227
/**
228
* Base validator class
229
*/
230
class Validator {
231
/** Pre-validation hook */
232
beforeValidate(args: ValidatorArgs): void;
233
234
/** Main validation method */
235
validate(args: ValidatorArgs): object;
236
237
/** Post-validation hook */
238
afterValidate(args: ValidatorArgs): void;
239
}
240
```
241
242
**Usage Examples:**
243
244
```javascript
245
const { Validator } = require('objection');
246
247
// Custom validator implementation
248
class CustomValidator extends Validator {
249
validate({ model, json, options }) {
250
const errors = {};
251
252
// Custom validation logic
253
if (json.firstName && json.firstName.length < 2) {
254
errors.firstName = [{
255
message: 'First name must be at least 2 characters',
256
keyword: 'minLength',
257
params: { limit: 2 }
258
}];
259
}
260
261
if (json.age && json.age < 0) {
262
errors.age = [{
263
message: 'Age cannot be negative',
264
keyword: 'minimum',
265
params: { limit: 0 }
266
}];
267
}
268
269
if (Object.keys(errors).length > 0) {
270
throw new ValidationError({
271
type: 'ModelValidation',
272
data: errors
273
});
274
}
275
276
return json;
277
}
278
}
279
280
class Person extends Model {
281
static createValidator() {
282
return new CustomValidator();
283
}
284
}
285
```
286
287
### Model Validation Hooks
288
289
Validation lifecycle hooks available on model instances.
290
291
```javascript { .api }
292
/**
293
* Pre-validation hook
294
* @param jsonSchema - JSON schema for validation
295
* @param json - Data to validate
296
* @param options - Validation options
297
* @returns Modified schema or original schema
298
*/
299
$beforeValidate(jsonSchema: object, json: object, options: ModelOptions): object;
300
301
/**
302
* Main validation method
303
* @param json - Data to validate (optional, uses model's own data if not provided)
304
* @param options - Validation options
305
* @returns Validated data
306
*/
307
$validate(json?: object, options?: ModelOptions): object;
308
309
/**
310
* Post-validation hook
311
* @param json - Validated data
312
* @param options - Validation options
313
*/
314
$afterValidate(json: object, options: ModelOptions): void;
315
```
316
317
**Usage Examples:**
318
319
```javascript
320
class Person extends Model {
321
$beforeValidate(jsonSchema, json, options) {
322
// Modify schema based on context
323
if (options.skipEmailValidation) {
324
const schema = { ...jsonSchema };
325
delete schema.properties.email.format;
326
return schema;
327
}
328
return jsonSchema;
329
}
330
331
$afterValidate(json, options) {
332
// Custom post-validation logic
333
if (json.firstName && json.lastName) {
334
this.fullName = `${json.firstName} ${json.lastName}`;
335
}
336
}
337
}
338
339
// Use validation with options
340
const person = Person.fromJson({
341
firstName: 'John',
342
lastName: 'Doe',
343
email: 'invalid-email'
344
}, { skipEmailValidation: true });
345
```
346
347
### Database Errors
348
349
Database constraint violation errors from the db-errors package.
350
351
```javascript { .api }
352
/**
353
* Base database error
354
*/
355
class DBError extends Error {
356
nativeError: Error;
357
client: string;
358
}
359
360
/**
361
* Unique constraint violation
362
*/
363
class UniqueViolationError extends ConstraintViolationError {
364
columns: string[];
365
table: string;
366
constraint: string;
367
}
368
369
/**
370
* NOT NULL constraint violation
371
*/
372
class NotNullViolationError extends ConstraintViolationError {
373
column: string;
374
table: string;
375
}
376
377
/**
378
* Foreign key constraint violation
379
*/
380
class ForeignKeyViolationError extends ConstraintViolationError {
381
table: string;
382
constraint: string;
383
}
384
385
/**
386
* Base constraint violation error
387
*/
388
class ConstraintViolationError extends DBError {}
389
390
/**
391
* Check constraint violation
392
*/
393
class CheckViolationError extends ConstraintViolationError {
394
table: string;
395
constraint: string;
396
}
397
398
/**
399
* Data-related database error
400
*/
401
class DataError extends DBError {}
402
```
403
404
**Usage Examples:**
405
406
```javascript
407
const {
408
UniqueViolationError,
409
NotNullViolationError,
410
ForeignKeyViolationError
411
} = require('objection');
412
413
try {
414
await Person.query().insert({
415
email: 'existing@example.com' // Duplicate email
416
});
417
} catch (error) {
418
if (error instanceof UniqueViolationError) {
419
console.log('Duplicate value for:', error.columns); // ['email']
420
console.log('In table:', error.table); // 'persons'
421
console.log('Constraint:', error.constraint); // 'persons_email_unique'
422
} else if (error instanceof NotNullViolationError) {
423
console.log('Missing required field:', error.column);
424
} else if (error instanceof ForeignKeyViolationError) {
425
console.log('Invalid foreign key reference');
426
}
427
}
428
```
429
430
### Validation Options
431
432
Options for controlling validation behavior.
433
434
```javascript { .api }
435
interface ModelOptions {
436
/** Skip JSON schema validation */
437
skipValidation?: boolean;
438
439
/** Perform partial validation (PATCH semantics) */
440
patch?: boolean;
441
442
/** Reference to old model data for comparison */
443
old?: object;
444
}
445
```
446
447
**Usage Examples:**
448
449
```javascript
450
// Skip validation for performance
451
const person = Person.fromJson(data, { skipValidation: true });
452
453
// Patch validation (only validate provided fields)
454
await Person.query()
455
.findById(1)
456
.patch({ age: 30 }, { patch: true });
457
458
// Validation with old data reference
459
const updated = await person.$query()
460
.patch(newData, { old: person.toJSON() });
461
```
462
463
## Types
464
465
```typescript { .api }
466
type ValidationErrorType =
467
| 'ModelValidation'
468
| 'RelationExpression'
469
| 'UnallowedRelation'
470
| 'InvalidGraph';
471
472
interface ValidationErrorItem {
473
message: string;
474
keyword: string;
475
params: object;
476
}
477
478
interface ErrorHash {
479
[propertyName: string]: ValidationErrorItem[];
480
}
481
482
interface ValidatorContext {
483
[key: string]: any;
484
}
485
486
interface ValidatorArgs {
487
ctx: ValidatorContext;
488
model: Model;
489
json: object;
490
options: ModelOptions;
491
}
492
493
interface AjvConfig {
494
onCreateAjv?: (ajv: Ajv) => void;
495
options?: AjvOptions;
496
}
497
498
interface ModelOptions {
499
patch?: boolean;
500
skipValidation?: boolean;
501
old?: object;
502
}
503
```