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

An entity is represented by a Kinvey.Entity object. Each entity belongs to an entity collection. For example, let’s assume we are building a calendar application, and want to represent an event. Our event will have a name, time & date, and a location. Here is what the entity might look like:

var myBirthdayParty = new Kinvey.Entity({
    name:     'Birthday Party',
    date:     '2012-12-01 16:30:00',
    location: 'Mike’s House'
}, 'events');

Instead of using Kinvey.Entity directly, you can also define your own classes. This allows you to add additional methods or properties to instances of this class. For example, let’s define an Event class:

var Event = Kinvey.Entity.extend({
    // Overwrite the constructor to automatically link all instances to the events collection.
    constructor: function(attributes) {
        Kinvey.Entity.prototype.constructor.call(this, attributes, 'events');
    },

    // Returns the name of the event.
    getName: function() {
        return this.get('name');
    },

    // Returns the date of the event.
    getDate: function() {
        return this.get('date');
    },

    // Returns the location of the event.
    getLocation: function() {
        return this.get('location');
    }
});

// Here is what the entity may look like:
var myBirthdayParty = new Event({
    name:     'Birthday Party',
    date:     '2012-12-01 16:30:00',
    location: 'Mike’s House'
});
alert(myBirthdayParty.getLocation());// Displays "Mike’s House".

In the above example, Kinvey.Entity.prototype.constructor.call(this) invokes the parent constructor. Generally, use <class>.prototype.<method>.call(this) to invoke a parent method.

Attributes

The Kinvey.Entity class exposes a number of methods to manage its Key/Value pairs. To obtain a value, use get:

var name = myBirthdayParty.get('name');
var date = myBirthdayParty.get('date');
var location = myBirthdayParty.get('location');

The Kinvey entity id is obtained using getId:

var id = myBirthdayParty.getId();

Changing or adding Key/Value pairs is done using set:

myBirthdayParty.set('host', 'Mike');
myBirthdayParty.set('guestlist', ['Mark', 'Morgan']);

To remove a Key/Value pair, use unset:

myBirthdayParty.unset('host');
myBirthdayParty.unset('guestlist');

Sometimes, it is useful to have raw access to all Key/Value pairs. By using toJSON and passing in true, all Key/Value pairs are returned in JSON format:

var attributes = myBirthdayParty.toJSON(true);

Collections

Entities are organized in collections. A Kinvey.Collection object manages a set of entities. For example, creating a calendar collection to manage events looks like:

var myCalendar = new Kinvey.Collection('events');

You can define your own collection classes. This way, you can add additional methods or properties to instances of this class. For example, let’s define a Calendar class to manage instances of the Event class:

// Define our entity class.
var Event = Kinvey.Entity.extend(/* ... */);

// Calendar should manage only entities of type Event.
var Calendar = Kinvey.Collection.extend({
    // Set the entity class.
    entity: Event,

    // Overwrite the constructor to automatically link all instances to the events collection.
    constructor: function(options) {
        Kinvey.Collection.prototype.constructor.call(this, 'events', options);
    }
});

// Create a calendar instance.
var myCalendar = new Calendar();

Saving

Use Kinvey.Entity.save to save an entity on Kinvey:

myBirthdayParty.save({
    success: function(myBirthdayParty) {
        // myBirthdayParty is the saved entity.
    },
    error: function(e) {
        // Failed to save myBirthdayParty.
        // e holds information about the nature of the error.
    }
});

Fetching

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

Fetching by Id

Use Kinvey.Entity.load to retrieve an entity by its id:

var myBirthdayParty = new Kinvey.Entity({}, 'events');
myBirthdayParty.load('birthday-party-id', {
    success: function(myBirthdayParty) {
        // myBirthdayParty is the whole entity.
    },
    error: function(e) {
        // Failed to fetch birthday-party-id.
        // e holds information about the nature of the error.
    }
});

Fetching by Query

Use Kinvey.Collection.fetch to return all entities in a collection:

myCalendar.fetch({
    success: function(list) {
        // list holds all events in my calendar.
    },
    error: function(e) {
        // Failed to fetch events.
        // e holds information about the nature of the error.
    }
});

A Kinvey.Query object is used to retrieve a specific set of entities. By default, a Kinvey.Query object matches all entities. To learn more on how to write complex queries, see the Querying section. A query is created and passed to a collection as follows:

// Create a query.
var myQuery = new Kinvey.Query();

