# Static Methods
# static
query()
const queryBuilder = Person.query(transactionOrKnex);
Creates a query builder for the model's table.
All query builders are created using this function, including $query
, relatedQuery
and $relatedQuery
. That means you can modify each query by overriding this method for your model class.
See the query examples section for more examples.
# Arguments
Argument | Type | Description |
---|---|---|
transactionOrKnex | object | Optional transaction or knex instance for the query. This can be used to specify a transaction or even a different database. for a query. Falsy values are ignored. |
# Return value
Type | Description |
---|---|
QueryBuilder | The created query builder |
# Examples
Read models from the database:
// Get all rows.
const people = await Person.query();
console.log("there are", people.length, "people in the database");
// Example of a more complex WHERE clause. This generates:
// SELECT "persons".*
// FROM "persons"
// WHERE ("firstName" = 'Jennifer' AND "age" < 30)
// OR ("firstName" = 'Mark' AND "age" > 30)
const marksAndJennifers = await Person.query()
.where((builder) => {
builder.where("firstName", "Jennifer").where("age", "<", 30);
})
.orWhere((builder) => {
builder.where("firstName", "Mark").where("age", ">", 30);
});
console.log(marksAndJennifers);
// Get a subset of rows and fetch related models
// for each row.
const oldPeople = await Person.query()
.where("age", ">", 60)
.withGraphFetched("children.children.movies");
console.log(
"some old person's grand child has appeared in",
oldPeople[0].children[0].children[0].movies.length,
"movies"
);
Insert models to the database:
const sylvester = await Person.query().insert({
firstName: "Sylvester",
lastName: "Stallone",
});
console.log(sylvester.fullName());
// --> 'Sylvester Stallone'.
// Batch insert. This only works on Postgresql as it is
// the only database that returns the identifiers of
// _all_ inserted rows. If you need to do batch inserts
// on other databases useknex* directly.
// (See .knexQuery() method).
const inserted = await Person.query().insert([
{ firstName: "Arnold", lastName: "Schwarzenegger" },
{ firstName: "Sylvester", lastName: "Stallone" },
]);
console.log(inserted[0].fullName()); // --> 'Arnold Schwarzenegger'
update
and patch
can be used to update models. Only difference between the mentioned methods is that update
validates the input objects using the model class's full jsonSchema and patch
ignores the required
property of the schema. Use update
when you want to update all properties of a model and patch
when only a subset should be updated.
const numUpdatedRows = await Person.query()
.update({ firstName: "Jennifer", lastName: "Lawrence", age: 35 })
.where("id", jennifer.id);
console.log(numUpdatedRows);
// This will throw assuming that `firstName` or `lastName`
// is a required property for a Person.
await Person.query().update({ age: 100 });
// This will _not_ throw.
await Person.query().patch({ age: 100 });
console.log("Everyone is now 100 years old");
Models can be deleted using the delete method. Naturally the delete query can be chained with any knex* methods:
await Person.query().delete().where("age", ">", 90);
console.log("anyone over 90 is now removed from the database");
# static
relatedQuery()
const queryBuilder = Person.relatedQuery(relationName, transactionOrKnex);
Creates a query builder that can be used to query a relation of an item (or items).
This method is best explained through examples. See the examples below and the following sections:
- relation queries
- relation subqueries
# Arguments
Argument | Type | Description |
---|---|---|
relationName | string | The name of the relation to query. |
transactionOrKnex | object | Optional transaction or knex instance for the query. This can be used to specify a transaction or even a different database for a query. Falsy values are ignored. |
# Return value
Type | Description |
---|---|
QueryBuilder | The created query builder |
# Examples
This example fetches pets
for a person with id 1. pets
is the name of the relation defined in relationMappings.
const personId = 1;
const pets = await Person.relatedQuery("pets").for(personId);
select "animals".* from "animals"
where "animals"."ownerId" = 1
Just like to any query, you can chain any methods. The following example only fetches dogs and sorts them by name:
const dogs = await Person.relatedQuery("pets")
.for(1)
.where("species", "dog")
.orderBy("name");
select "animals".* from "animals"
where "species" = 'dog'
and "animals"."ownerId" = 1
order by "name" asc
If you want to fetch dogs of multiple people in one query, you can pass an array of identifiers to the for method like this:
const dogs = await Person.relatedQuery("pets")
.for([1, 2])
.where("species", "dog")
.orderBy("name");
select "animals".* from "animals"
where "species" = 'dog'
and "animals"."ownerId" in (1, 2)
order by "name" asc
You can even give it a subquery! The following example fetches all dogs of all people named Jennifer.
// Note that there is no `await` here. This query does not get executed.
const jennifers = Person.query().where("name", "Jennifer");
// This is the only executed query in this example.
const allDogsOfAllJennifers = await Person.relatedQuery("pets")
.for(jennifers)
.where("species", "dog")
.orderBy("name");
select "animals".* from "animals"
where "species" = 'dog'
and "animals"."ownerId" in (
select "persons"."id"
from "persons"
where "name" = 'Jennifer'
)
order by "name" asc
relatedQuery
also works with relate
, unrelate
, delete
and all other query methods. The following example relates a person with id 100 to a movie with id 200 for the many-to-many relation movies
:
await Person.relatedQuery("movies").for(100).relate(200);
insert into "persons_movies" ("personId", "movieId") values (100, 200)
See more examples here.
relatedQuery
can also be used as a subquery when for
is omitted. The next example selects the count of a relation and the maximum value of another one:
const people = await Person.query().select([
"persons.*",
Person.relatedQuery("pets").count().where("species", "dog").as("dogCount"),
Person.relatedQuery("movies").max("createdAt").as("mostRecentMovieDate"),
]);
console.log(people[0].id);
console.log(people[0].dogCount);
console.log(people[0].mostRecentMovieDate);
Find models that have at least one item in a relation:
const peopleThatHavePets = await Person.query().whereExists(
Person.relatedQuery("pets")
);
Generates something like this:
select "persons".*
from "persons"
where exists (
select "pets".*
from "animals" as "pets"
where "pets"."ownerId" = "persons"."id"
)
# static
knex()
Get/Set the knex instance for a model class.
Subclasses inherit the connection. A system-wide knex instance can thus be set by calling objection.Model.knex(knex)
. This works even after subclasses have been created.
If you want to use multiple databases, you can instead pass the knex instance to each individual query or use the bindKnex method.
# Examples
Set a knex instance:
const knex = require("knex")({
client: "sqlite3",
connection: {
filename: "database.db",
},
});
Model.knex(knex);
Get the knex instance:
const knex = Person.knex();
# static
transaction()
const result = await Person.transaction(callback);
const result = await Person.transaction(trxOrKnex, callback);
Shortcut for Person.knex().transaction(callback)
.
# Arguments
Argument | Type | Description |
---|---|---|
callback | function | |
trxOrKnex | knex or Transation | Optional existing transaction or knex instance. |
# Examples
try {
const scrappy = await Person.transaction(async (trx) => {
const jennifer = await Person.query(trx).insert({
firstName: "Jennifer",
lastName: "Lawrence",
});
const scrappy = await jennifer
.$relatedQuery("pets", trx)
.insert({ name: "Scrappy" });
return scrappy;
});
console.log("Great success! Both Jennifer and Scrappy were inserted");
} catch (err) {
console.log(
"Something went wrong. Neither Jennifer nor Scrappy were inserted"
);
}
# static
startTransaction()
const trx = await Person.startTransaction(trxOrKnex);
Shortcut for objection.transaction.start(Model1.knex())
.
# Arguments
Argument | Type | Description |
---|---|---|
trxOrKnex | knex or Transation | Optional existing transaction or knex instance. |
# Examples
const trx = await Person.startTransaction();
try {
await Person.query(trx).insert(person1);
await Person.query(trx).insert(person2);
await Person.query(trx).patch(person3).where("id", person3.id);
await trx.commit();
} catch (err) {
await trx.rollback();
throw err;
}
# static
beforeFind()
class Person extends Model {
static beforeFind(args) {}
}
A hook that is executed before find queries.
See these sections for more information:
# Arguments
Argument | Type | Description |
---|---|---|
args | StaticHookArguments | The arguments |
# Return value
Type | Description |
---|---|
any | The return value is not used. |
# static
afterFind()
class Person extends Model {
static afterFind(args) {}
}
A hook that is executed after find queries.
See these sections for more information:
# Arguments
Argument | Type | Description |
---|---|---|
args | StaticHookArguments | The arguments |
# Return value
Type | Description |
---|---|
any | If the return value is not undefined , it will be used as the return value of the query. |
# static
beforeUpdate()
class Person extends Model {
static beforeUpdate(args) {}
}
A hook that is executed before update and patch queries.
See these sections for more information:
# Arguments
Argument | Type | Description |
---|---|---|
args | StaticHookArguments | The arguments |
# Return value
Type | Description |
---|---|
any | The return value is not used. |
# static
afterUpdate()
class Person extends Model {
static afterUpdate(args) {}
}
A hook that is executed after update and patch queries.
See these sections for more information:
# Arguments
Argument | Type | Description |
---|---|---|
args | StaticHookArguments | The arguments |
# Return value
Type | Description |
---|---|
any | If the return value is not undefined , it will be used as the return value of the query. |
# static
beforeInsert()
class Person extends Model {
static beforeInsert(args) {}
}
A hook that is executed before insert queries.
See these sections for more information:
# Arguments
Argument | Type | Description |
---|---|---|
args | StaticHookArguments | The arguments |
# Return value
Type | Description |
---|---|
any | The return value is not used. |
# static
afterInsert()
class Person extends Model {
static afterInsert(args) {}
}
A hook that is executed after insert queries.
See these sections for more information:
# Arguments
Argument | Type | Description |
---|---|---|
args | StaticHookArguments | The arguments |
# Return value
Type | Description |
---|---|
any | If the return value is not undefined , it will be used as the return value of the query. |
# static
beforeDelete()
class Person extends Model {
static beforeDelete(args) {}
}
A hook that is executed before delete queries.
See these sections for more information:
# Arguments
Argument | Type | Description |
---|---|---|
args | StaticHookArguments | The arguments |
# Return value
Type | Description |
---|---|
any | The return value is not used. |
# static
afterDelete()
class Person extends Model {
static afterDelete(args) {}
}
A hook that is executed after delete queries.
See these sections for more information:
# Arguments
Argument | Type | Description |
---|---|---|
args | StaticHookArguments | The arguments |
# Return value
Type | Description |
---|---|
any | If the return value is not undefined , it will be used as the return value of the query. |
# static
bindKnex()
const BoundPerson = Person.bindKnex(transactionOrKnex);
Creates an anonymous model subclass class that is bound to the given knex instance or transaction.
This method can be used to bind a Model subclass to multiple databases for example in a multi-tenant system.
Also check out the model binding pattern for transactions which internally uses bindKnex
.
# Arguments
Argument | Type | Description |
---|---|---|
transactionOrKnex | object | knex instance or a transaction to bind the model to. |
# Return value
Type | Description |
---|---|
Constructor<? extends Model> | The created model subclass constructor |
# Examples
const knex1 = require("knex")({
client: "sqlite3",
connection: {
filename: "database1.db",
},
});
const knex2 = require("knex")({
client: "sqlite3",
connection: {
filename: "database2.db",
},
});
SomeModel.knex(null);
const BoundModel1 = SomeModel.bindKnex(knex1);
const BoundModel2 = SomeModel.bindKnex(knex2);
// Throws since the knex instance is null.
await SomeModel.query();
// Works.
const models = await BoundModel1.query();
console.log(models[0] instanceof SomeModel); // --> true
console.log(models[0] instanceof BoundModel1); // --> true
// Works.
const models = await BoundModel2.query();
console.log(models[0] instanceof SomeModel); // --> true
console.log(models[0] instanceof BoundModel2); // --> true
# static
bindTransaction()
Alias for bindKnex.
# Examples
const { transaction } = require("objection");
const Person = require("./models/Person");
await transaction(Person.knex(), async (trx) => {
const TransactingPerson = Person.bindTransaction(trx);
await TransactingPerson.query().insert({ firstName: "Jennifer" });
return TransactingPerson.query()
.patch({ lastName: "Lawrence" })
.where("id", jennifer.id);
});
This is 100% equivalent to the example above:
const { transaction } = require("objection");
const Person = require("./models/Person");
await transaction(Person, async (TransactingPerson) => {
await TransactingPerson.query().insert({ firstName: "Jennifer" });
return TransactingPerson.query()
.patch({ lastName: "Lawrence" })
.where("id", jennifer.id);
});
# static
fromJson()
const person = Person.fromJson(json, opt);
Creates a model instance from a POJO (Plain Old Javascript Object).
The object is checked against jsonSchema if a schema is provided and an exception is thrown on failure.
The json
object is also passed through the $parseJson hook before the model instance is created. See this section for more info.
# Arguments
Argument | Type | Description |
---|---|---|
json | Object | The JSON object from which to create the model. |
opt | ModelOptions | Update options. |
# Return value
Type | Description |
---|---|
Model | The created model instance |
# Examples
Create a model instance:
const jennifer = Person.fromJson({ firstName: "Jennifer" });
Create a model instance skipping validation:
const jennifer = Person.fromJson(
{ firstName: "Jennifer" },
{ skipValidation: true }
);
# static
fromDatabaseJson()
const person = Person.fromDatabaseJson(row);
Creates a model instance from a JSON object send by the database driver.
Unlike fromJson, this method doesn't validate the input. The input is expected to be in the database format as explained here.
# Arguments
Argument | Type | Description |
---|---|---|
row | Object | A database row. |
# Return value
Type | Description |
---|---|
Model | The created model instance |
# static
modifierNotFound()
class BaseModel extends Model {
static modifierNotFound(builder, modifier) {
const { properties } = this.jsonSchema;
if (properties && modifier in properties) {
builder.select(modifier);
} else {
super.modifierNotFound(builder, modifier);
}
}
}
This method is called when an unknown modifier is used somewhere.
By default, the static modifierNotFound()
hook throws a ModifierNotFoundError
error. If a model class overrides the hook, it can decide to handle the modifer through the passed builder
instance, or call the hook's definition in the super class to still throw the error.
# Arguments
Argument | Type | Description |
---|---|---|
builder | QueryBuilder | The query builder on which to apply the modifier. |
modifier | string | The name of the unknown modifier. |
# static
createValidator()
class BaseModel extends Model {
static createValidator() {
return new MyCustomValidator();
}
}
Creates an instance of a Validator that is used to do all validation related stuff. This method is called only once per model class.
You can override this method to return an instance of your custom validator. The custom validator doesn't need to be based on the jsonSchema
. It can be anything at all as long as it implements the Validator interface.
If you want to use the default json schema based AjvValidator but want to modify it, you can use the objection.AjvValidator
constructor. See the default implementation example.
If you want to share the same validator instance between multiple models, that's completely fine too. Simply implement createValidator
so that it always returns the same object instead of creating a new one.
# Examples
Sharing the same validator between model classes is also possible:
const validator = new MyCustomValidator();
class BaseModel extends Model {
static createValidator() {
return validator;
}
}
The default implementation:
const AjvValidator = require("objection").AjvValidator;
class Model {
static createValidator() {
return new AjvValidator({
onCreateAjv: (ajv) => {
// Here you can modify the `Ajv` instance.
},
options: {
allErrors: true,
validateSchema: false,
ownProperties: true,
v5: true,
},
});
}
}
# static
createNotFoundError()
class BaseModel extends Model {
static createNotFoundError(queryContext) {
return new MyCustomNotFoundError();
}
}
Creates an error thrown by throwIfNotFound method. You can override this to throw any error you want.
# Arguments
Argument | Type | Description |
---|---|---|
queryContext | Object | The context object of query that produced the empty result. See context. |
# Return value
Type | Description |
---|---|
Error | The created error. NotFoundError by default. |
# Examples
The default implementation:
class Model {
static createNotFoundError(queryContext) {
return new this.NotFoundError();
}
}
# static
createValidationError()
class BaseModel extends Model {
static createValidationError({ type, message, data }) {
return new MyCustomValidationError({ type, message, data });
}
}
Creates an error thrown when validation fails for a model. You can override this to throw any error you want. The errors created by this function don't have to implement any interface or have the same properties as ValidationError
. ExpressWeb JS only throws errors created by this function an never catches them.
# Return value
Type | Description |
---|---|
Error | The created error. ValidationError by default. |
# static
loadRelated()
WARNING
Deprecated! Will be removed in version 3.0. Use fetchGraph instead.
# static
fetchGraph()
const queryBuilder = Person.fetchGraph(models, expression, options);
Load related models for a set of models using a RelationExpression.
# Arguments
Argument | Type | Description |
---|---|---|
models | Array<Model|Object> | Model instances for which to fetch the relations. Can be an array of model instances, array of POJOs, a single model instance or a single POJO. |
expression | string|RelationExpression | The relation expression |
options | FetchGraphOptions | Optional options. |
# Return value
Type | Description |
---|---|
QueryBuilder | The created query builder |
# Examples
const people = await Person.fetchGraph([person1, person2], "children.pets");
const person1 = people[0];
const person2 = people[1];
Relations can be filtered by giving modifier functions as arguments for the relations:
const people = await Person.fetchGraph(
[person1, person2],
`
children(orderByAge).[
pets(onlyDogs, orderByName),
movies
]
`
).modifiers({
orderByAge(builder) {
builder.orderBy("age");
},
orderByName(builder) {
builder.orderBy("name");
},
onlyDogs(builder) {
builder.where("species", "dog");
},
});
console.log(people[1].children.pets[0]);
# static
traverseAsync()
Traverses the relation tree of a model instance (or a list of model instances).
Calls the callback for each related model recursively. The callback is called also for the input models themselves.
In the second example the traverser function is only called for Person
instances.
# Arguments
Argument | Type | Description |
---|---|---|
filterConstructor | function | If this optional constructor is given, the traverser is only called for models for which model instanceof filterConstructor returns true. |
models | Model|Model[] | The model(s) whose relation trees to traverse. |
traverser | function(Model, string, string) | The traverser function that is called for each model. The first argument is the model itself. If the model is in a relation of some other model the second argument is the parent model and the third argument is the name of the relation. |
# Examples
There are two ways to call this method:
const models = await SomeModel.query();
await Model.traverseAsync(models, async (model, parentModel, relationName) => {
await doSomething(model);
});
and
const persons = await Person.query();
Model.traverseAsync(
Person,
persons,
async (person, parentModel, relationName) => {
await doSomethingWithPerson(person);
}
);
Also works with a single model instance
const person = await Person.query();
await Person.traverseAsync(person, async (model, parentModel, relationName) => {
await doSomething(model);
});
# static
getRelations()
const relations = Person.getRelations();
Returns a Relation object for each relation defined in relationMappings.
This method is mainly useful for plugin developers and for other generic usages.
# Return value
Type | Description |
---|---|
Object<string, Relation> | Object whose keys are relation names and values are Relation instances. |
# static
columnNameToPropertyName()
const propertyName = Person.columnNameToPropertyName(columnName);
Runs the property through possible columnNameMappers
and $parseDatabaseJson
hooks to apply any possible conversion for the column name.
# Arguments
Argument | Type | Description |
---|---|---|
columnName | string | A column name |
# Return value
Type | Description |
---|---|
string | The property name |
# Examples
If you have defined columnNameMappers = snakeCaseMappers()
for your model:
const propName = Person.columnNameToPropertyName("foo_bar");
console.log(propName); // --> 'fooBar'
# static
propertyNameToColumnName()
const columnName = Person.propertyNameToColumnName(propertyName);
Runs the property through possible columnNameMappers
and $formatDatabaseJson
hooks to apply any possible conversion for the property name.
# Arguments
Argument | Type | Description |
---|---|---|
propertyName | string | A property name |
# Return value
Type | Description |
---|---|
string | The column name |
# Examples
If you have defined columnNameMappers = snakeCaseMappers()
for your model:
const columnName = Person.propertyNameToColumnName("fooBar");
console.log(columnName); // --> 'foo_bar'
# static
fetchTableMetadata()
const metadata = await Person.fetchTableMetadata(opt);
Fetches and caches the table metadata.
Most of the time ExpressWeb JS doesn't need this metadata, but some methods like withGraphJoined do. This method is called by ExpressWeb JS when the metadata is needed. The result is cached and after the first call the cached promise is returned and no queries are executed.
Because ExpressWeb JS uses this on demand, the first query that needs this information can have unpredicable performance. If that's a problem, you can call this method for each of your models during your app's startup.
If you've implemented tableMetadata method to return a custom metadata object, this method doesn't execute database queries, but returns Promise.resolve(this.tableMetadata())
instead.
# Arguments
Argument | Type | Description |
---|---|---|
opt | TableMetadataFetchOptions | Optional options |
# Return value
Type | Description |
---|---|
Promise<TableMetadata> | The table metadata object |
# static
tableMetadata()
const metadata = Person.tableMetadata(opt);
Synchronously returns the table metadata object from the cache.
You can override this method to return a custom object if you don't want ExpressWeb JS to use fetchTableMetadata.
See fetchTableMetadata for more information.
# Arguments
Argument | Type | Description |
---|---|---|
opt | TableMetadataOptions | Optional options |
# Return value
Type | Description |
---|---|
TableMetadata | The table metadata object |
# Examples
A custom override that uses the property information in jsonSchema
.
class Person extends Model {
static tableMetadata() {
return {
columns: Object.keys(this.jsonSchema.properties),
};
}
}
# static
raw()
Shortcut for Person.knex().raw(...args)
# static
ref()
Returns a ReferenceBuilder instance that is bound to the model class. Any reference created using it will add the correct table name to the reference.
const { ref } = Person;
await Person.query().where(ref("firstName"), "Jennifer");
select "persons".* from "persons" where "persons"."firstName" = 'Jennifer'
ref
uses the correct table name even when an alias has been given to the table.
const { ref } = Person;
await Person.query().alias("p").where(ref("firstName"), "Jennifer");
select "p".* from "persons" as "p" where "p"."firstName" = 'Jennifer'
Note that the following two ways to use Model.ref
are completely equivalent:
const { ref } = Person;
await Person.query().where(ref("firstName"), "Jennifer");
await Person.query().where(Person.ref("firstName"), "Jennifer");
# static
fn()
Shortcut for Person.knex().fn
# static
knexQuery()
Shortcut for Person.knex().table(Person.tableName)