These days, your app needs to store and sync more than just files. With the Datastore API, structured data like contacts, to-do items, and game state can be synced effortlessly. Datastores support multiple platforms, offline access, and automatic conflict resolution.
Here are the basic concepts that underlie the Datastore API:
Datastores are containers for your app's data. Each datastore contains a set of tables, and each table is a collection of records. As you'd expect, the table allows you to query existing records or insert new ones.
A datastore is cached locally once it's opened, allowing for fast access and offline operation. Datastores are also the unit of transactions; changes to one datastore are committed independently from another datastore. When data in a datastore is modified, it will automatically sync those changes back to Dropbox and also apply changes that may have been made by other devices.
The unit of sharing is a single datastore, and one or more datastores may be shared between accounts. Any datastore with a shareable ID can be shared by assigning roles to principals, creating an access control list. Any Dropbox account with the correct permissions will then be able to open the shared datastore by ID.
Records are how your app stores data. Each record consists of a set of fields, each with a name and a value. Values can be simple objects, like strings, integers, and booleans, or they can be lists of simple objects. A record has an ID and can have any number of fields.
Unlike in SQL, tables in datastores don't have a schema, so each record can have an arbitrary set of fields. While there's no requirement to have the same fields, it makes sense for all the records in a table to have roughly the same fields so you can query over them.
Now that you're familiar with the basics, read on to learn how to get the Datastore API running in your app. If you want to see a demo of a datastores app, check out the example Lists app.
If you want to code along with this guide, start by visiting the SDKs page for instructions on setting up your project.
To start using the Datastore API, you'll need to create a Client
object. This object lets you link to a Dropbox user's account, which is the first step to working with data on their behalf.
You'll want to create the client object as soon as your app has loaded. Depending on your JavaScript framework, this can be done in a variety of different ways. For example, in jQuery, you would use the $()
function.
Be sure to replace APP_KEY with the real value for your app.
var client = new Dropbox.Client({key: APP_KEY}); // Try to finish OAuth authorization. client.authenticate({interactive: false}, function (error) { if (error) { alert('Authentication error: ' + error); } }); if (client.isAuthenticated()) { // Client is authenticated. Display UI. }
The next step is to have the user start the linking flow. You can start the linking flow in response to a user action asking them to link to Dropbox. For this example, add a button to your app and then add the following code to its click handler.
client.authenticate();
The linking process will redirect the user to the Dropbox website and ask them to grant your app permission to access their Dropbox. When the user approves (or denies), they will be automatically directed back to the same page, and the initDropbox()
will automatically complete the authentication with the client.authenticate({interactive: false})
call. The JavaScript SDK will automatically store the auth token in localStorage so you don't have to manage it.
You now have all the pieces you need to link to a user's account. Run the app and press your Link to Dropbox button. Your app should proceed through the authorization flow and return to your app.
With a Client
in hand, the next step is to open the default datastore. Each app has its own default datastore per user.
var datastoreManager = client.getDatastoreManager(); datastoreManager.openDefaultDatastore(function (error, datastore) { if (error) { alert('Error opening default datastore: ' + error); } // Now you have a datastore. The next few examples can be included here. });
In order to store records in a datastore, you'll need to put them in a table. Let's define a table named "tasks":
var taskTable = datastore.getTable('tasks');
In the future, you might choose to add more tables to store related sets of things such as a "settings" table for the app or a "people" table to keep track of people assigned to each task. For now, this app is really simple so you only need one table to hold all your tasks.
You've got a datastore manager, a datastore for your app, and a table for all the tasks you're about to make. Let's start storing some data.
A record is a set of name and value pairs called fields, similar in concept to an object (though a record cannot be nested within a record the way an object can have sub-objects). Records in the same table can have different combinations of fields; there's no schema on the table which contains them. In fact, the record is created by first creating an object.
var firstTask = taskTable.insert({ taskname: 'Buy milk', completed: false, created: new Date() });
Unlike the iOS and Android datastore APIs, JavaScript will sync your changes at the next opportunity after they're made; you don't need to call a sync method explicitly. This implicit sync fits naturally with the event loop model of JavaScript.
Once syncing completes, visit the datastore browser and you should see your newly created task.
Accessing data from a record is straightforward:
var taskname = firstTask.get('taskname');
Editing tasks is just as easy. This is how you can mark the first result as completed:
firstTask.set('completed', true);
Finally, if you want to remove the record completely, just call deleteRecord()
.
firstTask.deleteRecord();
You can query the records in a table to get a subset of records that match a set of field names and values you specify. The query
method takes a set of conditions that the fields of a record must match to be returned in the result set. For each included condition, all records must have a field with that name and that field's value must be exactly equal to the specified value. For strings, this is a case-sensitive comparison (e.g. "abc" won't match "ABC").
var results = taskTable.query({completed: false}); var firstResult = results[0];
results
is a list of Record
objects.
The records that meet the specified query are not returned in any guaranteed order. The entire result set is returned so you may apply sort in memory after the request completes.
If no condition set is provided, the query will return every record in the table.
var results = taskTable.query();
A datastore will automatically receive changes from other instances of your app as they become available. For some apps, the frequency of updates will be low; others may be rapid-fire. In either case, your app should respond as soon as those changes happen by updating the state of your app. You can do this by registering record change listeners.
datastore.recordsChanged.addListener(function (event) { console.log('records changed:', event.affectedRecordsForTable('tasks')); });
The records-changed
event fires whenever the data in the Datastore has changed including when the changes were made locally. This is an important point to highlight. As a result, your app can and should use update events to update the state of your app consistently for changes made locally as well as changes made on other devices.
The event object gives you the ability to check which records were updated by table:
var records = event.affectedRecordsForTable('table-name');
Because this event triggers whenever any table in the datastore is changed, this helps you narrow down what has changed. The affectedRecordsForTable()
method will return a set of records that have been added, updated, or deleted. You can then use the records to update the state of your app in response. If no records were affected in the specified table, the returned set will be empty. Note that deleted records are returned with the ID but no other fields. You can use a record's isDeleted()
method to identify if that record was deleted.
The record is the smallest grouping of data in a datastore. It combines a set of fields to make a useful set of information within a table.
Each record has a string ID. An ID can be provided when a record is created, or one will be automatically generated and assigned if none is provided. Once a record is created, the ID cannot be changed.
Other records can refer to a given record by storing its ID. This is similar to the concept of a foreign key in SQL databases.
Records can contain a variety of field types. Earlier in this tutorial, you saw strings and booleans, but you can also specify a number of other types. Here is a complete list of all supported types:
String
)Boolean
)Dropbox.Datastore.int64
)Number
) – IEEE double. All native JavaScript numbers will be interpreted as floating-point numbers. To store integers, use the Dropbox.Datastore.int64
type.Date
) – POSIX-like timestamp stored with millisecond precision.Uint8Array
) – Arbitrary data, which is treated as binary, such as thumbnail images or compressed data. Individual records can be up to 100KB, which limits the size of the data.Dropbox.Datastore.List
) – A special value that can contain other values, though not other lists.Datastores automatically merge changes on a per-field basis. If, for example, a user were to edit the taskname
of a task on one device and the completed
status of that same task on another device, the Datastore API would merge these changes without a conflict.
Sometimes, however, there will be simultaneous changes to the same field of the same record, and this requires conflict resolution. For example, if a user were to edit the completed
status of a task on two different devices while offline, it's unclear how those changes should be merged when the devices come back online. Because of this ambiguity, app developers can choose what conflict resolution rule they want to follow.
To set the conflict resolution rule, call the setResolutionRule()
method on a table, and pass in the name of a field and the resolution rule you want to apply to that field.
taskTable.setResolutionRule('completed', 'local');
There are five available resolution rules that affect what happens when a remote change conflicts with a local change:
remote
– The remote value will be chosen. This is the default behavior for all fields.local
– The local value of the field will be chosen.max
– The greater of the two changes will be chosen.min
– The lesser of the two changes will be chosen.sum
– Additions and subtractions to the value will be preserved and combined.Note that resolution rules don't persist, so you should set any custom resolution rules immediately after opening a datastore.
For some applications you may want to share data between users. The Datastore API allows you to share a datastore across multiple Dropbox accounts.
To share a datastore, you'll first need to update its permissions by assigning a role to a group of users (called a principal).
// Shareable datastore datastoreManager.createDatastore(function (error, datastore) { datastore.setRole(Dropbox.Datastore.PUBLIC, Dropbox.Datastore.EDITOR); });
There are two available principals to whom you may apply a role:
Dropbox.Datastore.PUBLIC
– The role will apply to all Dropbox users.Dropbox.Datastore.TEAM
– The role will apply to everyone on the user's team (only applicable for Dropbox for Business accounts).There are four available roles:
Dropbox.Datastore.NONE
– The principal has no access to this datastore.Dropbox.Datastore.VIEWER
– The principal is able to view this datastore.Dropbox.Datastore.EDITOR
– The principal is able to edit this datastore.Dropbox.Datastore.OWNER
– The principal is the owner of this datastore. This role cannot be assigned directly. The user who created a datastore is always that datastore's owner.After assigning a role to a principal, you'll want to share the datastore ID with other users. A common way to share the datastore ID is to send a URL containing the datastore ID via email, text message, or some other mechanism within your app.
Any user who has the datastore ID and the appropriate permissions may then open the datastore:
datastoreManager.openDatastore(datastoreId, function (error, datastore) { // The datastore is now shared with this user. });
At any time you may view the access control list for a datastore as a mapping of roles applied to principals using the listRoles()
method. You can also find out the current user's role with the getEffectiveRole()
method.
See our example apps for more help getting started with the Datastore API.