Data Store

The simplest use case of Kinvey is storing and retrieving data to and from your cloud backend.

The basic unit of data is an entity and entities of the same kind are organized in collections. An entity is a set of Key-Value pairs which is stored in the backend in JSON format. Kinvey's libraries automatically translate your native objects to JSON.

Kinvey's data store provides simple CRUD operations on data, as well as powerful filtering and aggregation.

Entities

The Kinvey service has the concept of entities, which represent a single resource. In the library, such a resource is represented by an object literal.

{
    _id  : 'entity-id',
    key1 : 'value1',
    key2 : 'value2'
    ...
}

The library does not provide any built-in classes to represent entities. There are a large variety of third party JavaScript libraries which do provide this, and most of them will accept object literals as entity attributes.

If you are using Backbone.js, we recommend you use Kinvey’s Backbone library which provides tighter intregation between Kinvey and Backbone.js.

Saving

You can save an entity by calling save.

var promise = Kinvey.DataStore.save('collection-name', {
    _id  : 'optional-id',
    prop : 'value'
}, {
    success: function(response) {
        ...
    }
});

If you want to update an existing entity (which has an _id), you can use either Kinvey.DataStore.save or Kinvey.DataStore.update.

Fetching

You can retrieve entities by either looking them up using an id, or by querying a collection.

Fetching by Id

To fetch an (one) entity by id, call Kinvey.DataStore.get.

var promise = Kinvey.DataStore.get('collection-name', ':id', {
    success: function(response) {
        ...
    }
});

Fetching by Query

To fetch all models in a collection, call Kinvey.DataStore.find.

var promise = Kinvey.DataStore.find('collection-name', null, {
    success: function(response) {
        ...
    }
});

To fetch multiple entities using a query, call Kinvey.DataStore.find and pass in a query.

var query = new Kinvey.Query();
query.equalTo('prop', 'value');

var promise = Kinvey.DataStore.find('collection-name', query, {
    success: function(response) {
        ...
    }
});

Deleting

To delete an entity, call Kinvey.DataStore.destroy and pass in the entity _id.

var promise = Kinvey.DataStore.destroy('collection-name', ':id', {
    success: function(response) {
        ...
    }
});

Deleting Multiple Entities at Once

To delete multiple entities at once, call Kinvey.DataStore.clean. Optionally, you can pass in a query to only delete entities matching the query.

var query = new Kinvey.Query();
query.equalTo('prop', 'value');

var promise = Kinvey.DataStore.clean('collection-name', query, {
    success: function(response) {
        ...
    }
});

Metadata

Every entity has metadata associated with it. The metadata is accessible through the entities’ _kmd property. Alternatively, you can use the Kinvey.Metadata class to easily extract this metadata.

var entity = {
    _id  : 'entity-id',
    _acl : { /*ACL*/ },
    _kmd : { /*Metadata*/ },
    prop : 'value'
}
var metadata = new Kinvey.Metadata(entity);

The following methods are exposed on the metadata object:

  • metadata.getCreatedAt() returns the Date when the entity was created on Kinvey.
  • metadata.getLastModified() returns the Date when the entity was last updated on Kinvey.
  • metadata.getAcl() returns the Acl object. Read the Security guide on Entity Level Permissions for more information.

Querying

The Kinvey.Query class allows you to build queries for use in collections. An empty query, by default, matches all models in a collection.

var query = new Kinvey.Query();

Operators

All operator methods as exposed by Kinvey.Query follow the same pattern: the first argument must be the field under condition, while the other arguments specify the exact condition on that field. All operators return the query itself, so it is easy to concatenate multiple condition on one line.

For example, to select all models with a rate between 25 and 50:

var query = new Kinvey.Query();
query.greaterThanOrEqualTo('rate', 25).lessThanOrEqualTo('rate', 50);

Comparison Operators

  • equalTo matches if the field is = the supplied value.
  • greaterThan matches if the field is > the supplied value.
  • greaterThanOrEqualTo matches if the field is >= the supplied value.
  • lessThan matches if the field is < the supplied value.
  • lessThanOrEqualTo matches if the field is <= the supplied value.
  • notEqualTo matches if the field is != the supplied value.
  • exists matches if the field exists.
  • mod matches if the field modulo the supplied divisor (second argument) has the supplied remainder (third argument).
  • matches matches if the field matches the supplied regular expression.

Regular expressions need to be anchored (prefixed with ^), and case sensitive. To do case insensitive search, create a normalized (i.e. all lowercase) field in your collection and perform the match on that field.

Array Operators

  • contains matches if any of the supplied values is an element in field.
  • containsAll matches if the supplied values are all elements in field.
  • notContainedIn matches if the supplied value is not an element in field.
  • size matches if the number of elements in field equals the supplied value.

Modifiers

Query modifiers control how query results are presented. A distinction is made between limit, skip, and sort modifiers.

