Grind’s ORM is powered by Objection.js which provides full ES6/7 class-based Models.

This document only focuses on additional functionality Grind ORM provides on top of Objection.js, for full documentation on how Objection.js works, head over to vincit.github.io/objection.js.

First, add the grind-orm package via your preferred package manager:

yarn add grind-orm

Next, you’ll need to add OrmProvider to your app providers in app/Boostrap.js:

import Grind from 'grind-framework'
import { OrmProvider } from 'grind-orm'
 
const app = new Grind()
app.providers.push(OrmProvider)

The fastest way to create a new model is by using the model generator via bin/cli make:model.

You can invoke make:model with a few different arguments:

  • bin/cli make:model UserModel will create app/Models/UserModel.js, but will not infer a table name.
  • bin/cli make:model --table=users will also create app/Models/UserModel.js and will set the table name for you.
  • You can also pass both a class name and a command name at the same time if your class name differs from the table name.

Once you’ve triggered make:model, a model is generated for you that looks like this:

import { Model } from 'grind-orm'
 
export class UserModel extends Model {
  static tableName = 'users'
  static descriptiveName = 'user'
 
  static jsonSchema = {
    type: 'object',
    required: [ 'name' ],
 
    properties: {
      id: { type: 'integer' },
      name: { type: 'string', maxLength: 255 },
      created_at: { type: 'string', format: 'date-time' },
      updated_at: { type: 'string', format: 'date-time' }
    }
  }
 
  static buildRelations() {
    return {
      // children: this.hasMany(ChildModel) 
    }
  }
 
}

This is the name of the table this model represents and tells the query builder which table to query on.


This is used by the describe function throughout the framework to describe what this model represents, most prevalently in error messages. If descriptiveName is not provided, the Model class will generate one based on the tableName.


This is used by Objection.js to validate data going into your database. It’s not required but highly recommended to ensure your data looks like it’s expected to.

The jsonSchema properties are not tied directly to the database schema. They’re solely intended for validation and will not be used to generate migrations. You are still responsible for building out the backing database schema the model represents.


This function is called by Grind’s Model to populate the relationMappings property in Objection.js.

Grind’s Model offers an alternative way to establish relationships when compared to Objection.js’s relationMappings property, which can get fairly verbose and repetitive.

Grind uses a buildRelations function to return an object of relations that are then used to populate the relationMappings class property.

Grind’s Model offers several functions to help build relations:

hasOne establishes a one to one relationship where the target model has a property that references a property of the local model.

hasOne(modelClass, foreignKey = null, localKey = null)
  • modelClass — Target model you’re establishing a relationship with
  • foreignKey — The property on the target model that references the local model. If not provided, Model will generate a foreign key based on the local model’s name, UserModel becomes user_id.
  • localKey — The property that foreignKey references on the local model. If no value is provided, it uses the local model’s idColumn property, which defaults to id.

If UserModel calls this.hasOne(AvatarModel) it establishes that AvatarModel has a user_id property that references id on UserModel.


hasMany establishes a one to many relationship where the target model has a property that references a property of the local model.

hasMany(modelClass, foreignKey = null, localKey = null)
  • modelClass — Target model you’re establishing a relationship with
  • foreignKey — The property on the target model that references the local model. If not provided, Model will generate a foreign key based on the local model’s name, UserModel becomes user_id.
  • localKey — The property that foreignKey references on the local model. If no value is provided, it uses the local model’s idColumn property, which defaults to id.

If UserModel calls this.hasMany(PostModel) it establishes that PostModel has a user_id property that references id on UserModel.


belongsTo is the inverse of hasOne/hasMany and establishes a one to one relationship where the local model has a property that references a property of the target model.

belongsTo(modelClass, foreignKey = null, otherKey = null)
  • modelClass — Target model you’re establishing a relationship with
  • foreignKey — The property on the target model that references the local model. If not provided, the Model will generate a foreign key based on the local model’s name, UserModel becomes user_id.
  • otherKey — The property that foreignKey references on the target class. If no value is provided, it uses the target models idColumn property, which defaults to id.

