{"id":13780329,"url":"https://github.com/feathersjs-ecosystem/feathers-objection","last_synced_at":"2025-05-11T13:31:54.693Z","repository":{"id":34169918,"uuid":"60149476","full_name":"feathersjs-ecosystem/feathers-objection","owner":"feathersjs-ecosystem","description":"Feathers database adapter for Objection.js, an ORM based on KnexJS SQL query builder for Postgres, Redshift, MSSQL, MySQL, MariaDB, SQLite3, and Oracle. Forked from feathers-knex.","archived":false,"fork":false,"pushed_at":"2023-04-02T08:47:21.000Z","size":2431,"stargazers_count":98,"open_issues_count":23,"forks_count":48,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-11-08T21:06:30.509Z","etag":null,"topics":["feathers-knex","feathers-service-adapter","feathersjs","knex","mysql","objection-orm","objectionjs","orm","postgres","postgresql","redshift"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/feathersjs-ecosystem.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2016-06-01T05:58:00.000Z","updated_at":"2023-07-25T14:02:04.000Z","dependencies_parsed_at":"2024-01-17T00:55:14.723Z","dependency_job_id":"c671fe52-b9ea-4344-852b-ac740b24e790","html_url":"https://github.com/feathersjs-ecosystem/feathers-objection","commit_stats":{"total_commits":388,"total_committers":25,"mean_commits":15.52,"dds":"0.44329896907216493","last_synced_commit":"255443386a9f6118d6121e906eb4e3fcc49f1f0b"},"previous_names":["mcchrish/feathers-objection"],"tags_count":109,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/feathersjs-ecosystem%2Ffeathers-objection","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/feathersjs-ecosystem%2Ffeathers-objection/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/feathersjs-ecosystem%2Ffeathers-objection/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/feathersjs-ecosystem%2Ffeathers-objection/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/feathersjs-ecosystem","download_url":"https://codeload.github.com/feathersjs-ecosystem/feathers-objection/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224703869,"owners_count":17355710,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["feathers-knex","feathers-service-adapter","feathersjs","knex","mysql","objection-orm","objectionjs","orm","postgres","postgresql","redshift"],"created_at":"2024-08-03T18:01:14.595Z","updated_at":"2024-11-17T15:31:06.114Z","avatar_url":"https://github.com/feathersjs-ecosystem.png","language":"JavaScript","readme":"# feathers-objection\n\n[![Build Status](https://travis-ci.org/feathersjs-ecosystem/feathers-objection.svg?branch=master)](https://travis-ci.org/feathersjs-ecosystem/feathers-objection)\n[![Coverage Status](https://coveralls.io/repos/github/feathersjs-ecosystem/feathers-objection/badge.svg?branch=master)](https://coveralls.io/github/feathersjs-ecosystem/feathers-objection?branch=master)\n[![js-semistandard-style](https://img.shields.io/badge/code%20style-semistandard-brightgreen.svg?style=flat-square)](https://github.com/standard/semistandard)\n[![Dependency Status](https://img.shields.io/david/feathersjs-ecosystem/feathers-objection.svg)](https://david-dm.org/feathersjs-ecosystem/feathers-objection)\n[![npm](https://img.shields.io/npm/v/feathers-objection.svg?maxAge=3600)](https://www.npmjs.com/package/feathers-objection)\n\n[Feathers](https://feathersjs.com/) database adapter for\n[Objection.js](https://vincit.github.io/objection.js), an ORM based on\n[KnexJS](https://knexjs.org/) SQL query builder for Postgres, Redshift, MSSQL, MySQL,\nMariaDB, SQLite3, and Oracle.\n\n## Installation\n\n```\nnpm install --save feathers-objection\nnpm install --save objection\nnpm install --save knex\n```\n\nThen add one of the following:\n\n```\nnpm install --save pg\nnpm install --save sqlite3\nnpm install --save mysql\nnpm install --save mysql2\nnpm install --save oracle\nnpm install --save mssql\n```\n\nIf you want to use a MariaDB instance, you can use the mysql driver.\n\n### [Feathers CLI](https://github.com/feathersjs/feathers/tree/master/packages/cli)\n\nUse `feathers generate service` command to generate a new `Objection` service.\n\n## Documentation\n\nPlease refer to the\n[Feathers database adapter documentation](https://docs.feathersjs.com/api/databases/adapters.html)\nfor more details or directly at:\n\n- [Querying](https://docs.feathersjs.com/api/databases/querying.html) - The\n  common adapter querying mechanism\n- [Pagination and Sorting](https://docs.feathersjs.com/api/databases/common.html#pagination) -\n  How to use pagination and sorting for the database adapter\n- [Extending](https://docs.feathersjs.com/api/databases/common.html#extending-adapters) -\n  How to extend a database adapter\n\nRefer to the official\n[Objection.js documention](https://vincit.github.io/objection.js/).\n\nIt works like the\n[Knex service](https://github.com/feathersjs-ecosystem/feathers-knex) adapter,\nexcept it has all the benefits of the Objection ORM.\n\n### [Initializing the Library](https://knexjs.org/#Installation-client)\n\nconfig/defaults.json\n\n```js\n{\n  \"mysql\": {\n    \"client\": \"mysql2\",\n    \"connection\": {\n      \"host\": \"mysql.example.com\",\n      \"user\": \"root\",\n      \"password\": \"secret\",\n      \"database\": \"example\"\n    }\n  }\n}\n```\n\nobjection.js\n\n```js\nconst { Model } = require('objection');\n\nmodule.exports = function(app) {\n  const { client, connection } = app.get('mysql');\n  const knex = require('knex')({ client, connection, useNullAsDefault: false });\n\n  Model.knex(knex);\n\n  app.set('knex', knex);\n};\n```\n\n### Service Options\n\n- `model` (**required**) - The Objection model definition.\n\n- `id` (_optional_, default: `model.idColumn` or `'id'`) - The name of the id field property. Use\n  array of strings for composite primary keys.\n  \n- `events` (_optional_) - List of\n  [custom service events](https://docs.feathersjs.com/api/events.html#custom-events)\n  sent by this service.\n\n- `paginate` (_optional_) - [Pagination object](https://docs.feathersjs.com/api/databases/common.html#pagination)\n  containing a `default` and `max` page size.\n\n- `multi` (_optional_) - Allow `create` with arrays and `update` and `remove`\n  with `id` `null` to change multiple items. Can be `true` for all methods or an\n  array of allowed methods (e.g. `[ 'remove', 'create' ]`).\n\n- `whitelist` (_optional_) - List of additional query operators to allow (e.g.\n  `[ '$eager', '$joinRelation' ]`).\n\n- `schema` (_optional_) - Database schema to use with all the service queries (e.g.\n  `public`). See [`withSchema`](https://vincit.github.io/objection.js/api/query-builder/find-methods.html#withschema) documentation.\n\n### Default Query Operators\n\nStarting at version 2.0.0 `feathers-objection` converts queries securely. If you\nwant to support additional Objection operators, the `whitelist` service option\ncan contain an array of additional allowed operators. By default, supported\noperators are:\n\n```\n'$eq',\n'$ne',\n'$gte',\n'$gt',\n'$lte',\n'$lt',\n'$in',\n'$nin',\n'$like',\n'$notLike',\n'$ilike',\n'$notILike',\n'$or',\n'$and',\n'$sort',\n'$not'\n```\n\n### Eager Queries\n\nEager queries is one way of solving the SQL database relational model in\nFeathers services, instead of relying on hooks.\n\n#### Service Options\n\nNote that all this eager related options are optional.\n\n- **`allowedEager`** - relation expression to limit the allowed eager queries in\n  the service. Defaults to `'[]'`, meaning no eager queries allowed. See\n  [`allowGraph`](https://vincit.github.io/objection.js/api/query-builder/eager-methods.html#allowgraph)\n  documentation.\n  \n- **`eagerOptions`** - options object to use with `$eager` and `$joinEager` query operators. \n  See [`GraphOptions`](https://vincit.github.io/objection.js/api/types/#type-graphoptions)\n  documentation.\n\n- **`eagerFilters`** - option to impose compulsory eager filter. It takes an\n  object or array of objects with the following properties:\n  - `expression` - the relation expression that the filter will be applied.\n  - `filter` - the filter function. It uses\n    [`modifyGraph`](https://vincit.github.io/objection.js/api/query-builder/eager-methods.html#modifygraph)\n    internally.\n\n#### Query Operators\n\n- **`$modify`** - modifiers allow you to easily reuse snippets of query logic. you can pass arguments and use \n  multiple modifiers. value can be one of the following:\n    - String with comma-separated modifier names. e.g. `modifier1,modifier2`\n    - Array or serialized array with modifier name or array of modifier names as the first item. The rest of the array items would be the modifier/s arguments.\n    e.g. `['modifier1', arg1, arg2]` or `[['modifier1', 'modifier2'], arg1, arg2]`\n    - Object or serialized object with modifiers as keys and their arguments as values. Set modifier's value to `true` when it has no arguments. \n    e.g. `{ modifier1: [arg1, arg2], modifier2: [arg3, arg4], modifier3: true }`\n  \n  See [`modify`](https://vincit.github.io/objection.js/api/query-builder/other-methods.html#modify) documentation.\n\n- **`$eager`** - eager load relations defined in models' `relationMappings` getter methods. See\n  [`withGraphFetched`](https://vincit.github.io/objection.js/api/query-builder/eager-methods.html#withgraphfetched) documentation.\n  \n- **`$joinRelation`** - filter based on a relation's field. use with `$eager` to also fetch the relation. See\n  [`joinRelated`](https://vincit.github.io/objection.js/api/query-builder/join-methods.html#joinrelated) \n  documentation.\n  \n- **`$leftJoinRelation`** - filter based on a relation's field using LEFT JOIN. use with `$eager` to also fetch the relation. See\n  [`leftJoinRelated`](https://vincit.github.io/objection.js/api/query-builder/join-methods.html#leftjoinrelated)\n  documentation.\n  \n- **`$joinEager`** - filter based on a relation's field. See\n  [`withGraphJoined`](https://vincit.github.io/objection.js/api/query-builder/eager-methods.html#withgraphjoined)\n  documentation.\n  \n- **`$modifyEager`** - filter relation based on a relation's field. does not support JSON fields.\n  e.g. `companies.find({ query: { $eager: 'employees', $modifyEager: { employees: { name: 'John' } } } })`\n  \n- **`$mergeEager`** - merge an eager expression to `$eager`,\n  e.g. `companies.find({ query: { $eager: 'employees', $mergeEager: 'ceos' } })`\n\n- **`$allowRefs`** - allow the usage of `ref` keyword to reference another field. Reference a relation's field using `$joinEager` or `$joinRelation`,\n  e.g. `companies.find({ query: { name: 'ref(size)', $allowRefs: true } })`, `employees.find({ query: { $joinEager: 'company', 'company.name': 'ref(employees.name)', $allowRefs: true } })`. See\n  [`ref`](https://vincit.github.io/objection.js/api/objection/#ref) documentation.    \n\n- **`$select`** - add SELECT statement with given array of column names, e.g. `['name', 'ref(jsonb:a)', 'ref(jsonb:a) as a']`. See\n  [`select`](https://vincit.github.io/objection.js/api/query-builder/find-methods.html#select) \n  and [`FieldExpression`](https://vincit.github.io/objection.js/api/types/#type-fieldexpression) documentation.\n    \n- **`$sort`** - add an order by clause to the query, e.g. `query: { $sort: { a: 1, 'b.c': -1, 'ref(jsonb:a)': 1 } }`. See\n  [`FieldExpression`](https://vincit.github.io/objection.js/api/types/#type-fieldexpression) documentation.    \n\n- **`$noSelect`** - skips SELECT queries in create, patch \u0026 remove requests. response data will be based on the input data.\n\n- **`$null`** - filter based on if a column is NULL with REST support, e.g. `companies.find({ query: { ceo: { $null: false } } })`, `companies.find({ query: { ceo: { $null: 'false' } } })`\n\n- **`$not`** - filter based on if a query is NOT true. It can be used with an object `$not: { name: { $in: ['craig', 'tim'] } }` or array `$not: [ { $id: 1 }, { $id: 2 } ]`\n\n- **`$between`** - filter based on if a column value is between range of values\n\n- **`$notBetween`** - filter based on if a column value is not between range of values  \n\n- **`$like`** - filter column value based on a LIKE pattern\n\n- **`$notLike`** - filter column value based on a NOT LIKE pattern\n\n- **`$regexp`** - filter column value based on a REGEXP pattern\n\n- **`$notRegexp`** - filter column value based on a NOT REGEXP pattern\n\n- **`$ilike`** - (Postgres) filter column value based on a case-insensitive LIKE pattern\n\n- **`$notILike`** - (Postgres) filter column value based on a case-insensitive NOT LIKE pattern\n\n- **`$iRegexp`** - (Postgres) filter column value based on a case-insensitive REGEXP pattern\n\n- **`$notIRegexp`** - (Postgres) filter column value based on a case-insensitive NOT REGEXP pattern\n  \n- **`$containsKey`** (Postgres) - filter based on if a column contains a key\n\n- **`$any`** (Postgres) - filter based on if a column contains any key from array of strings\n\n- **`$all`** (Postgres) - filter based on if a column contains all keys from array of strings\n\n- **`$contains`** (Postgres) - filter based on if a column contains all values from array of values\n\n- **`$contained`** (Postgres) - filter based on if a column is contained within a serialized object\n\n#### Params Operators\n\n- **`transaction`** - A transaction object. See\n  [`transaction`](https://vincit.github.io/objection.js/api/objection/#transaction)\n  documentation.\n\n- **`atomic`** - when `true` ensure that multi create or graph insert/upsert success or fail all at once. Under the hood, automatically create a transaction and commit on success or rollback on partial or total failure. __Ignored__ if you added your own `transaction` object in params.\n\n- **`mergeAllowEager`** - Will merge the given expression to the existing expression from the `allowEager` service option. \n  See [`allowGraph`](https://vincit.github.io/objection.js/api/query-builder/eager-methods.html#allowgraph)\n  documentation.\n  \n- **`eagerOptions`** - Options object to use with `$eager` and `$joinEager` query operators.\n  merges on top of the `eagerOptions` service option.\n  See [`GraphOptions`](https://vincit.github.io/objection.js/api/types/#type-graphoptions)\n  documentation.\n  \n- **`schema`** - Database schema to use with the query (e.g. `public`)\n  See [`withSchema`](https://vincit.github.io/objection.js/api/query-builder/find-methods.html#withschema)\n  documentation.\n\n- **`modifierFiltersResults`** - when `false` the `total` count of a `find()` query is calculated from the original result set, ignoring the `count` of any `$modify` query.\n  The default behaviour is to apply the count of the modifier to the result total, assuming that the modifier may influence the result total by filtering the result set. This can be used to workaround issues with `groupBy` and the result count. See [this issue](https://github.com/feathersjs-ecosystem/feathers-objection/issues/102) for a detailed explanation.\n\n### Composite primary keys\n\nComposite primary keys can be passed as the `id` argument using the following\nmethods:\n\n- String with values separated by the `idSeparator` property (order matter,\n  recommended for REST)\n- JSON array (order matter, recommended for internal service calls)\n- JSON object (more readable, recommended for internal service calls)\n\nWhen calling a service method with the `id` argument, all primary keys are\nrequired to be passed.\n\n#### Service Options\n\n- **`idSeparator`** - (optional) separator char to separate composite primary\n  keys in the `id` argument of get/patch/update/remove external service calls.\n  Defaults to `','`.\n\n```js\napp.use('/user-todos', service({\n  id: ['userId', 'todoId'],\n  idSeparator: ','\n})\n\napp.service('/user-todos').get('1,2')\napp.service('/user-todos').get([1, 2])\napp.service('/user-todos').get({ userId: 1, todoId: 2 })\n```\n\n### JSON column\n\nJSON column will be automatically converted from and to JS object/array and will\nbe saved as text in unsupported databases. it must be defined in the model class.\n\nQuery against a JSON column in PostgresSQL:\n\n```js\napp.service('companies').find({\n  query: {\n    obj: { stringField: 'string' }\n  }\n});\n\napp.service('companies').find({\n  query: {\n    obj: { numberField: 1.5 }\n  }\n});\n\napp.service('companies').find({ \n  query: {\n    obj: { numberField: { $gt: 1.5 } }\n  }\n});\n\napp.service('companies').find({\n  query: {\n    obj: { 'objectField.object': 'string in obj.objectField.object' }\n  }\n});\n\napp.service('companies').find({\n  query: {\n    obj: { 'arrayField(0).object': 'string in obj.arrayField[0].object' }\n  }\n});\n\napp.service('companies').find({\n  query: {\n    arr: { '(0).objectField.object': 'string in arr[0].objectField.object' }\n  }\n});\n\napp.service('companies').find({\n  query: {\n    obj: { \"(field.WithDot)\": 'string' }\n  }\n});\n```\n\n### Graph upsert\n\nArbitrary relation graphs can be upserted (insert + update + delete) using the\nupsertGraph method. See\n[`examples`](https://vincit.github.io/objection.js/guide/query-examples.html#graph-upserts) for a better explanation.  \n\nRuns on `update` and `patch` service methods when `id` is set. When the `data` object also contains `id`, then both must be the same or an error is thrown.\n\n#### Service Options\n\n- **`allowedUpsert`** - relation expression to allow relations to be upserted\n  along with update. Defaults to `null`, meaning relations will not be\n  automatically upserted unless specified here. See\n  [`allowGraph`](https://vincit.github.io/objection.js/api/query-builder/eager-methods.html#allowgraph)\n  documentation.\n- **`upsertGraphOptions`** - See\n  [`upsertGraphOptions`](https://vincit.github.io/objection.js/api/types/#type-upsertgraphoptions)\n  documentation.\n- **`createUseUpsertGraph`** - If set to `true`, Graph Upsert will also be used\n  for `.create(data, params)` method instead of Graph Insert.\n\n```js\napp.use('/companies', service({\n  model: Company,\n  allowedEager: 'clients',\n  allowedUpsert: 'clients'\n})\n\napp.service('/companies').update(1, {\n  name: 'New Name',\n  clients: [{\n    id: 100,\n    name: 'Existing Client'\n  }, {\n    name: 'New Client'\n  }]\n})\n```\n\nIn the example above, we are updating the name of an existing company, along\nwith adding a new client which is a relationship for companies. The client\nwithout the ID would be inserted and related. The client with the ID will just\nbe updated (if there are any changes at all).\n\n#### Params Operators\n\n- **`mergeAllowUpsert`** - Merge given expression into `allowedUpsert`.\n- **`mergeUpsertGraphOptions`** - Merge given options into `upsertGraphOptions`.\n\n### Graph insert\n\nArbitrary relation graphs can be inserted using the insertGraph method. Provides\nthe ability to relate the inserted object with its associations.  \n\nRuns on the `.create(data, params)` service method.\n\n#### Service Options\n\n- **`allowedInsert`** - relation expression to allow relations to be created\n  along with insert. Defaults to `null`, meaning relations will not be\n  automatically created unless specified here. See\n  [`allowGraph`](https://vincit.github.io/objection.js/api/query-builder/eager-methods.html#allowgraph)\n  documentation.\n- **`insertGraphOptions`** - See\n  [`insertGraphOptions`](https://vincit.github.io/objection.js/api/types/#type-insertgraphoptions)\n  documentation.\n\n#### Params Operators\n\n- **`mergeAllowInsert`** - Merge given expression into `allowedInsert`.\n- **`mergeInsertGraphOptions`** - Merge given options into `insertGraphOptions`.\n\n### Transaction\n\n[Create a transaction](https://vincit.github.io/objection.js/guide/transactions.html#creating-a-transaction) \nobject and pass it to series of service calls using the `transaction` params operator.  \nCommit the transaction by calling `await transaction.trx.commit()`.  \nRollback by calling `await transaction.trx.rollback()`.\n\n### Service\n\nusers.service.js\n\n```js\nconst createService = require('feathers-objection');\nconst createModel = require('../../models/users.model');\nconst hooks = require('./users.hooks');\n\nmodule.exports = function(app) {\n  const Model = createModel(app);\n  const paginate = app.get('paginate');\n\n  const options = {\n    model: Model,\n    paginate,\n    whitelist: ['$eager', '$joinRelation'],\n    allowedEager: 'todos'\n  };\n\n  app.use('/users', createService(options));\n\n  const service = app.service('users');\n\n  service.hooks(hooks);\n};\n```\n\ntodos.service.js\n\n```js\nconst createService = require('feathers-objection');\nconst createModel = require('../../models/todos.model');\nconst hooks = require('./todos.hooks');\n\nmodule.exports = function(app) {\n  const Model = createModel(app);\n  const paginate = app.get('paginate');\n\n  const options = {\n    model: Model,\n    paginate,\n    whitelist: ['$eager', '$joinRelation'],\n    allowedEager: '[user, subtask]',\n    eagerFilters: [\n      {\n        expression: 'subtask',\n        filter: function(builder) {\n          builder.where('archived', true);\n        }\n      }\n    ]\n  };\n\n  app.use('/todos', createService(options));\n\n  const service = app.service('todos');\n\n  service.hooks(hooks);\n};\n```\n\nUse eager queries as follows:\n\n```js\n// Get all todos and their unfinished tasks\napp.service('/todos').find({\n  query: {\n    $eager: 'subtask(unDone)'\n  }\n});\n\n// Get all todos of an active user with firstName 'John'\napp.service('/todos').find({\n  query: {\n    'user.firstName': 'John',\n    $eager: 'user(active)',\n    $joinRelation: 'user(active)'\n  }\n});\n```\n\nSee\n[this article](https://www.vincit.fi/blog/nested-eager-loading-and-inserts-with-objection-js/)\nfor more information.\n\n### Models\n\nObjection requires you to define\n[Models](https://vincit.github.io/objection.js/api/model/#models) with [JSON Schema](https://json-schema.org/understanding-json-schema/) format for your tables:\n\nusers.model.js\n\n```js\nconst { Model } = require('objection');\n\nclass User extends Model {\n  static get tableName() {\n    return 'user';\n  }\n\n  static get jsonSchema() {\n    return {\n      type: 'object',\n      required: ['firstName', 'lastName'],\n\n      properties: {\n        id: { type: 'integer' },\n        firstName: { type: 'string', maxLength: 45 },\n        lastName: { type: 'string', maxLength: 45 },\n        status: {\n          type: 'string',\n          enum: ['active', 'disabled'],\n          default: 'active'\n        },\n        address: {\n          type: 'object',\n          properties: {\n            street: { type: 'string' },\n            city: { type: 'string' },\n            zipCode: { type: 'string' }\n          }\n        },\n        list: {\n          type: 'array',\n          maxItems: 3,\n          items: { type: 'string' }\n        }\n      }\n    };\n  }\n\n  static get relationMappings() {\n    const Todo = require('./todos.model')();\n\n    return {\n      todos: {\n        relation: Model.HasManyRelation,\n        modelClass: Todo,\n        join: {\n          from: 'user.id',\n          to: 'todo.userId'\n        }\n      }\n    };\n  }\n\n  static get modifiers() {\n    return {\n      active: builder =\u003e {\n        builder.where('status', 'active');\n      }\n    };\n  }\n\n  $beforeInsert() {\n    this.createdAt = this.updatedAt = new Date().toISOString();\n  }\n\n  $beforeUpdate() {\n    this.updatedAt = new Date().toISOString();\n  }\n}\n\nmodule.exports = function(app) {\n  if (app) {\n    const db = app.get('knex');\n\n    db.schema\n      .hasTable('user')\n      .then(exists =\u003e {\n        if (!exists) {\n          db.schema\n            .createTable('user', table =\u003e {\n              table.increments('id');\n              table.string('firstName', 45);\n              table.string('lastName', 45);\n              table.enum('status', ['active', 'disabled']).defaultTo('active');\n              table.timestamp('createdAt');\n              table.timestamp('updatedAt');\n            })\n            .then(() =\u003e console.log('Created user table'))\n            .catch(e =\u003e console.error('Error creating user table', e));\n        }\n      })\n      .catch(e =\u003e console.error('Error creating user table', e));\n  }\n\n  return User;\n};\n\nmodule.exports = User;\n```\n\ntodos.model.js\n\n```js\nconst { Model } = require('objection');\n\nclass Todo extends Model {\n  static setup(app) {\n    this.app = app;\n  }\n\n  static get tableName() {\n    return 'todo';\n  }\n\n  static get jsonSchema() {\n    return {\n      type: 'object',\n      required: ['userId', 'text'],\n\n      properties: {\n        id: { type: 'integer' },\n        userId: { type: 'integer' },\n        text: { type: 'string', maxLength: 500 },\n        complete: { type: 'boolean', default: false },\n        dueDate: { type: 'string', format: 'date-time' }\n      }\n    };\n  }\n\n  static get relationMappings() {\n    const User = require('./users.model')();\n\n    return {\n      user: {\n        relation: Model.BelongsToOneRelation,\n        modelClass: User,\n        join: {\n          from: 'todo.userId',\n          to: 'user.id'\n        }\n      }\n    };\n  }\n\n  static get modifiers() {\n    const knex = this.app.get('knex');\n\n    return {\n      unDone: function(builder) {\n        builder.where('complete', false);\n      },\n      overdue: builder =\u003e {\n        builder\n          .where('complete', false)\n          .where('dueDate', '\u003c', knex.fn.now());\n      }\n    };\n  }\n\n  $beforeInsert() {\n    this.createdAt = this.updatedAt = new Date().toISOString();\n  }\n\n  $beforeUpdate() {\n    this.updatedAt = new Date().toISOString();\n  }\n}\n\nmodule.exports = function(app) {\n  if (app) {\n    Todo.setup(app);\n\n    const db = app.get('knex');\n\n    db.schema\n      .hasTable('todo')\n      .then(exists =\u003e {\n        if (!exists) {\n          db.schema\n            .createTable('todo', table =\u003e {\n              table.increments('id');\n              table.integer('userId');\n              table.string('text', 500);\n              table.boolean('complete');\n              table.timestamp('dueDate');\n              table.timestamp('createdAt');\n              table.timestamp('updatedAt');\n            })\n            .then(() =\u003e console.log('Created todo table'))\n            .catch(e =\u003e console.error('Error creating todo table', e));\n        }\n      })\n      .catch(e =\u003e console.error('Error creating todo table', e));\n  }\n\n  return Todo;\n};\n```\n\n## Complete Example\n\nHere's a complete example of a Feathers server with a `todos` SQLite service:\n\n`$ npm install @feathersjs/feathers @feathersjs/express body-parser feathers-objection objection knex sqlite3`\n\napp.js\n\n```js\nconst feathers = require('@feathersjs/feathers');\nconst express = require('@feathersjs/express');\nconst rest = require('@feathersjs/express/rest');\nconst errorHandler = require('@feathersjs/express/errors');\nconst bodyParser = require('body-parser');\nconst createService = require('feathers-objection');\nconst { Model } = require('objection');\n\nconst knex = require('knex')({\n  client: 'sqlite3',\n  connection: {\n    filename: './db.sqlite'\n  },\n  useNullAsDefault: false\n});\n\n// Bind Objection.js\nModel.knex(knex);\n\n// Clean up our data. This is optional and is here\n// because of our integration tests\nknex.schema.dropTableIfExists('todo').then(function() {\n  console.log('Dropped todo table');\n\n  // Initialize your table\n  return knex.schema.createTable('todo', function(table) {\n    console.log('Creating todo table');\n    table.increments('id');\n    table.string('text');\n    table.boolean('complete');\n    table.timestamp('createdAt');\n    table.timestamp('updatedAt');\n  });\n});\n\n// Create a feathers instance.\nconst app = express(feathers())\n  // Enable REST services\n  .configure(rest())\n  // Turn on JSON parser for REST services\n  .use(bodyParser.json())\n  // Turn on URL-encoded parser for REST services\n  .use(bodyParser.urlencoded({ extended: true }));\n\n// Create an Objection Model\nclass Todo extends Model {\n  static get tableName() {\n    return 'todo';\n  }\n\n  static get jsonSchema() {\n    return {\n      type: 'object',\n      required: ['text'],\n\n      properties: {\n        id: { type: 'integer' },\n        text: { type: 'string' },\n        complete: { type: 'boolean', default: false }\n      }\n    };\n  }\n\n  $beforeInsert() {\n    this.createdAt = this.updatedAt = new Date().toISOString();\n  }\n\n  $beforeUpdate() {\n    this.updatedAt = new Date().toISOString();\n  }\n}\n\n// Create Objection Feathers service with a default page size of 2 items\n// and a maximum size of 4\napp.use(\n  '/todos',\n  createService({\n    model: Todo,\n    id: 'id',\n    paginate: {\n      default: 2,\n      max: 4\n    }\n  })\n);\n\n// Handle Errors\napp.use(errorHandler());\n\n// Start the server\nmodule.exports = app.listen(3030);\n\nconsole.log('Feathers Todo Objection service running on 127.0.0.1:3030');\n```\n\nRun the [example](https://github.com/feathersjs-ecosystem/feathers-objection/tree/master/example) app with `npm run example` and go to\n[localhost:3030/todos](http://localhost:3030/todos).\n\nYou should see an empty array. That's because you don't have any Todos yet, but\nyou now have full CRUD for your new todos service!\n\n## DB migrations\n\n[Knex Migration CLI](http://knexjs.org/#Migrations) can be used to manage DB migrations \nand to [seed](http://knexjs.org/#Seeds) a table with mock data.\n  \n## Error handling\nAs of version 4.8.0, `feathers-objection` only throws [Feathers Errors](https://docs.feathersjs.com/api/errors.html) \nwith the message.  \nOn the server, the original error can be retrieved through a secure symbol via  `error[require('feathers-objection').ERROR]`.\n\n```js\nconst { ERROR } = require('feathers-objection');\n\ntry {\n  await objectionService.doSomething();\n} catch (error) {\n  // error is a FeathersError with just the message\n  // Safely retrieve the original error\n  const originalError = error[ERROR];\n}\n```\n\nAs of version 7.0.0, `feathers-objection` has normalized errors accross all databases supported by Objection, and makes a best-effort attempt to provide reasonable error messages that can be returned directly to the client.\n\nIf these error messages do not work for your needs, the original error is still available using the symbol described above.\n\n## Migrating to `feathers-objection` v2\n\n`feathers-objection` 2.0.0 comes with important security and usability updates\n\n\u003e **Important:** For general migration information to the new database adapter functionality see    \n\u003e [crow.docs.feathersjs.com/migrating.html#database-adapters](https://crow.docs.feathersjs.com/migrating.html#database-adapters)\n\nThe following breaking changes have been introduced:\n\n- All methods allow additional query parameters\n- Multiple updates are disabled by default (see the `multi` option)\n- Objection related operators are disabled by default (see the `whitelist`\n  option)\n  \n## Migrating to `feathers-objection` v5\n\n`feathers-objection` 5.0.0 comes with usability updates and was migrated to use Objection v2\n\n\u003e **Important:** For general migration information to Objection v2 see  \n\u003e [https://vincit.github.io/objection.js/release-notes/migration.html](https://vincit.github.io/objection.js/release-notes/migration.html)\n\nThe following breaking changes have been introduced:\n\n- `$pick` query operator was removed\n- `namedEagerFilters` service option was removed. use Model's [`modifiers`](https://vincit.github.io/objection.js/recipes/modifiers.html#modifiers) instead\n- Model's `namedFilters` property was renamed to `modifiers`\n\n## Migrating to `feathers-objection` v6\n\n`feathers-objection` 6.0.0 comes with usability and security updates\n\n- `$not` operator is now available. It can be used with an object `$not: { name: { $in: [\"craig\", \"tim\"] } }` or array `$not: [ { $id: 1 }, { $id: 2 } ]`\n- `$eager` is no longer needed with upsert operations\n\nThe following breaking changes have been introduced:\n\n- Graph upsert now requires that `id` fields in the `data` object will match the `id` argument\n- `$noSelect` now always return the input data\n- `$select` is now honored with upsert methods\n- `patch` method now enforce `params.query` with upsert\n- NotFound error will be thrown when `get` \u0026 `update` methods are called with different values in `id` \u0026 `params.query.id`\n\n## Migrating to `feathers-objection` v7\n\n`feathers-objection` 7.0.0 comes with improved error handling.\n\nThe following breaking changes have been introduced:\n\n- All Databases will return the same types of errors based on the underlying Objection error\n- SQL Driver error text is no longer used as the Feathers error message\n- Objection errors are mapped more accurately to Feathers errors, e.g.\n  - Objection's `UniqueViolationError` -\u003e Feathers' `Conflict` error type\n\n## License\n\nCopyright © 2020\n\nLicensed under the [MIT license](LICENSE).\n","funding_links":[],"categories":["Plugins"],"sub_categories":["Database"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffeathersjs-ecosystem%2Ffeathers-objection","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffeathersjs-ecosystem%2Ffeathers-objection","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffeathersjs-ecosystem%2Ffeathers-objection/lists"}