Limit and Skip

Limit and skip modifiers allow for paging of results. Set the limit to the number of results you want to show per page. The skip modifier indicates how many results are skipped from the beginning.

var query = new Kinvey.Query();
query.limit(20);
query.skip(0);// The first page, shows results 0–20.
query.skip(20);// Second page, show results 20–40.

Sort

Query results are sorted either in ascending or descending order. It is possible to add multiple fields to sort on.

// Sort on last name (ascending), then on age (descending).
var query = new Kinvey.Query();
query.ascending('last_name');
query.descending('age');

Data is sorted lexicographically, meaning B comes before a, and 10 before 2.

Field Selection

By default, all fields in a document will be retrieved. You can, however, specify specific fields to retrieve. This can be useful to save bandwidth.

var query = new Kinvey.Query();
query.fields([ 'last_name', 'age' ]);

Saving documents after retrieving them using Field Selection will result in the loss of all fields not selected.

Compound Queries

You can combine filters with modifiers within a single query.

// Returns the first five users with last_name “Doe”, sorted by first_name.
var query = new Kinvey.Query();
query.limit(5).equalTo('last_name', 'Doe').ascending('first_name');

Joining Operators

It is very easy to join multiple queries into one. In order of precedence, the three joining operators are listed below in order of precendence.

  • and joins two or more queries using a logical AND operation.
  • nor joins two or more queries using a logical NOR operation.
  • or joins two or more queries using a logical OR operation.

The example below demonstrates how to join two separate queries.

var query = new Kinvey.Query();
query.equalTo('last_name', 'Doe');
var secondQuery = new Kinvey.Query();
secondQuery.equalTo('last_name', 'Roe')

// Selects all users with last_name “Doe” or “Roe”.
query.or(secondQuery);

Alternatively, the snippet above can be shortened using the join operator inline.

// Selects all users with last_name “Doe” or “Roe”.
var query = new Kinvey.Query();
query.equalTo('last_name', 'Doe').or().equalTo('last_name', 'Roe');

You can build arbitrary complex queries using any join operators. The rule of thumb is to take the precendence order into account when building queries to make sure the correct results are returned.

Counting

To count the number of entities in a collection, call Kinvey.DataStore.count. Optionally, you can pass in a query to only count the entities matching the query.

Aggregation/Grouping

Grouping allows you to collect all entities with the same value for a field or fields, and then apply a reduce function (such as count or average) on all those items.

The results are returned as an object literal that represents the list of groups containing the result of the reduce function.

For example, let’s group a collection of companies based on the year they launched.

var promise = Kinvey.DataStore.group('companies', Kinvey.Group.count('launched'), {
    success: function(response) {
        ...
    }
});

Reduce Function

There are five pre-defined reduce functions.

  • Kinvey.Group.count counts the number of elements in the group.
  • Kinvey.Group.sum sums together the numeric values of the supplied field.
  • Kinvey.Group.min finds the minimum of the numeric values of the supplied field.
  • Kinvey.Group.max finds the maximum of the numeric values of the supplied field.
  • Kinvey.Group.average finds the average of the numeric values of the supplied field.

Scoping With Queries

Groups can also take an optional condition. This is a query that acts as a filter that is applied on the server before the reduce function is evaluated. Any limit, skip, and sort modifiers are applied after the reduce function is evaluated.

In our above example, assume we wanted to group only the companies who have offices in Boston. In addition, we want to order the resulting groups by the year they launched and only display the first two. It would look like:

// Build the query and group.
var query = new Kinvey.Query();
query.equalTo('city', 'Boston').ascending('launched').limit(2);

var group = Kinvey.Group.count('launched');
group.query(query);

var promise = Kinvey.DataStore.group('companies', group, {
    success: function(response) {
        ...
    }
});

Location Querying

See the Location guide for information on how to query data by location.

Relational Data

Your data often includes entities with relationships to other entities. Relationships can be modeled either by denormalization or by having properties that are pointers to other objects.

Setup

An app can establish a reference from one entity to another by embedding a reference to the second entity as a value of a property in the first entity. This can be done while saving the first entity. An app can establish references anywhere in the entity property hierarchy. This includes embedding references in array-valued properties.

Saving

To save an entity and its relations, you need to specify the references you want to save along with the entity.

The snippet below demonstrates how to save an event, and save its location as separate entity. The relations property maps the entity property name to the collection of the referenced entity. In the example, the object under the location property is saved in the locations collection. The library will initiate two save requests: one for the location, and one for the event.

var event = {
    name     : 'My Birthday Party',
    location : {
        city    : 'Boston',
        state   : 'MA',
        country : 'USA'
    }
};

// Save the event, and save its location as separate entity.
var promise = Kinvey.DataStore.save('events', event, {
    relations : { location: 'locations' },
    success   : function(response) {
       ...
    }
});

