simple-knex-model
A simple model for knex queries.
Features
- ✅ Fully es6 module compatible
- ✅ Schema validation using ajv
- ✅ Extensible via hooks
- ✅ Model registry for relationships, no circular dependencies
Inspired a bit by objection, but with way less features. On the plus side, it works with es6 modules.
Usage
yarn add simple-knex-model knex
# or npm install --save simple-knex-model knex
Configure your knex instance and provide it to the base model. This can be done any time before you use a model instance, and subsequent calls are safe.
;; const knexConfig = {};const db = ; BaseModel;
Then define your models.
; static { // required return 'users'; } static { // optional return 'id'; // default value } static { // optional return type: 'object' required: 'name' 'email' properties: id: type: 'string' name: type: 'string' email: type: 'string' format: 'email' status: type: 'string' enum: 'pending' 'approved' 'denied' ; }
If you provide a jsonSchema
, documents will be validated with it before they are saved.
API
Model
here is your model, which extends BaseModel
(simple-knex-model);
new Model(document)
Creates a new instance of the model, ready to be saved. Document is an object.
modelInstance.save()
Inserts the document into the database. Returns a promise. If jsonSchema
is defined on the model, it will be used to validate the document before saving, and the Promise will reject if the validation fails.
Model.query()
Returns an instance of knex
, with the table name already applied. Use this to execute any of the normal knex
operations.
Model.queryById(id)
Returns an instance of knex
, with the table name already applied and a where on the primary key for the provided id. Use this to execute any of the normal knex
operations on documents with a given id. Most useful when used with .first()
.
Model.queryWith(relations)
relations
is either a key from the relationships object or an array of said keys. Returns an instance of knex
, with the table name already applied and the related join queries applied. Use this to execute any of the normal knex
operations with your defined joins.
Relationships
Basic relationships are supported, using BaseModel.hasMany
, BaseModel.belongsTo
, and BaseModel.belongsToMany
. The naming should be pretty self-explanatory, but there is plenty of information around that explains how it works. Here's a quick example:
; static { return 'chefs'; } static { return recipes: model: 'Recipe' relation: BaseModelhasMany remote: 'chef_id' ; } static { return 'recipes'; } static { return chef: model: 'Chef' relation: BaseModelbelongsTo local: 'chef_id' ; } // get all recipes and include the chef's name in the joined resultsawait results = Recipe;
Here's the breakdown of the options for each. If you need something more complex, you'll have to craft if by hand directly from the knex
instance.
BaseModel.hasMany
Owner model has many child models
property | default | description |
---|---|---|
model | string - Model name to join with |
|
relation | BaseModel.hasMany - The model relationship type |
|
joinType | inner |
string - The join type to use (inner, left, right, leftOuter, rightOuter, fullOuter, cross) |
local | primaryKey | string - The local field to join with |
remote | string - The remote field to join with |
BaseModel.belongsTo
Owner model belongs to child model
property | default | description |
---|---|---|
model | string - Model name to join with |
|
relation | BaseModel.belongsTo - The model relationship type |
|
joinType | inner |
string - The join type to use (inner, left, right, leftOuter, rightOuter, fullOuter, cross) |
local | string - The local field to join with |
|
remote | primaryKey | string - The remote field to join with |
BaseModel.belongsToMany
Owner model has and belongs to many child models, coordinated through a join table
property | default | description |
---|---|---|
model | string - Model name to join with |
|
relation | BaseModel.belongsToMany - The model relationship type |
|
joinType | inner |
string - The join type to use (inner, left, right, leftOuter, rightOuter, fullOuter, cross) |
joinTable | string - Table join table to use |
|
local | primaryKey | string - The local field to join with |
joinLocal | string - The field in the join table to match on the local field |
|
remote | primaryKey | string - The remote field to join with |
joinRemote | string - The field in the join table to match on the remote field |
Adding Functionality
simple-knex-model
provides 3 hooks which can be used to modify data on the fly. You can use that to create your own custom base model, or even to write plugins. For example, let's say you want to apply a custom id to documents, via uuid or some other method, you can use the onCreate
hook to do just that.
;; const rand = crypto; static { return 'users'; } static { docid = ; } const user = email: 'user@email.co' ; console; // { id: 'daa92600af5d', email: 'user@email.co' }
Now every User document you create will have a random id. You could plug any GUID/UUID functionality you want in here. You can also take this idea further and create your own custom base class that adds this functionality to every model that extends it.
;; const rand = crypto; static { docid = ; } static { return 'users'; } static { return 'posts'; } const user = email: 'user@email.co' ; const post = title: 'Hello World' ;console; // { id: 'b8919bf858ab', email: 'user@email.co' }console: // { id: 'f624c9ad373c', title: 'Hello World' }
Hooks
onCreate(doc)
Called when a new instance of the model is created. The doc
object is passed by reference and can be mutated directly.
beforeValidate(jsonSchema, doc)
Called before the doc is validated against the defined schema. jsonSchema
is a shallow clone of the schema defined on the model, and anything returned from this function will be used as the new schema. The doc
object is passed by reference and can be mutated directly.
beforeCreate(doc, [returning])
Called after the validation but before the document is written to the database. The doc
object is passed by reference and can be mutated directly. All arguments from .insert()
are passed directly. This method can return a Promise, and the query won't execute until the Promise resolves.
beforeUpdate(doc, [returning]) / beforeUpdate(key, value, [returning])
Called after the validation but before the document update query is executed. If doc
object is provided, is it passed by reference and can be mutated directly. All arguments from .update()
are passed directly, so you should check what you get. This method can return a Promise, and the query won't execute until the Promise resolves.
License
MIT © w33ble