// Retrieve all entities matching myQuery.
myCalendar.setQuery(myQuery);
myCalendar.fetch({
    success: function(list) {
        // list holds all events matching myQuery.
    },
    error: function(e) {
        // Failed to fetch events matching myQuery.
        // e holds information about the nature of the error.
    }
});

You can obtain the result set of a query outside the success callback using:

myCollection.query({ /* ... */ });

// Make sure the query call above has completed.
// Now, you can retrieve its result set again: 
var results = myCollection.list;

Only the result set from the last query call is available through the list property. Be careful using this property, as due to the asynchrony of the library the property may not hold the entities you expect.

Deleting

Use Kinvey.Entity.destroy to delete an entity:

myBirthdayParty.destroy({
    success: function() {
        // myBirthdayParty is deleted.
    },
    error: function(e) {
        // Failed to delete myBirthdayParty.
        // e holds information about the nature of the error.
    }
});

Deleting Multiple Entities at Once

Use Kinvey.Collection.clear to delete all entities matching a query:

// Create a query.
var myQuery = new Kinvey.Query();

// Remove all events matching myQuery.
myCalendar.setQuery(myQuery);
myCalendar.clear({
    success: function() {
        // All events matching myQuery are deleted.
    },
    error: function(e) {
        // Failed to delete events matching myQuery.
        // e holds information about the nature of the error.
    }
});

Metadata

Every entity has a Kinvey.Metadata object associated with it. This class provides meta-information about an entity. You can obtain the metadata object using:

var metadata = myBirthdayParty.getMetadata();

The returned object exposes the following method:

  • lastModified returns the time the entity was last updated on the server.

Additional methods are available to control read and write permissions of the entity. These are covered in the Security guide.

Querying

The Kinvey.Query class allows you to execute queries to the Kinvey backend. By default, a query object matches all entities in a collection:

var myQuery = new Kinvey.Query();

Queries are always executed in the context of a Kinvey.Collection object. When creating a collection, you can pass in a query:

var myCollection = new Kinvey.Collection('my-collection', { query: myQuery });

Alternatively, setQuery allows you to specify a query at a later time:

myCollection.setQuery(myQuery);

Operators

Adding conditions to a query is easy. The general pattern is that you specify the field under condition first, using on. After, you can attach any number of conditions to it. For example, imagine we want to select all users between 25 and 50 years old:

// 25 < age < 50.
myQuery.on('age').greaterThan(25).lessThan(50);

Comparison Operators

  • equal matches where field is = the supplied value.
  • exist matches if the field exists.
  • lessThan matches if field is < the supplied value.
  • lessThanEqual matches if field is <= the supplied value.
  • greaterThan matches if field is > the supplied value.
  • greaterThanEqual if where field is >= the supplied value.
  • notEqual matches if field != the supplied value.
  • regex matches if field matches the supplied regular expression.

Array Operators

  • all matches if field is an array containing all of the supplied values.
  • in_ matches if field is an array containing any of the supplied values.
  • notIn matches if field is an array not containing any of the supplied values.
  • size matches if the number of elements in field equals the supplied value.

The underscore in the in_ method is on purpose, since in is a reserved word in JavaScript.

Modifiers

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

Limit and Skip

Limit and Skip modifiers allow for paging of results. You can 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:

myQuery.setLimit(20);
myQuery.setSkip(0);// First page, show results 0–20.
myQuery.setSkip(20);// Second page, show results 20–40.

Sort

Query results can be sorted using sort. The order can be either ascending or descending:

// Sort on age, ascending.
myQuery.on('age').sort(Kinvey.Query.ASC);

// Sort on age, descending
myQuery.on('age').sort(Kinvey.Query.DESC);

Compound Queries

You can combine filters with modifiers using a single query.

# Returns the first 5 entities with firstName "John" in ascending order of lastName.
var myQuery = new Kinvey.Query();
myQuery.on('firstName').equal('John');
myQuery.setLimit(5);
myQuery.on('lastName').sort();

Joining Operators

You can use Kinvey.Query.and or Kinvey.Query.or to join two queries using the AND- or OR-operator respectively:

// Return users with "John" as first_name and "Doe" as last_name.
var myQuery = new Kinvey.Query();
myQuery.on('first_name').equal('John');

var mySecondQuery = new Kinvey.Query();
mySecondQuery.on('last_name').equal('Doe');

// Join.
myQuery.and(mySecondQuery);

Counting

Use Kinvey.Collection.count to count the number of entities in a collection:

