JSORM the isomorphic, framework-agnostic Javascript ORM

Reads

The interface for read operations is a simpler version of the ActiveRecord Query Interface. Instead of generating SQL, we’ll be generating JSONAPI requests.

Basic Finders

Execute queries with .all(), find(), or .first():

Typescript
Javascript
let response = await Post.all()
response.data // array of Post instances
  
Post.all().then(function(response) {
  response.data // array of Post instances
});
  

GET /posts

Typescript
Javascript
let response = await Post.find(123)
response.data // Post instance
  
Post.find(123).then(function(response) {
  response.data // Post instance
});
  

GET /posts/123

Typescript
Javascript
let response = await Post.first()
response.data // Post instance
  
Post.first().then(function(response) {
  response.data // Post instance
});
  

GET /posts?page[size]=1

Composable Queries with Scopes

The beauty of ORMs is their ability to compose queries. We’ll be doing this by chaining together Scopes (query fragments). All of the methods you see on this page can be chained together - the request will not fire until the chain ends with all(), first(), or find. Example:

Typescript
Javascript
let scope = Post.order({ name: "desc" })

if (someCheckboxIsChecked) {
  scope = scope.where({ important: true })
} else {
  scope = scope.where({ important: false })
}

scope.all() // request fires
  
var scope = Post.order({ name: "desc" });

if (someCheckboxIsChecked) {
  scope = scope.where({ important: true });
} else {
  scope = scope.where({ important: false });
}

scope.all() // request fires
  

/posts?sort=-name&filter[important]=true

/posts?sort=-name&filter[important]=false

In practice, you’ll probably have some scopes you want to re-use across different contexts. A best practice is to store these scopes as class methods (static methods) in the model:

Typescript
Javascript
class Post extends ApplicationRecord {
  // ... code ...
  static superImportant() {
    return this
      .where({ ranking_gt: 8 })
      .order({ ranking: 'desc' })
      .stats({ total 'count' })
  }
}

// get 10 super important posts
let scope = Post.superImportant().per(10)
scope.all() // fire query
  
const Post = ApplicationRecord.extend({
  // ... code ...
  static: {
    superImportant() {
      return this
        .where({ ranking_gt: 8 })
        .order({ ranking: 'desc' })
        .stats({ total 'count' })
    }
  }
})

// get 10 super important posts
var scope = Post.superImportant().per(10);
scope.all() // fire query
  

/posts?sort=-ranking&stats[total]=count&page[size]=10&filter[ranking_gt]=8

Metadata

The meta information of the JSONAPI response is available as a POJO on the response:

Typescript
Javascript
let response = await Post.all()
response.meta // { stats: { total: { count: 100 } } }
  
await Post.all().then(function(response) {
  response.meta // { stats: { total: { count: 100 } } }
})
  

Promises and Async/Await

The result of all(), first() or find is a Promise. The promise will resolve to a Response object.

A Response object has three keys - data, meta, and raw. data - the one you’ll be using the most - will be a Model instance (or array of Model) instances. meta will be the Meta Information returned by the API (mostly used for statistics in our case). raw is only used to introspect the raw response document.

Typescript
Javascript
Post.all().then((response) => {
  response.data // array of Post instances
  response.meta // js object from the server
  response.raw // js response document
})
  
Post.all().then(function(response) {
  response.data // array of Post instances
  response.meta // js object from the server
  response.raw // js response document
});
  

/posts

Hopefully you’re running in an environment that supports ES7’s Async/Await. This makes things even easier:

let { data } = await Post.all()
data // array of Post instances

// alternatively

let posts = (await Post.all()).data
posts // array of Post instances

/posts