In this post we will see an example model for Objection.js and how we can define basic database concepts to our model. Objection.js is a relational query builder for Nodejs and is built on top of the Knex SQL query builder.
Let’s assume the following SQL table to use as an example in this post.
CREATE TABLE `users` ( `id` INT NOT NULL AUTO_INCREMENT, `full_name` VARCHAR(100) NOT NULL, `email` VARCHAR(60) NOT NULL, `last_login` TIMESTAMP NULL, PRIMARY KEY (`id`));
Basic Definition
Objection.js does not require a full definition, so we can have our model ready to use by simply setting the table name.
import { Model } from 'objection' class User extends Model { // Table name is the only required property. static get tableName() { return 'users'; } }
Primary Key Definition
To define the primary key, we can set the idColumn
property in the model. It returns an array with the column names that participate in the primary key.
static get idColumn() { return ['id']; }
Model Validation
Objection allows us to define some properties on the JSON schema of a row. We can define which properties are required, so that operations like INSERT or UPDATE to fail if required fields are missing. We can also define the data type for each property and whether it allows NULL values.
static get jsonSchema () { return { type: 'object', required: [ 'id', 'full_name', 'email' ], // Properties defined as objects or arrays are // automatically converted to JSON strings when // writing to database and back to objects and arrays // when reading from database. To override this // behaviour, you can override the // User.jsonAttributes property. properties: { id: {type: 'integer'}, full_name: {type: 'string'}, email: {type: 'string'}, last_login: {type: ['integer', 'null'], format: 'date-time'} } }; }
Relation Mapping
We can also define relations with other tables so that we can quickly retrieve the user data from the other tables within the same query.
static get relationMappings() { return { user_orders: { // This is the name we will use in eager relation: Model.HasManyRelation, modelClass: Order, join: { from: 'users.id', to : 'orders.user_id' } } }; }
Property Naming
In most projects, the naming convention of the database columns does not match the casing we use in our code. Objection.js offers a mapper functionality which maps the column names to snake case. For example full_name
will be fullName
throughout our code.
static get columnNameMappers() { return snakeCaseMappers({ upperCase: true }); }
Derived Properties
We can extend the model with additional properties and choose whether we want them to be included in the output JSON.
get firstName() { return this.fullName.split(' ')[0]; } static get virtualAttributes() { return ['firstName']; }
Date Handling
Date handling can be a bit tricky. We can control what values we set to the date properties of our model. The code sample below shows how we can handle epoch timestamps as values and cast them to Date
objects, rather than have our model fail to validate.
Notice the use of .toIsoString()
conversion. That can save you some time if working with time-zones.
$set(obj) { super.$set(obj); if(typeof obj.last_login === 'number') { this.last_login = new Date(obj.last_login).toISOString(); } }
$set(obj) { // A an alternative approach we loop all properties and apply the same logic for all properties with format date-time for (const column in this.constructor.jsonSchema.properties) { const cp = this.constructor.jsonSchema.properties[column]; if(cp.format === 'date-time' && typeof this[key] === 'number') { this[key] = new Date(this[key]).toISOString() } } }
Full Object Definition
Here is the final and full version of the model’s definition.
import { Model, snakeCaseMappers } from 'objection' class User extends Model { static get tableName() { return 'users'; } static get idColumn() { return ['id']; } static get columnNameMappers() { return snakeCaseMappers({ upperCase: true }); } firstName() { return this.fullName.split(' ')[0]; } static get virtualAttributes() { return ['fullName']; } static get jsonSchema () { return { type: 'object', required: [ 'id', 'full_name' ], properties: { id: {type: 'integer'}, full_name: {type: 'string'}, email: {type: 'string'}, last_login: {type: ['integer', 'null'], format: 'date-time'} } }; } static get relationMappings() { return { user_orders: { relation: Model.HasManyRelation, modelClass: Order, join: { from: 'users.id', to : 'orders.user_id' } } }; } $set(obj) { super.$set(obj); for (const column in this.constructor.jsonSchema.properties) { const cp = this.constructor.jsonSchema.properties[column]; if(cp.format === 'date-time' && typeof this[key] === 'number') { this[key] = new Date(this[key]).toISOString() } } } }
References:
- Make sure to check out Objection.js’s Model API Reference page
Leave a Reply