CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-objection

An SQL-friendly ORM for Node.js built on Knex.js with powerful query building, relationship handling, and JSON Schema validation

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

validation.mddocs/

Validation and Error Handling

JSON Schema validation system and comprehensive error types for database constraints.

Capabilities

Validation Error

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' } }]
    // }
  }
}

Not Found Error

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'
}

AJV Validator

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

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();
  }
}

Model Validation Hooks

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 Errors

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');
  }
}

Validation Options

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() });

Types

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

docs

expression-builders.md

graph-operations.md

index.md

model-definition.md

query-building.md

relationships.md

transactions.md

utilities.md

validation.md

tile.json