If PostModel calls this.belongsTo(UserModel) it establishes that PostModel has a user_id property that references id on UserModel.


belongsToMany establishes a many to many relationship using a join table.

belongsToMany(modelClass, tableName = null, foreignKey = null, otherKey = null)
  • modelClass — Target model you’re establishing a relationship with
  • tableName — Name of the join table to connect the two models. If not provided, the Model will generate a table name based on the names of the tables on each end, sorted ascendingly and joined with an underscore. If you’re establishing a relationship between UserModel and BlogModel the default join table name will be blogs_users.
  • foreignKey — The column on the join table that references the target model. If not provided, the Model will generate a foreign key based on the target model’s name, BlogModel becomes blog_id.
  • otherKey — The column on the join table that references the local model. If not provided, the Model will generate a foreign key based on the local model’s name, UserModel becomes user_id.

If UserModel calls this.belongsToMany(BlogModel) it established that UserModel is connected to BlogModel through a joint able called blogs_users with blogs_users.user_id referencing UserModel.idColumn and blogs_users.blog_id referencing BlogModel.idColumn.

If wanted to establish a followers relationship, you could define it as:

this.belongsToMany(UserModel, 'followers', 'to_user_id', 'from_user_id')

This would establish that one UserModel is related to another UserModel through a followers join table.

Putting it all together, here’s an example of creating a few relationships using Grind Model’s buildRelations:

export class UserModel extends Model {
  static buildRelations() {
    return {
      avatar: this.hasOne(AvatarModel),
      posts: this.hasMany(PostModel),
      blogs: this.belongsToMany(BlogModel),
      followers: this.belongsToMany(this, 'followers', 'to_user_id', 'from_user_id'),
      following: this.belongsToMany(this, 'followers', 'from_user_id', 'to_user_id'),
    }
  }
}

Grind ORM extends Objection.js’s already powerful QueryBuider to provide some additional functionality.

The paginate method provides a way to return paginated results with a object result object that includes the total number of items, number of pages, the current page, and more.

paginate(req, perPage = 25, { param, query } = { })
  • req — The original http request object — this is used to automatically detect the current page.
  • perPage — The number of items to include in a single page
  • options.param — If provided, paginate will look for the current page number in a URL param instead of the query string. This value will override any provided options.query value.
  • options.query — By default, paginate looks for a page field in the query string, this option can be used to tell it to look for a different field.
  • totalPages — The total number of pages.
  • perPage — The number of items included in each page.
  • page — The current page number.
  • start — The start range for this page.
  • end — The end range for this page.
  • total — The total number of items for this page.
  • results — The items in this page.

The orFail method will throw a ModelNotFoundError if the result set is empty.

const user = UserModel.where('id', req.param.user).first().orFail()

By tacking on orFail() to the query, your code can now be confident the user var is populated, saving you the need to check and throw errors. orFail() will automatically throw ModelNotFoundError — which extends NotFoundError, resulting in a 404 being displayed to the end user.


The withoutEager method will clear any previously added eager filter as well as ignore any Model level eager filters that may be defined.

const user = UserModel.where('id', req.param.user).first().withoutEager()

Objection.js has an amazing way to do eager loading, so Grind Model builds on top of it to add a few additional niceties.

Grind Model provides a way to define eager loading on a Model level that will ensure whenever your model is used, it will eager load those relationships.

export class BlogModel {
  static eager = '[owner,posts(latest)]'
  static eagerFilters = {
    latest: builder => builder.orderBy('created_at').limit(1)
  }
}

The eager class property lets you define the relation expression to load every time the model is queried.

You an also define filters for eager by setting them on the (optional) eagerFilter class property.

Grind Model enables you to define global filters to use in any eager expression by calling QueryBuilder.registerFilter().

The recommended way to load filters is via a provider:

import { Model } from 'grind-orm'
 
export function EagerFiltersProvider() {
  Model.QueryBuilder.registerFilter('active', builder => builder.where('active', 1))
}

Now you can just use the active filter in any eager expression without having to declare it each time.

Edit