myCollection.count({
    success: function(i) {
        // i is the number of entities.
    },
    error: function(e) {
        // Failed to count the number of entities.
        // e holds information about the nature of the error.
    }
});

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 Kinvey.Aggregation class allows you to do this. Aggregations are executed in the context of a Kinvey.Collection, and always return an object in JSON format.

For example, let's group all users based on age:

// Create the aggregation.
var aggregation = new Kinvey.Aggregation();
aggregation.on('age');

// Apply the aggregation.
myCollection.aggregate(aggregation, {
    success: function(response) {
        // response looks like [{ age: 25, count: <n> }, { age: 50, count: <n> }, ...]
    },
    error: function(e) {
        // Failed to aggregate data.
        // e holds information about the nature of the error.
    }
});

Reduce Function

More complex usage of aggregations require you to use the following methods:

  • setInitial sets the initial value of the aggregation object.
  • setFinalize defines a function to run on the aggregation object after completion.
  • setReduce defines the function that reduces the objects iterated.

The exposed aggregation API is identical to MongoDB’s aggregation API, which Kinvey relies on behind the scenes. Therefore, for detailed information on the aggregation methods, you can consult the MongoDB Aggregation documentation.

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

The JavaScript library enables support for relational data by allowing to link entities to each other.

Continuing the calendar and events example, the code below demonstrates an event that has a set of Invitation objects. The invitation has a reference to its event, as well as to a user:

// Create the event.
var myBirthdayParty = new Kinvey.Entity({
    name:     'Birthday Party',
    date:     '2012-12-01 16:30:00',
    location: 'Mike’s House'
}, 'events');

// Create the invitee. The user *must* exist in the Kinvey backend.
var someUser = new Kinvey.User({
    name: 'Mark'
});

// Create an invitation for Mark.
var myInvitation = new Kinvey.Entity({
    status: 'open',
    event: myBirthdayParty,
    invitee: someUser
}, 'invitations');

// Display the invitations event name.
alert(myInvitation.get('event').get('name'));// Displays "Birthday Party".

Saving

Use Kinvey.Entity.save on the parent entity to save the entity, and all of its relations (to which the active user has write access):

myInvitation.save({
    success: function(myInvitation) {
        // myInvitation is the saved invitation.
        alert(myInvitation.get('event').get('name'));// Displays "Birthday Party".
    },
    error: function(e) {
        // Failed to save myInvitation.
        // e holds information about the nature of the error.
    }
});

Most circular references can be saved. This will however require one additional network request.

Fetching

It is possible to fetch an entity together with any related entities. This is similar to performing a JOIN in SQL. Joining is possible by specifying an array of field names on which to join. For example, to load an invitation with its event and invitee embedded, use the following:

myInvitation.load('my-invitation-id', {
    resolve: ['event', 'invitee'],
    success: function(myInvitation) {
        // myInvitation is the entity instance.
        alert(myInvitation.get('event').get('name'));// Displays "Birthday Party".
    },
    error: function(e) {
        // Failed to load myInvitation.
        // e holds information about the nature of the error.
    }
});

As you can see, it is possible to join on more than one field at the time. Also, there is support for resolving nested relations.

If you have extended Kinvey.Entity with your own classes, you probably want relations to be an instance of your own class. This is possible by mapping a field to a class:

// Define the Event class.
var Event = Kinvey.Entity.extend({ /* ... */ });

// Create an invitation, and set event and invitee properties to the correct class.
var myInvitation = new Kinvey.Entity({}, 'invitations', {
    map: {
        event: Event,
        invitee: Kinvey.User
    }
});
myInvitation.load('my-invitation-id', {
    resolve: ['event', 'invitee'],
    success: function(myInvitation) {
        // myInvitation is the entity instance.
        alert(myInvitation.get('event').get('name'));// Displays "Birthday Party".
    },
    error: function(e) {
        // Failed to load myInvitation.
        // e holds information about the nature of the error.
    }
});

If a field is not joined, but contains a relationship, its value will be an object containing the referenced entity collection and id.

Fetching by Query

The resolve option is also allowed when using Kinvey.Collection.fetch:

var myInvitations = new Kinvey.Collection('invitations');
myInvitations.fetch({
    resolve: ['event'],
    success: function(list) {
        // list is an array of invitations, with their event embedded.
    },
    error: function(e) {
        // Failed to fetch all invitations.
        // e holds information about the nature of the error.
    }
});

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.
  • When loading entities, if two objects share a reference to a common third entity, two separate objects will be created for that third entity. 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 (with multiple circular references) cannot be saved completely.

Related Samples

Get in touch