The returned response will be the the saved event with its location reference embedded:

{
    _id      : 'event-id',
    _acl     : { /* ACL */ },
    _kmd     : { /* Metadata */ },
    name     : 'My Birthday Party',
    location : {
        _id     : 'location-id',
        _acl    : { /* ACL */ },
        _kmd    : { /* Metadata */ },
        city    : 'Boston',
        state   : 'MA',
        country : 'USA'
    }
}

The example above demonstrates how to create a new entity with references. The process for updating an existing entity with existing (or new) references works the same way: call Kinvey.DataStore.save and specify what properties contain references to what collections. Internally, the library will determine whether the reference already exists and needs updating, or to create a new one.

If you have deeply nested properties, use the dot-separator to map the reference. For example, foo.bar resolves to the bar property inside the foo object.

Control the Saving of References

The section above explains how to save an entity with all its references. However, sometimes, it might not be desirable or possible to save an entity and all its references. For example, you might not have write permission to a referenced entity. The exclude option allows you to control which references are saved.

The snippet below demonstrates how to save an entity, but not save the reference embedded in the location property. The library will initiate only one save request for the event.

var event = {
    name     : 'My Birthday Party',
    location : {
        _id     : 'location-id',
        _acl    : { /* ACL */ },
        _kmd    : { /* Metadata */ },
        city    : 'Boston',
        state   : 'MA',
        country : 'USA'
    }
};

// Save the event, but do not save the location.
var promise = Kinvey.DataStore.save('events', event, {
    exclude   : [ 'location' ],
    relations : { location: 'locations' },
    success   : function(response) {
       ...
    }
});

The returned response will be the saved event with its location reference embedded:

{
    _id      : 'event-id',
    _acl     : { /* ACL */ },
    _kmd     : { /* Metadata */ },
    name     : 'My Birthday Party',
    location : {
        _id     : 'location-id',
        _acl    : { /* ACL */ },
        _kmd    : { /* Metadata */ },
        city    : 'Boston',
        state   : 'MA',
        country : 'USA'
    }
}

If you exclude a reference from saving, you still need to include the relation in the relations property. Also, if you exclude a reference, that entities references (if any) will be excluded as well.

Fetching

To fetch an entity with its references embedded, you need to specify the references you want to resolve. You need to explicitly indicate in what collection the reference resides.

The snippet below demonstrates how to fetch an event, and resolve its location reference.

var promise = Kinvey.DataStore.get('events', 'event-id', {
    relations : { location: 'locations' },
    success   : function(response) {
        ...
    }
});

The returned response will be the retrieved event with its location reference embedded.

{
    _id      : 'event-id',
    _acl     : { /* ACL */ },
    _kmd     : { /* Metadata */ },
    name     : 'My Birthday Party',
    location : {
        _id     : 'location-id',
        _acl    : { /* ACL */ },
        _kmd    : { /* Metadata */ },
        city    : 'Boston',
        state   : 'MA',
        country : 'USA'
    }
}

The relations property is also supported when fetching by query.

var promise = Kinvey.DataStore.find('events', query, {
    relations : { location: 'locations' },
    success   : function(response) {
        ...
    }
});

Queries

Kinvey does not support queries that peer into a related object’s properties. However, you can construct queries to retrieve all entities that have a relationship to a specific entity. This is done by querying on the _id field of the reference. For example, to find what events take place in Boston, query for the _id of the Boston location record:

var query = new Kinvey.Query();
query.equalTo('location._id', 'boston-location-id');

var promise = Kinvey.DataStore.find('events', query, {
    relations : { location: 'locations' },
    success   : function(response) {
       ...
    }
});

When fetching, the relations property is a map of relational fields to their collection ({ "field": "collection-name" }). The collection name is used to save the retrieved data correctly when using [Caching and Offline Saving]. Therefore, it is highly recommended to use the correct collection name.

Limitations

When using this feature, you need to be aware of the following limitations:

  • Per request, a maximum of 100 references will be resolved. This limit is enforced to achieve a good trade-off between wait times and user experience.
  • You cannot nest relations deeper than 10 levels.
  • When loading models, if two models share a reference to a common third models, two separate models will be instantiated for that third model. This means two objects will be created, holding the same data. Changes to one of these objects will not be reflected in the other, meaning saving an object will override each others’ changes.
  • Complex object-graphs (including circular references) will not be saved completely.

User Relationships

There are times you may want to reference users. We highly recommended to exclude referenced users when saving, since it is likely you do not have write access to the embedded user. The example below will save the event, but not save the active user.

var event = {
    name  : 'My Birthday Party',
    owner : Kinvey.getActiveUser()
};
var promise = Kinvey.DataStore.save('events', event, {
    exclude   : [ 'owner' ],
    relations : { owner: 'user' },
    success   : function(response) {
        ...
    }
});

Related Samples

Got a question?