An SQL-friendly ORM for Node.js built on Knex.js with powerful query building, relationship handling, and JSON Schema validation
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
JSON Schema validation system and comprehensive error types for database constraints.
Error thrown during model validation with detailed error information.
/**
* Validation error with detailed error information
*/
class ValidationError extends Error {
constructor(args: CreateValidationErrorArgs);
/** HTTP status code (typically 400) */
statusCode: number;
/** Error message */
message: string;
/** Detailed validation error data */
data?: ErrorHash | any;
/** Error type identifier */
type: ValidationErrorType | string;
/** Model class that failed validation */
modelClass?: typeof Model;
}
interface CreateValidationErrorArgs {
statusCode?: number;
message?: string;
data?: ErrorHash | any;
type: ValidationErrorType | string;
}
type ValidationErrorType =
| 'ModelValidation'
| 'RelationExpression'
| 'UnallowedRelation'
| 'InvalidGraph';
interface ErrorHash {
[propertyName: string]: ValidationErrorItem[];
}
interface ValidationErrorItem {
message: string;
keyword: string;
params: object;
}Usage Examples:
const { ValidationError } = require('objection');
try {
await Person.query().insert({
firstName: '', // Required field
age: -5, // Invalid age
email: 'invalid-email' // Invalid format
});
} catch (error) {
if (error instanceof ValidationError) {
console.log('Validation failed:', error.message);
console.log('Status code:', error.statusCode); // 400
console.log('Error type:', error.type); // 'ModelValidation'
console.log('Field errors:', error.data);
// {
// firstName: [{ message: 'should have required property firstName', keyword: 'required', params: {} }],
// age: [{ message: 'should be >= 0', keyword: 'minimum', params: { limit: 0 } }],
// email: [{ message: 'should match format "email"', keyword: 'format', params: { format: 'email' } }]
// }
}
}Error thrown when queries return no results but results are expected.
/**
* Error for operations that expect to find records but don't
*/
class NotFoundError extends Error {
constructor(args: CreateNotFoundErrorArgs);
/** HTTP status code (typically 404) */
statusCode: number;
/** Additional error data */
data?: any;
/** Error type identifier */
type: 'NotFound';
/** Model class that was not found */
modelClass?: typeof Model;
}
interface CreateNotFoundErrorArgs {
statusCode?: number;
message?: string;
data?: any;
[key: string]: any;
}Usage Examples:
const { NotFoundError } = require('objection');
try {
const person = await Person.query()
.findById(999)
.throwIfNotFound();
} catch (error) {
if (error instanceof NotFoundError) {
console.log('Person not found');
console.log('Status code:', error.statusCode); // 404
console.log('Error type:', error.type); // 'NotFound'
}
}
// Custom not found handling
try {
const person = await Person.query()
.where('email', 'nonexistent@example.com')
.first()
.throwIfNotFound({ message: 'User with that email not found' });
} catch (error) {
console.log(error.message); // 'User with that email not found'
}JSON Schema validator using the AJV library for comprehensive validation.
/**
* JSON Schema validator using AJV
*/
class AjvValidator extends Validator {
constructor(config: AjvConfig);
/** Validate model data against JSON schema */
validate(args: ValidatorArgs): object;
/** Pre-validation hook */
beforeValidate(args: ValidatorArgs): void;
/** Post-validation hook */
afterValidate(args: ValidatorArgs): void;
}
interface AjvConfig {
onCreateAjv?: (ajv: Ajv) => void;
options?: AjvOptions;
}
interface ValidatorArgs {
ctx: ValidatorContext;
model: Model;
json: object;
options: ModelOptions;
}
interface ValidatorContext {
[key: string]: any;
}Usage Examples:
const { AjvValidator } = require('objection');
// Custom validator configuration
class Person extends Model {
static get tableName() {
return 'persons';
}
static createValidator() {
return new AjvValidator({
onCreateAjv: (ajv) => {
// Add custom formats
ajv.addFormat('phone', /^\d{3}-\d{3}-\d{4}$/);
},
options: {
allErrors: true,
verbose: true
}
});
}
static get jsonSchema() {
return {
type: 'object',
required: ['firstName', 'lastName', 'phone'],
properties: {
id: { type: 'integer' },
firstName: { type: 'string', minLength: 1, maxLength: 255 },
lastName: { type: 'string', minLength: 1, maxLength: 255 },
phone: { type: 'string', format: 'phone' },
age: { type: 'integer', minimum: 0, maximum: 200 },
email: { type: 'string', format: 'email' }
}
};
}
}Base validator class for custom validation implementations.
/**
* Base validator class
*/
class Validator {
/** Pre-validation hook */
beforeValidate(args: ValidatorArgs): void;
/** Main validation method */
validate(args: ValidatorArgs): object;
/** Post-validation hook */
afterValidate(args: ValidatorArgs): void;
}Usage Examples:
const { Validator } = require('objection');
// Custom validator implementation
class CustomValidator extends Validator {
validate({ model, json, options }) {
const errors = {};
// Custom validation logic
if (json.firstName && json.firstName.length < 2) {
errors.firstName = [{
message: 'First name must be at least 2 characters',
keyword: 'minLength',
params: { limit: 2 }
}];
}
if (json.age && json.age < 0) {
errors.age = [{
message: 'Age cannot be negative',
keyword: 'minimum',
params: { limit: 0 }
}];
}
if (Object.keys(errors).length > 0) {
throw new ValidationError({
type: 'ModelValidation',
data: errors
});
}
return json;
}
}
class Person extends Model {
static createValidator() {
return new CustomValidator();
}
}Validation lifecycle hooks available on model instances.
/**
* Pre-validation hook
* @param jsonSchema - JSON schema for validation
* @param json - Data to validate
* @param options - Validation options
* @returns Modified schema or original schema
*/
$beforeValidate(jsonSchema: object, json: object, options: ModelOptions): object;
/**
* Main validation method
* @param json - Data to validate (optional, uses model's own data if not provided)
* @param options - Validation options
* @returns Validated data
*/
$validate(json?: object, options?: ModelOptions): object;
/**
* Post-validation hook
* @param json - Validated data
* @param options - Validation options
*/
$afterValidate(json: object, options: ModelOptions): void;Usage Examples:
class Person extends Model {
$beforeValidate(jsonSchema, json, options) {
// Modify schema based on context
if (options.skipEmailValidation) {
const schema = { ...jsonSchema };
delete schema.properties.email.format;
return schema;
}
return jsonSchema;
}
$afterValidate(json, options) {
// Custom post-validation logic
if (json.firstName && json.lastName) {
this.fullName = `${json.firstName} ${json.lastName}`;
}
}
}
// Use validation with options
const person = Person.fromJson({
firstName: 'John',
lastName: 'Doe',
email: 'invalid-email'
}, { skipEmailValidation: true });Database constraint violation errors from the db-errors package.
/**
* Base database error
*/
class DBError extends Error {
nativeError: Error;
client: string;
}
/**
* Unique constraint violation
*/
class UniqueViolationError extends ConstraintViolationError {
columns: string[];
table: string;
constraint: string;
}
/**
* NOT NULL constraint violation
*/
class NotNullViolationError extends ConstraintViolationError {
column: string;
table: string;
}
/**
* Foreign key constraint violation
*/
class ForeignKeyViolationError extends ConstraintViolationError {
table: string;
constraint: string;
}
/**
* Base constraint violation error
*/
class ConstraintViolationError extends DBError {}
/**
* Check constraint violation
*/
class CheckViolationError extends ConstraintViolationError {
table: string;
constraint: string;
}
/**
* Data-related database error
*/
class DataError extends DBError {}Usage Examples:
const {
UniqueViolationError,
NotNullViolationError,
ForeignKeyViolationError
} = require('objection');
try {
await Person.query().insert({
email: 'existing@example.com' // Duplicate email
});
} catch (error) {
if (error instanceof UniqueViolationError) {
console.log('Duplicate value for:', error.columns); // ['email']
console.log('In table:', error.table); // 'persons'
console.log('Constraint:', error.constraint); // 'persons_email_unique'
} else if (error instanceof NotNullViolationError) {
console.log('Missing required field:', error.column);
} else if (error instanceof ForeignKeyViolationError) {
console.log('Invalid foreign key reference');
}
}Options for controlling validation behavior.
interface ModelOptions {
/** Skip JSON schema validation */
skipValidation?: boolean;
/** Perform partial validation (PATCH semantics) */
patch?: boolean;
/** Reference to old model data for comparison */
old?: object;
}Usage Examples:
// Skip validation for performance
const person = Person.fromJson(data, { skipValidation: true });
// Patch validation (only validate provided fields)
await Person.query()
.findById(1)
.patch({ age: 30 }, { patch: true });
// Validation with old data reference
const updated = await person.$query()
.patch(newData, { old: person.toJSON() });type ValidationErrorType =
| 'ModelValidation'
| 'RelationExpression'
| 'UnallowedRelation'
| 'InvalidGraph';
interface ValidationErrorItem {
message: string;
keyword: string;
params: object;
}
interface ErrorHash {
[propertyName: string]: ValidationErrorItem[];
}
interface ValidatorContext {
[key: string]: any;
}
interface ValidatorArgs {
ctx: ValidatorContext;
model: Model;
json: object;
options: ModelOptions;
}
interface AjvConfig {
onCreateAjv?: (ajv: Ajv) => void;
options?: AjvOptions;
}
interface ModelOptions {
patch?: boolean;
skipValidation?: boolean;
old?: object;
}Install with Tessl CLI
npx tessl i tessl/npm-objection