Skip to content

Models ​

Model Definition ​

js
const Model = require('@igojs/db').Model;

const schema = {
  table:   'users',
  columns: [
    'id',
    'email',
    'password',
    'first_name',
    'last_name',
    'created_at'
  ]
};

class User extends Model(schema) {
  name() {
    return this.first_name + ' ' + this.last_name;
  }
}

module.exports = User;

The schema object defines the table name, columns, associations, and scopes.

Special column types ​

By default, columns are listed as plain strings — no type spec needed. For automatic conversion between database and JavaScript representations, declare the column as an object with type:

js
const schema = {
  table:   'users',
  columns: [
    'id',
    'first_name',
    'last_name',
    { name: 'is_validated', type: 'boolean' },
    { name: 'details_json', type: 'json', attr: 'details' },
    { name: 'pets_array', type: 'array', attr: 'pets' },
  ]
};
TypeBehavior
booleanAutomatically cast to true/false on load
jsonStringified on write, parsed on load. Access via attr name.
arrayJoined to CSV on write, split on load. Access via attr name.

Associations ​

Declare relationships in the schema with has_many and belongs_to:

js
const Project = require('./Project');
const Country = require('./Country');

const schema = {
  table: 'users',
  columns: ['id', 'country_id', 'email'],
  associations: [
    // [type, name, Model, localKey, foreignKey]
    ['has_many',   'projects', Project, 'id', 'user_id'],
    ['belongs_to', 'country',  Country, 'country_id'],
  ]
};

has_many from JSON array ​

has_many can reference an array of IDs stored as JSON:

js
const schema = {
  table: 'users',
  columns: [
    'id',
    { name: 'projects_ids_json', type: 'json', attr: 'projects_ids' },
  ],
  associations: () => ([
    ['has_many', 'projects', require('./Project'), 'projects_ids', 'id'],
  ])
};

Extra WHERE on associations ​

Filter associated records with an extra condition:

js
associations: [
  ['has_many', 'active_posts', Post, 'id', 'user_id', { active: true }],
]

See Queries for loading associations with includes() and join().

Scopes ​

Scopes add default query conditions:

js
const schema = {
  table: 'users',
  columns: ['id', 'email', 'status', 'created_at'],
  scopes: {
    default:   (query) => { query.order('`created_at` DESC'); },
    active:    (query) => { query.where({ status: 'active' }); },
  }
};

The default scope applies to all queries, including when using .scope() — named scopes stack on top of the default scope.

js
// default + active scopes both apply
const users = await User.scope('active').list();

// Remove all scopes (including default)
const all   = await User.unscope().list();

// Remove only the includes added by the default scope
const users = await User.unscope('includes').list();

// Replace default includes with specific ones
const users = await User.unscope('includes').includes('profile').list();

API Reference ​

Static Methods ​

MethodReturnsDescription
Model.create(values)instanceInsert a new record
Model.createMany(list, options?)numberInsert many records, all or nothing
Model.find(id)instance | nullFind by primary key
Model.first()instance | nullFirst record (respects default scope)
Model.last()instance | nullLast record (respects default scope)
Model.list()[instance, ...]All records
Model.count()numberCount records
Model.delete(id)—Delete by primary key
Model.deleteAll()—Delete all records
Model.update(values)—Update all records
Model.where(conditions)QueryFilter records (see Queries)
Model.whereNot(conditions)QueryExclude records
Model.select(columns)QueryCustom SELECT
Model.distinct(columns)QueryDistinct values
Model.group(columns)QueryGROUP BY
Model.order(clause)QueryORDER BY
Model.limit(n)QueryLimit results
Model.offset(n)QuerySkip results
Model.page(page, perPage)QueryPaginate
Model.includes(assocs)QueryEager load associations
Model.join(assocs)QuerySQL JOIN
Model.scope(name)QueryApply named scope
Model.unscope(...clauses)QueryRemove all scopes (no args) or specific clauses

Instance Methods ​

MethodReturnsDescription
instance.update(values)—Update this record
instance.delete()—Delete this record
instance.reload(includes?)—Refresh from database

CRUD ​

Create ​

js
const user = await User.create({
  first_name: 'John',
  last_name:  'Doe',
});
// => User { id: 1, first_name: 'John', last_name: 'Doe', ... }

If the primary key is AUTO_INCREMENT, it is set automatically on the returned instance.

create(values, options) accepts a second argument forwarded to the underlying driver query (silent, etc. — see the config page).

Create many ​

js
const count = await InvoiceLine.createMany([
  { invoice_id, label: 'Consulting', amount: 1200 },
  { invoice_id, label: 'Travel',     amount: 340 },
]);
// => 2

Each record goes through beforeCreate and gets its timestamps, like with create. Rows are inserted with one multi-row INSERT per chunk of 1,000 ({ chunkSize } changes it, within PostgreSQL's limit of 65,535 parameters per statement); when there are several chunks, they run in a transaction, so a failure leaves nothing inserted.

A column missing from a record takes its database default. createMany returns the number of rows inserted, not instances: reload them with a query if needed.

Find ​

js
const user = await User.find(id);
// Returns null if not found

First / Last ​

js
const user = await User.first();
const user = await User.last();
const user = await User.where({ status: 'active' }).first();

List ​

js
const users = await User.list();
const users = await User.where({ status: 'active' }).list();

Count ​

js
const total  = await User.count();
const active = await User.where({ status: 'active' }).count();

Update ​

js
// Update a single instance
const user = await User.find(id);
await user.update({ first_name: 'Jim' });

// Bulk update
await User.where({ country: 'France' }).update({ language: 'French' });

// Update all
await User.update({ status: 'inactive' });

Rows that each take their own values are updated one by one. For a consistent batch — all rows updated or none — wrap the loop in a transaction:

js
await transaction(async () => {
  for (const line of lines) {
    await line.update({ position: line.position, amount: line.amount });
  }
});

Delete ​

js
// Delete by ID
await User.delete(id);

// Delete an instance
const user = await User.find(id);
await user.delete();

// Bulk delete
await User.where({ status: 'banned' }).delete();

// Delete all
await User.deleteAll();

Reload ​

Refresh an instance from the database:

js
const user = await User.find(id);
await user.reload();

// Reload with associations
await user.reload('country');

Hooks ​

Override beforeCreate, beforeUpdate and beforeDelete for custom logic:

js
class User extends Model(schema) {
  async beforeCreate() {
    this.created_at = new Date();
  }

  async beforeUpdate(values) {
    values.updated_at = new Date();
  }

  async beforeDelete() {
    await Session.where({ user_id: this.id }).delete();
  }
}

Hooks only run on instance methods: User.delete(id), User.deleteAll() and query-level update()/delete() skip them.

Single Table Inheritance ​

Support polymorphic models via subclass_column:

js
const schema = {
  table: 'users',
  columns: ['id', 'type', 'email'],
  subclass_column: 'type',
  subclasses: () => ({
    admin: require('./AdminUser'),
    user:  require('./RegularUser'),
  }),
};

Queries automatically return the correct subclass based on the type column.

Released under the ISC license.