This is not the current version. View the latest documentation
Introduction
Realm JavaScript enables you to efficiently write your app’s model layer in a safe, persisted and fast way. It’s designed to work with React Native and Node.js.
Here’s an example using React Native:
// Define your models and their properties
class Car {}
Car.schema = {
name: 'Car',
properties: {
make: 'string',
model: 'string',
miles: 'int',
}
};
class Person {}
Person.schema = {
name: 'Person',
properties: {
name: {type: 'string'},
cars: {type: 'list', objectType: 'Car'},
picture: {type: 'data', optional: true}, // optional property
}
};
// Get the default Realm with support for our objects
let realm = new Realm({schema: [Car, Person]});
// Create Realm objects and write to local storage
realm.write(() => {
let myCar = realm.create('Car', {
make: 'Honda',
model: 'Civic',
miles: 1000,
});
myCar.miles += 20; // Update a property value
});
// Query Realm for all cars with a high mileage
let cars = realm.objects('Car').filtered('miles > 1000');
// Will return a Results object with our 1 car
cars.length // => 1
// Add another car
realm.write(() => {
let myCar = realm.create('Car', {
make: 'Ford',
model: 'Focus',
miles: 2000,
});
});
// Query results are updated in realtime
cars.length // => 2
Getting Started
Installation
Follow the installation instructions below to install Realm JavaScript via npm, or see the source on GitHub.
Prerequisites
- Make sure your environment is set up to run React Native applications. Follow the React Native instructions for getting started.
- Apps using Realm can target both iOS and Android.
- React Native 0.20.0 and later is supported.
-
Make sure the React Native Package Manager (
rnpm
) is globally installed and up-to-date:npm install -g rnpm
Installation
-
Create a new React Native project:
react-native init <project-name>
-
Change directories into the new project (
cd <project-name>
) and add therealm
dependency:npm install --save realm
-
Next, link your project to the
realm
native module.-
React Native >= 0.31.0
react-native link realm
-
React Native < 0.31.0
rnpm link realm
-
Warning for Android: Depending on the version, rnpm
may generate an invalid configuration, updating Gradle correctly (android/settings.gradle
and android/app/build.gradle
) but failing to add the Realm module. Confirm that rnpm link
has added the Realm module; if it has not, link manually to the library with the following steps:
-
Add the following lines to
android/settings.gradle
:include ':realm' project(':realm').projectDir = new File(rootProject.projectDir, '../node_modules/realm/android')
-
Add the compile line to the dependencies in
android/app/build.gradle
:dependencies { compile project(':realm') }
-
Add the import and link the package in
MainApplication.java
:import io.realm.react.RealmReactPackage; // add this import public class MainApplication extends Application implements ReactApplication { @Override protected List<ReactPackage> getPackages() { return Arrays.<ReactPackage>asList( new MainReactPackage(), new RealmReactPackage() // add this line ); } }
You’re now ready to go. To see Realm in action, add the following as the definition for your class <project-name>
in index.ios.js
or index.android.js
:
const Realm = require('realm');
class <project-name> extends Component {
render() {
let realm = new Realm({
schema: [{name: 'Dog', properties: {name: 'string'}}]
});
realm.write(() => {
realm.create('Dog', {name: 'Rex'});
});
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Count of Dogs in Realm: {realm.objects('Dog').length}
</Text>
</View>
);
}
}
You can then run your app on a device and in a simulator!
These instructions install the Developer Edition of the Realm Node.js SDK. If you have downloaded the Professional Edition or Enterprise Edition, follow the installation instructions you received in email.
To install Realm Node.js, simply use the Node Package Manager:
npm install --save realm
To use the SDK, require('realm')
in your application.
'use strict';
var Realm = require('realm');
var realm = new Realm({
schema: [{name: 'Dog', properties: {name: 'string'}}]
});
realm.write(() => {
realm.create('Dog', {name: 'Rex'});
});
Install examples
An optional repository of examples can be cloned from Github.
git clone https://github.com/realm/realm-js.git
Change to the cloned directory, and update the submodules:
cd realm-js
git submodule update --init --recursive
On Android, you need the NDK installed and must have set the ANDROID_NDK environment variable.
export ANDROID_NDK=/usr/local/Cellar/android-ndk/r10e
The React Native examples are in the examples
directory. You must run npm install
for each example.
Getting Help
- Need help with your code? Ask on StackOverflow. We actively monitor & answer questions on SO!
- Have a bug to report? Open an issue on our repo. If possible, include the version of Realm, a full log, the Realm file, and a project that shows the issue.
- Have a feature request? Open an issue on our repo. Tell us what the feature should do, and why you want the feature.
If you’re using a crash reporter (like Crashlytics or HockeyApp), make sure to enable log collection. Realm logs metadata information (but no user data) when throwing exceptions and in irrecoverable situations, and these messages can help debug when things go wrong.
Models
Realm data models are defined by the schema information passed into a Realm during initialization. The schema for an object consists of the object’s name
and a set of properties each of which has a name
and type
as well as the objectType
for object and list properties. You can also designate each property to be optional
or to have a default
value.
var Realm = require('realm');
const CarSchema = {
name: 'Car',
properties: {
make: 'string',
model: 'string',
miles: {type: 'int', default: 0},
}
};
const PersonSchema = {
name: 'Person',
properties: {
name: 'string',
birthday: 'date',
cars: {type: 'list', objectType: 'Car'},
picture: {type: 'data', optional: true}, // optional property
}
};
// Initialize a Realm with Car and Person models
let realm = new Realm({schema: [CarSchema, PersonSchema]});
If you’d prefer your objects inherit from an existing class, you just need to define the schema on the object constructor and pass in the constructor when creating a realm:
class Person {
get ageSeconds() {
return Math.floor((Date.now() - this.birthday.getTime()));
}
get age() {
return ageSeconds() / 31557600000;
}
}
Person.schema = PersonSchema;
// Note here we are passing in the `Person` constructor
let realm = new Realm({schema: [CarSchema, Person]});
Once you have defined your object models you can create and fetch objects from the realm:
realm.write(() => {
let car = realm.create('Car', {
make: 'Honda',
model: 'Civic',
miles: 750,
});
// you can access and set all properties defined in your model
console.log('Car type is ' + car.make + ' ' + car.model);
car.miles = 1500;
});
Basic Property Types
Realm supports the following basic types: bool
, int
, float
, double
, string
, data
, and date
.
bool
properties map to JavaScriptBoolean
objectsint
,float
, anddouble
properties map to JavaScriptNumber
objects. Internally ‘int’ and ‘double’ are stored as 64 bits whilefloat
is stored with 32 bits.string
properties map toString
data
properties map toArrayBuffer
date
properties map toDate
When specifying basic properties as a shorthand you may specify only the type rather than having to specify a dictionary with a single entry:
const CarSchema = {
name: 'Car',
properties: {
// The following property types are equivalent
make: {type: 'string'},
model: 'string',
}
}
Object Properties
For object types you specify the name
property of the object schema you are referencing:
const PersonSchema = {
name: 'Person',
properties: {
// All of the following property definitions are equivalent
car: {type: 'Car'},
van: 'Car',
}
};
When using object properties you need to make sure all referenced types are present in the schema used to open the Realm:
// CarSchema is needed since PersonSchema contains properties of type 'Car'
let realm = new Realm({schema: [CarSchema, PersonSchema]});
When accessing object properties, you can access nested properties using normal property syntax:
realm.write(() => {
var nameString = person.car.name;
person.car.miles = 1100;
// create a new Car by setting the property to valid JSON
person.van = {make: 'Ford', model: 'Transit'};
// set both properties to the same car instance
person.car = person.van;
});
List Properties
For list properties you must specify the property type as list
as well as the objectType
:
const PersonSchema = {
name: 'Person',
properties: {
cars: {type: 'list', objectType: 'Car'},
}
}
When accessing list properties a List
object is returned. List
has methods very similar to a regular JavaScript array. The big difference is that any changes made to a List
are automatically persisted to the underlying Realm. Additionally, List
s belong to the underlying object they were acquired from - you can only get List
instances by accessing a property from an owning object and they cannot be manually created.
let carList = person.cars;
// Add new cars to the list
realm.write(() => {
carList.push({make: 'Honda', model: 'Accord', miles: 100});
carList.push({make: 'Toyota', model: 'Prius', miles: 200});
});
let secondCar = carList[1].model; // access using an array index
Optional Properties
Properties can be declared as optional or non-optional by specifying the optional
designator in your property definition:
const PersonSchema = {
name: 'Person',
properties: {
name: {type: 'string'}, // required property
birthday: {type: 'date', optional: true}, // optional property
// object properties are always optional
car: {type: 'Car'},
}
};
let realm = new Realm({schema: [PersonSchema, CarSchema]});
realm.write(() => {
// optional properties can be set to null or undefined at creation
let charlie = realm.create('Person', {
name: 'Charlie',
birthday: new Date(1995, 11, 25),
car: null,
});
// optional properties can be set to `null`, `undefined`,
// or to a new non-null value
charlie.birthday = undefined;
charlie.car = {make: 'Honda', model: 'Accord', miles: 10000};
});
As seen above object properties are always optional and do not need an optional designation. List properties cannot be declared as optional or set to null. You can set or initialize a list with an empty array to clear it.
Default Property Values
Default property values can be specified by setting the default
designator in the property definition. To use a default value, leave the property unspecified during object creation.
const CarSchema = {
name: 'Car',
properties: {
make: {type: 'string'},
model: {type: 'string'},
drive: {type: 'string', default: 'fwd'},
miles: {type: 'int', default: 0}
}
};
realm.write(() => {
// Since `miles` is left out it defaults to `0`, and since
// `drive` is specified, it overrides the default value
realm.create('Car', {make: 'Honda', model: 'Accord', drive: 'awd'});
});
Indexed Properties
You can add an indexed
designator to a property definition to cause that property to be indexed. This is supported for int
, string
, and bool
property types:
var BookSchema = {
name: 'Book',
properties: {
name: { type: 'string', indexed: true },
price: 'float'
}
};
Indexing a property will greatly speed up queries where the property is compared for equality at the cost of slower insertions.
Primary Keys
You can specify the primaryKey
property in an object model for string
and int
properties. Declaring a primary key allows objects to be looked up and updated efficiently and enforces uniqueness for each value. Once an object with a primary key has been added to a Realm the primary key cannot be changed.
const BookSchema = {
name: 'Book',
primaryKey: 'id',
properties: {
id: 'int', // primary key
title: 'string',
price: 'float'
}
};
Primary key properties are automatically indexed.
Writes
All changes to an object (addition, modification and deletion) must be done within a write transaction.
Write transactions incur non-negligible overhead - you should architect your code to minimize the number of write transactions.
Creating Objects
As shown above, objects are created using the create
method:
let realm = new Realm({schema: [CarSchema]});
realm.write(() => {
realm.create('Car', {make: 'Honda', model: 'Accord', drive: 'awd'});
});
Nested Objects
If an object has object properties, values for those properties can be created recursively by specifying JSON values for each child property:
let realm = new Realm({schema: [PersonSchema, CarSchema]});
realm.write(() => {
realm.create('Person', {
name: 'Joe',
// nested objects are created recursively
car: {make: 'Honda', model: 'Accord', drive: 'awd'},
});
});
Updating Objects
Typed Updates
You can update any object by setting its properties within a write transaction.
realm.write(() => {
car.miles = 1100;
});
Creating and Updating Objects With Primary Keys
If your model class includes a primary key, you can have Realm intelligently update or add objects based off of their primary key values. This is done by passing true
as the third argument to the create
method:
realm.write(() => {
// Create a book object
realm.create('Book', {id: 1, title: 'Recipes', price: 35});
// Update book with new price keyed off the id
realm.create('Book', {id: 1, price: 55}, true);
});
In the example above, since an object already exists with the id
value of 1
and we have passed in true
for the third argument, the price property is updated rather than trying to create a new object. Since the title
property is omitted the object retains the original value for this property. Note that when creating or updating objects with primary key properties the primary key must be specified.
Deleting Objects
Objects can be deleted by calling the delete
method within a write transaction.
realm.write(() => {
// Create a book object
let book = realm.create('Book', {id: 1, title: 'Recipes', price: 35});
// Delete the book
realm.delete(book);
// Delete multiple books by passing in a `Results`, `List`,
// or JavaScript `Array`
let allBooks = realm.objects('Book');
realm.delete(allBooks); // Deletes all books
});
Queries
Queries allow you to get objects of a single type from a Realm, with the option of filtering and sorting those results. All queries (including queries and property access) are lazy in Realm. Data is only read when objects and properties are accessed. This allows you to represent large sets of data in a performant way.
When performing queries you are returned a Results
object. Results are simply a view of your data and are not mutable.
The most basic method for retrieving objects from a Realm is using the objects
method on a Realm
to get all objects of a given type:
let dogs = realm.objects('Dog'); // retrieves all Dogs from the Realm
Filtering
You can get a filtered Results
by calling the filtered
method with a query string.
For example, the following would change our earlier example to retrieve all dogs with the color tan and names beginning with ‘B’:
let dogs = realm.objects('Dog');
let tanDogs = dogs.filtered('color = "tan" AND name BEGINSWITH "B"');
At the moment only a subset of the NSPredicate syntax is supported in the query language. Basic comparison operators ==
, !=
, >
, >=
, <
, and <=
are supported for numeric properties. ==
, BEGINSWITH
, ENDSWITH
, and CONTAINS
are supported for string properties. String comparisons can be made case insensitive by appending [c]
to the operator: ==[c]
, BEGINSWITH[c]
etc. Filtering by properties on linked or child objects can by done by specifying a keypath in the query eg car.color == 'blue'
.
Sorting
Results
allows you to specify a sort criteria and order based on a single or multiple properties. For example, the following call sorts the returned cars from the example above numerically by miles:
let hondas = realm.objects('Car').filtered('make = "Honda"');
// Sort Hondas by mileage
let sortedHondas = hondas.sorted('miles');
Note that the order of Results
is only guaranteed to stay consistent when the query is sorted. For performance reasons, insertion order is not guaranteed to be preserved.
Auto-Updating Results
Results
instances are live, auto-updating views into the underlying data, which means results never have to be re-fetched. Modifying objects that affect the query will be reflected in the results immediately.
let hondas = realm.objects('Car').filtered('make = "Honda"');
// hondas.length == 0
realm.write(() => {
realm.create('Car', {make: 'Honda', model: 'RSX'});
});
// hondas.length == 1
This applies to all Results
instances, included those returned by the objects
, filtered
, and sorted
methods.
This property of Results
not only keeps Realm fast and efficient, it allows your code to be simpler and more reactive. For example, if your view relies on the results of a query, you can store the Results
in a property and access it without having to make sure to refresh its data prior to each access.
You can subscribe to notifications to know when Realm data is updated, indicating when your app’s UI should be refreshed for example, without having to re-fetch your Results
.
Limiting Results
Most other database technologies provide the ability to ‘paginate’ results from queries (such as the ‘LIMIT’ keyword in SQLite). This is often done out of necessity to avoid reading too much from disk, or pulling too many results into memory at once.
Since queries in Realm are lazy, performing this sort of paginating behavior isn’t necessary at all, as Realm will only load objects from the results of the query once they are explicitly accessed.
If for UI-related or other implementation reasons you require a specific subset of objects from a query, it’s as simple as taking the Results
object, and reading out only the objects you need.
let cars = realm.objects('Car');
// get first 5 Car objects
let firstCars = cars.slice(0, 5);
Realms
Multiple Realms
It’s sometimes useful to have multiple Realms persisted at different locations. For example, you may want to bundle some data with your application in a Realm file, in addition to your main Realm. You can do this by specifying the path
argument when initializing your realm. All paths are relative to the writable documents directory for your application:
// Open a realm at another path
let realmAtAnotherPath = new Realm({
path: 'anotherRealm.realm',
schema: [CarSchema]
});
Default Realm Path
You may have noticed in all previous examples that the path argument has been omitted. In this case the default Realm path is used. You can access and change the default Realm path using the Realm.defaultPath
global property.
Schema Version
The last option available when opening a Realm is the schemaVersion
property. When omitted, the schemaVersion
property defaults to 0
. You are required to specify the schemaVersion
when initializing an existing Realm with a schema that contains objects that differ from their previous specification. If the schema was updated and the schemaVersion
was not, an exception will be thrown.
const PersonSchema = {
name: 'Person',
properties: {
name: 'string'
}
};
// schemaVersion defaults to 0
let realm = new Realm({schema: [PersonSchema]});
const UpdatedPersonSchema = {
// The schema name is the same, so previous `Person` object
// in the Realm will be updated
name: 'Person',
properties: {
name: 'string',
dog: 'Dog' // new property
}
};
// this will throw because the schema has changed
// and `schemaVersion` is not specified
let realm = new Realm({schema: [UpdatedPersonSchema]});
// this will succeed and update the Realm to the new schema
let realm = new Realm({schema: [UpdatedPersonSchema], schemaVersion: 1});
If you wish to retrieve the current schema version of a Realm, you may do so with the Realm.schemaVersion
method.
let currentVersion = Realm.schemaVersion(Realm.defaultPath);
Migrations
When working with a database your data model will most likely change over time. For example, suppose we have the following Person
model:
var PersonSchema = {
name: 'Person',
properties: {
firstName: 'string',
lastName: 'string',
age: 'int'
}
}
We want to update the data model to require a name
property, rather than separate first and last names. To do this, we simply change the schema to the following:
var PersonSchema = {
name: 'Person',
properties: {
name: 'string',
age: 'int'
}
}
At this point if you had saved any data with the previous model version there will be a mismatch between the new code and the old data Realm has stored on disk. When this occurs, an exception will be thrown when you try to open the existing Realm with the new schema unless you run a migration.
Performing a Migration
You define a migration and the associated schema version by updating the schemaVersion and defining an optional migration
function. Your migration function provides any logic needed to convert data models from previous schemas to the new schema. When opening a Realm
the migration function will be applied to update the Realm
to the given schema version only if a migration is needed.
If no migration function is supplied then any new properties an automatically added and old properties are removed from the database when updating to the new schemaVersion
. If you need to update old or populate new properties when upgrading your version you can do this in the migration function. For example, suppose we want to migrate the Person
model declared earlier. You can populate the name
property of the new schema using the old firstName
and lastName
properties:
var realm = new Realm({
schema: [PersonSchema],
schemaVersion: 1,
migration: function(oldRealm, newRealm) {
// only apply this change if upgrading to schemaVersion 1
if (oldRealm.schemaVersion < 1) {
var oldObjects = oldRealm.objects('Person');
var newObjects = newRealm.objects('Person');
// loop through all objects and set the name property in the new schema
for (var i = 0; i < oldObjects.length; i++) {
newObjects[i].name = oldObjects[i].firstName + ' ' + oldObjects[i].lastName;
}
}
}
});
var fullName = realm.objects('Person')[0].name;
Once the migration is successfully completed the Realm and all of its objects can be accessed as usual by your app.
Linear Migrations
With the migration pattern described above you can potentially run into issues when migrating over multiple versions. This could happen if a user skips an app update and a property has been changed multiple times in the versions being skipped. In this case you may need to edit old migration code to correctly update data from old schema to the latest schema.
It’s possible to avoid this issue by running multiple migrations sequentially, making sure that the database is upgraded to each previous version and that the associated migration code is run. When following this pattern old migration code should never have to be modified, although you will need to keep all old schema and migration blocks for future use. An example of what this would look like:
var schemas = [
{ schema: schema1, schemaVersion: 1, migration: migrationFunction1 },
{ schema: schema2, schemaVersion: 2, migration: migrationFunction2 },
...
]
// the first schema to update to is the current schema version
// since the first schema in our array is at
var nextSchemaIndex = Realm.schemaVersion(Realm.defaultPath);
while (nextSchemaIndex < schemas.length) {
var migratedRealm = new Realm(schemas[nextSchemaIndex++]);
migratedRealm.close();
}
// open the Realm with the latest schema
var realm = new Realm(schemas[schemas.length-1]);
Notifications
The Realm
, Results
and List
objects provide addListener
methods to register notification callbacks. Whenever the object is updated, the change notification callback will be called.
There are two kinds of notifications, “Realm Notifications” (simple callbacks notified when write transactions are committed) and “Collection Notifications” (more sophisticated callbacks which receive change metadata on insertions, deletions and updates).
In addition, the Professional Edition and Enterprise Edition provide event handling notifications. Read “The Realm Mobile Platform” for more information.
Realm Notifications
Realm instances send out notifications to other instances every time a write transaction is committed. To register for notifications:
// Observe Realm Notifications
realm.addListener('change', () => {
// Update UI
...
});
// Unregister all listeners
realm.removeAllListeners();
Collection Notifications
Collection notifications contain information that describe what changes have occurred at a fine-grained level. This consists of the indices of objects that have been inserted, deleted, or modified since the last notification. Collection notifications are delivered asynchronously: first with the initial results, and then after any write transaction which modifies any of the objects in the collection, deletes objects from the collection, or adds new objects to the collection.
The notification callback function given to addListener
receives two parameters when these changes occur. The first one is the collection that changed, and the second one is a changes
object with information about the collection indices affected by deletions, insertions and modifications.
The former two, deletions and insertions, record the indices whenever objects start and stop being part of the collection. This takes into account when you add objects to the Realm or delete them from the Realm. For Results
this also applies when you filter for specific values and the object was changed so that it is now matching the query or not matching anymore. For collections based on List
, this applies when objects are added or removed from the relationship.
Your application is notified about modifications whenever a property of an object has changed, which was previously part of the collection and is still part of it. This happens as well when to-one and to-many relationships change, but doesn’t take changes on inverse relationships into account.
class Dog {}
Dog.schema = {
name: 'Dog',
properties: {
name: 'string',
age: 'int',
}
};
class Person {}
Person.schema = {
name: 'Person',
properties: {
name: {type: 'string'},
dogs: {type: 'list', objectType: 'Dog'},
}
};
Let’s assume you’re observing a list of dog owners as given by the model code above. You will be notified about modifications for a matched Person
object when:
- You modify the
Person
’sname
property. - You add or remove a
Dog
to thePerson
’sdogs
property. - You modify the
age
property of aDog
belonging to thatPerson
.
This makes it possible to discretely control the animations and visual updates made to the content inside your UI, instead of arbitrarily reloading everything each time a notification occurs.
// Observe Collection Notifications
realm.objects('Dog').filtered('age < 2').addListener((puppies, changes) => {
// Update UI in response to inserted objects
changes.insertions.forEach((index) => {
let insertedDog = puppies[index];
...
});
// Update UI in response to modified objects
changes.modifications.forEach((index) => {
let modifiedDog = puppies[index];
...
});
// Update UI in response to deleted objects
changes.deletions.forEach((index) => {
// Deleted objects cannot be accessed directly
// Support for accessing deleted objects coming soon...
...
});
});
// Unregister all listeners
realm.removeAllListeners();
Sync
The Realm Mobile Platform (RMP) extends the Realm Mobile Database across the network, enabling automatic synchronization of data across devices. In order to do this a new set of types and classes are provided that support these synchronized Realms; these new classes are additive to the existing Realm Mobile Database.
Users
The central object in the Realm Object Server is the Realm User (Realm.Sync.User
) associated with a synchronized Realm. A User
can be authenticated to a shared Realm via a username/password scheme, or through a number of third-party authentication methods.
Creating and logging in a user requires two things:
- A URL of a Realm Object Server to connect to.
- Credentials for an authentication mechanism that describes the user as appropriate for that mechanism (i.e., username/password, access key, etc).
Authentication
Authentication is used to establish the identity of users and log them in. Refer to our authentication documentation for a list of authentication providers supported by the Realm Mobile Platform.
The credential information for a given user can be created in one of several ways:
- Providing a valid username/password combination
- Providing a token obtained from a supported third-party authentication service
- Providing a token and a custom authentication provider (see Custom Authentication)
The username and password authentication is entirely managed by the Realm Object Server, giving you full control over your application’s user management. For other authentication methods, your application is responsible for logging into the external service and obtaining the authentication token.
Here are some examples of setting credentials with various providers.
Username/Password
Realm.Sync.User.login('http://my.realm-auth-server.com:9080', 'username', 'p@s$w0rd', (error, user) => { /* ... */ });
Before a user can log in, the account must be created. You can either do that in advance on the server using the admin Dashboard, or by calling register
:
Realm.Sync.User.register('http://my.realm-auth-server.com:9080', 'username', 'p@s$w0rd', (error, user) => { /* ... */ });
const googleAccessToken = 'acc3ssT0ken...';
Realm.Sync.User.registerWithProvider('http://my.realm-auth-server.com:9080', 'google', googleAccessToken, (error, user) => { /* ... */ });
const fbAccessToken = 'acc3ssT0ken...';
Realm.Sync.User.registerWithProvider('http://my.realm-auth-server.com:9080', 'facebook', fbAccessToken, (error, user) => { /* ... */ });
Custom Auth
// The user token provided by your authentication server
const accessToken = 'acc3ssT0ken...';
const user = Realm.Sync.User.registerWithProvider(
'http://my.realm-auth-server.com:9080',
'fooauth',
accessToken,
(error, user) => { /* ... */ }
);
Note: the JavaScript SDK does not currently allow you to send additional data. If you need to send more than a single token, please encode the additional data as JSON and pass it through the accessToken parameter, and decode this string on the server side.
Logging Out
Logging out of a synced Realm is simple:
user.logout();
When a user is logged out, the synchronization will stop. A logged out user can no longer open a synced Realm.
Working with Users
The sync server URL may contain the tilde character (“~”) which will be transparently expanded to represent the user’s unique identifier. This scheme easily allows you to write your app to cater to its individual users. The location on disk for shared Realms is managed by the framework, but can be overridden if desired.
Realm.Sync.User.login(/* ... */, (error, user) => {
if (!error) {
var realm = new Realm({
sync: {
user: user,
url: 'realm://object-server-url:9080/~/my-realm',
},
schema: [/* ... */]
});
realm.write(() => {
/* ... */
})
}
})
Realm.Sync.User.current
can be used to obtain the currently logged in user. If no users have logged in or all have logged out, it will return undefined
. If there are more than one logged in users, an error will be thrown.
let user = Realm.Sync.User.current;
If there are likely to be multiple users logged in, you can get a collection of them by calling Realm.Sync.User.all
. This will be empty if no users have logged in.
let users = Realm.Sync.User.all;
for(const key in users) {
const user = users[key];
// do something with the user.
})
Using Synced Realms
Once you have opened a Realm using a URL to a Realm Object Server and a User
object, you can interact with it as you would any other Realm in JavaScript.
realm.write(() => {
realm.create('MyObject', jsonData);
});
var objects = realm.object('MyObject');
Sync Sessions
A synced Realm’s connection to the Realm Object Server is represented by a Session
object. Session objects can be retrieved by calling realm.syncSession
.
The state of the underlying session can be retrieved using the state
property. This can be used to check whether the session is active, not connected to the server, or in an error state.
Access Control
The Realm Mobile Platform provides flexible access control mechanisms to restrict which users are allowed to sync against which Realm files. This can be used, for example, to create collaborative apps where multiple users write to the same Realm. It can also be used to share data in a publisher/subscriber scenario where a single writing user shares data with many users with read permissions.
There are three permissions that control the access level of a given Realm for a User:
mayRead
indicates that the user is allowed to read from the Realm.mayWrite
indicates that the user is allowed to write to the Realm.mayManage
indicates that the user is allowed to change the permissions for the Realm.
Unless permissions are explicitly modified, only the owner (creator) of a Realm can access it. The only exception is admin users: They are always granted all permissions to all Realms on the server.
Please refer to the general Realm Object Server documentation on Access Control to learn more about the concept.
Management Realm
All access level management operations are performed by writing to the Management Realm. The Management Realm is just like a regular synchronized Realm
, but that the Realm Object Server is specifically designed to react to by default. Permission modifying objects can be added to the Realm to apply those changes to the access control settings of a Realm file.
To obtain the Management Realm for a given User, call its user.openManagementRealm()
method:
const managementRealm = user.openManagementRealm();
Modifying Permissions
Modifying the access control settings for a Realm file is done by adding permission object instances to the management Realm. Currently, there are two workflows supported:
PermissionChange
The PermissionChange
object allows you to directly control the access control settings for a Realm. This is useful when you already know the Ids of the users you want to grant permissions to, or want to grant permissions to everyone.
managementRealm.write(() => {
managementRealm.create('PermissionChange', {
id: generateUniqueId(), // implement something that creates a unique id.
createdAt: new Date(),
updatedAt: new Date(),
userId: '...',
realmUrl: '...',
});
});
To apply the permission changes for all Realms managed by the User, specify a realmURL
value of *
.
To apply the permission changes for all Users authorized with the Object Server, specify a userId
value of *
.
If you don’t supply values for mayRead
, mayWrite
, or mayManage
, or you supply null
, the read
, write
, or manage
permissions will be left unchanged. This can be useful, for example, if you want to grant users read
permissions, but don’t want to take away write
permissions from people who already have it:
managementRealm.write(() => {
for(const userId in users) {
managementRealm.create('PermissionChange', {
id: generateUniqueId(),
createdAt: new Date(),
updatedAt: new Date(),
userId: userId,
realmUrl: realmUrl,
mayRead: true
});
});
});
This way, all users in the list will be granted read
permissions, but those who had write
or manage
access will not lose it.
PermissionOffer/PermissionResponse
The PermissionOffer
and PermissionOfferResponse
pair of classes allows you to design sharing scenarios where an inviter generates a token, which can then be consumed by one or more users:
managementRealm.write(() => {
let expirationDate = new Date();
expirationDate.setDate(expirationDate.getDate() + 7); // Expires in a week.
managementRealm.create('PermissionOffer', {
id: generateUniqueId(),
createdAt: new Date(),
updatedAt: new Date(),
userId: userId,
realmUrl: realmUrl,
mayRead: true,
mayWrite: true,
mayManage: false,
expiresAt: expirationDate
});
});
/* Wait for the offer to be processed */
var token = permissionOffer.token;
/* Send token to the other user */
Similarly to PermissionChange
, there are arguments controlling the read
, write
, and manage
access for the Realm at the supplied realmUrl
. One additional argument you can provide is expiresAt
which controls when the token will no longer be consumable. If you don’t pass a value or pass null
, the offer will never expire. Note that users who have consumed the offer will not lose access after it expires.
Once another user has obtained the token
, they can consume it:
const managementRealm = anotherUser.openManagementRealm();
let offerResponse;
managementRealm.write(() =>
{
offerResponse = managementRealm.create('PermissionOfferResponse', {
id: generateUniqueId(),
createdAt: new Date(),
token: token
});
});
/* Wait for the offer to be processed */
const realmUrl = offerResponse.realmUrl;
// Now we can open the shared realm:
var realm = new Realm({
sync: {
user: anotherUser,
url: realmUrl,
},
schema: [/* ... */]
});
Note that permissions granted by consuming a PermissionOffer
are additive, i.e. if a user already has write
access and accepts a PermissionOffer
granting read
permissions, they will not lose their write
access.
PermissionOffer
s can be revoked by deleting them from the Management Realm or setting expiresAt
to a date in the past. This will prevent new users from accepting the offer, but will not revoke any permissions of users who had consumed it prior to that.
Once the Object Server has processed the operations encoded in the permission object, it will set that object’s statusCode
and statusMessage
properties.
Event Handling
translation missing: en.documentation.realm-object-server.event-handling.alert
An exclusive feature of the Enterprise and Professional Editions of Realm Object Server is event handling capabilities. This functionality is provided in the server-side Node.js SDK via a global event listener API which hooks into the Realm Object Server, allowing you to observe changes across Realms. This could mean listening to every Realm for changes, or Realms that match a specific pattern. For example, if your app architecture separated user settings data into a Realm unique for each user where the virtual path was /~/settings
, then a listener could be setup to react to changes to any user’s settings
Realm.
Whenever a change is synchronized to the server, it triggers a notification which allows you to run custom server-side logic in response to the change. The notification will inform you about the virtual path of the updated Realm and provide the Realm object and fine-grained information on which objects changed. The change set provides the object indexes broken down by class name for any inserted, deleted, or modified object in the last synchronized transaction.
Creating an Event Handler
To use Realm Event Handling, you’ll need to create a small Node.js application.
Create a directory to place the server files, then create a file named package.json
. This JSON file is used by Node.js and npm, its package manager, to describe an application and specify external dependencies.
You can create this file interactively by using npm init
. You can also fill in a simple skeleton file yourself using your text editor:
{
"name": "MyApp",
"version": "0.0.1",
"main": "index.js",
"author": "Your Name",
"description": "My Cool Realm App",
"dependencies": {
"realm": "file:realm-1.0.0-professional.tgz"
}
}
We specify the downloaded archive of the Realm Mobile Platform as a file:
dependency; that archive should be in the same directory as the package.json
file. (Make sure the filename specified in package.json
is the filename that’s actually on disk!)
If you have other dependencies for your application, specify them in the dependencies
section of this file.
After the package.json
file is configured properly, type:
npm install
to download, unpack and configure all the modules and their dependencies.
Your event handler will need to access the Object Server with administrative privileges, so you’ll need to get the Object Server’s admin token. Under Linux, view the token with:
cat /etc/realm/admin_token.base64
On macOS, the token is stored in the realm-object-server
folder, inside the Realm Mobile Platform folder. Navigate to that folder and view the token:
cd path-to/realm-mobile-platform
cat realm-object-server/admin_token.base64
A sample index.js
file might look something like this. This example listens for changes to a user-specific private Realm at the virtual path /~/private
. It will look for updated Coupon
objects in these Realms and verify their code, if it wasn’t verified yet and write the result of the verification into the isValid
property of the Coupon
object.
var Realm = require('realm');
// Insert the Realm admin token here
// Linux: cat /etc/realm/admin_token.base64
// macOS: cat realm-object-server/admin_token.base64
var ADMIN_TOKEN = 'ADMIN_TOKEN';
// the URL to the Realm Object Server
var SERVER_URL = 'realm://127.0.0.1:9080';
// The regular expression you provide restricts the observed Realm files to only the subset you
// are actually interested in. This is done in a separate step to avoid the cost
// of computing the fine-grained change set if it's not necessary.
var NOTIFIER_PATH = '/^\/([0-9a-f]+)\/private$/';
// The handleChange callback is called for every observed Realm file whenever it
// has changes. It is called with a change event which contains the path, the Realm,
// a version of the Realm from before the change, and indexes indication all objects
// which were added, deleted, or modified in this change
function handleChange(changeEvent) {
// Extract the user ID from the virtual path, assuming that we're using
// a filter which only subscribes us to updates of user-scoped Realms.
var matches = changeEvent.path.match(/^\/([0-9a-f]+)\/private$/);
var userId = matches[1];
var realm = changeEvent.realm;
var coupons = realm.objects('Coupon');
var couponIndexes = changeEvent.changes.Coupon.insertions;
for (var couponIndex in couponIndexes) {
var coupon = coupons[couponIndex];
if (coupon.isValid !== undefined) {
var isValid = verifyCouponForUser(coupon, userId);
// Attention: Writes here will trigger a subsequent notification.
// Take care that this doesn't cause infinite changes!
realm.write(function() {
coupon.isValid = isValid;
});
}
}
}
// create the admin user
var adminUser = Realm.Sync.User.adminUser(ADMIN_TOKEN);
// register the event handler callback
Realm.Sync.addListener(SERVER_URL, adminUser, NOTIFIER_PATH, 'change', handleChange);
console.log('Listening for Realm changes');
Integrating With Another Service
For a complete example of integrating the Event Handling framework with another service (in this case, IBM Watson’s Bluemix), read the tutorial for the Scanner App.
Data Connector
translation missing: en.documentation.realm-object-server.ee.alert
The Enterprise Edition of the Realm Object Server offers a Node.js-based adapter API that allows you to access all low-level Object Server operations and data. This can be used to let a synced Realm interact with an existing legacy database such as PostgreSQL: the Realm will also be kept in sync with the external database in real time. Client applications can use the Realm Mobile Database API and get the benefits of working with real-time, native objects.
The Adapter API is set up in a very similar fashion to the Event Handler API described above. Create a small Node.js application by creating a directory to place the server files, then create a package.json
for npm dependencies or use npm init
to create it interactively. You’ll need to specify the Realm Mobile Platform EE Edition as a file:
dependency, with the archive in the same directory as the package.json
file. (Make sure the filename specified in package.json
is the filename that’s actually on disk!)
{
"name": "MyApp",
"version": "0.0.1",
"main": "index.js",
"author": "Your Name",
"description": "My Cool Realm App",
"dependencies": {
"realm": "file:realm-1.0.0-enterprise.tgz"
}
}
After your other dependencies are specified, use npm install
to download, unpack and configure the modules.
As with the event handler API, you’ll need to access the Object Server with administrative privileges, and will need to get the Object Server’s admin token. Under Linux, view the token with:
cat /etc/realm/admin_token.base64
On macOS, the token is stored in the realm-object-server
folder, inside the Realm Mobile Platform folder. Navigate to that folder and view the token:
cd path-to/realm-mobile-platform
cat realm-object-server/admin_token.base64
To use the Adapter API, the Node.js application you’re creating will act as a translator, receiving instructions from the Object Server and calling your external database’s API to read and write to it. A sample application might look like this:
var Realm = require('realm');
var adapterConfig = {
// Insert the Realm admin token here
// Linux: cat /etc/realm/admin_token.base64
// macOS: cat realm-object-server/admin_token.base64
admin_token: 'ADMIN_TOKEN',
// the URL to the Realm Object Server
server_url: 'realms://127.0.0.1:9080',
// local path for the Adapter API file
local_path: './adapter',
// regular expression to limit which Realms will be observed
realm_path_regex: '/^\/([0-9a-f]+)\/private$/'
};
class CustomAdapter {
constructor(config) {
this.adapter = new Realm.Sync.Adapter(
config.local_path,
config.server_url,
Realm.Sync.User.adminUser(config.admin_token),
config.realm_path_regex,
// This callback is called any time a new transaction is available for
// processing for the given path. The argument is the path to the Realm
// for which changes are available. This will be called for all Realms
// which match realm_path_regex.
(realm_path) => {
var current_instructions = this.adapter.current(realm_path);
while (current_instructions) {
// if defined, process the current array of instructions
this.process_instructions(current_instructions);
// call advance to progress to the next transaction
this.adapter.advance(realm_path);
current_instructions = this.adapter.current(realm_path);
}
}
)
}
// This method is passed the list of instructions returned from
// Adapter.current(path)
process_instructions(instructions) {
instructions.forEach((instruction) => {
// perform an operation for each type of instruction
switch (instruction.type) {
case 'INSERT':
insert_object(instruction.object_type, instruction.identity, instruction.values);
break;
case 'DELETE':
delete_object(instruction.object_type, instruction.identity);
break;
// ... add handlers for all other relevant instruction types
default:
break;
}
})
}
}
Each instruction object returned by Adapter.current
has a type
property which is one of the following strings. Two or more other properties containing data for the instruction processing will also be set.
INSERT
: insert a new objectobject_type
: type of the object being insertedidentity
: primary key value or row index for the objectvalues
: map of property names and property values for the object to insert
SET
: set property values for an existing objectobject_type
: type of the objectidentity
: primary key value or row index for the objectvalues
: map of property names and property values to update for the object
DELETE
: delete an exising objectobject_type
: type of the objectidentity
: primary key value or row index for the object
CLEAR
: delete all objects of a given typeobject_type
: type of the object
LIST_SET
: set the object at a given list index to an objectobject_type
: type of the objectidentity
: primary key for the objectproperty
: property name for the list property to mutatelist_index
: list index to setobject_identity
: primary key or row number of the object being set
LIST_INSERT
: insert an object in the list at the given indexobject_type
: type of the objectidentity
: primary key for the objectproperty
: property name for the list property to mutatelist_index
: list index at which to insertobject_identity
: primary key or row number of the object to insert
LIST_ERASE
: erase an object in the list at the given index: this removes the object- from the list but the object will still exist in the Realm
object_type
: type of the objectidentity
: primary key for the objectproperty
: property name for the list property to mutatelist_index
: list index which should be erased
LIST_CLEAR
: clear a list removing all objects: objects are not deleted from the Realmobject_type
: type of the objectidentity
: primary key for the objectproperty
: property name for the list property to clear
ADD_TYPE
: add a new typeobject_type
: name of the typeprimary_key
: name of primary key property for this typeproperties
: Property map as described in Realm.ObjectSchema
ADD_PROPERTIES
: add properties to an existing typeobject_type
: name of the typeproperties
: Property map as described in Realm.ObjectSchema
CHANGE_IDENTITY
: change the row index for an existing object: not called for objects with primary keysobject_type
: type fo the objectidentity
: old row value for the objectnew_identity
: new row value for the object
For full details, including all the instruction types and the data passed to them, consult the API reference for the Realm.Sync.Adapter
class.
A PostgreSQL data connector is already implemented, and more are on the way, including MongoDB and Microsoft SQL Server. Any data connector can be customized to your application’s specific needs. If you’re a Realm Enterprise customer, contact your representative for more information.
Data Access
translation missing: en.documentation.realm-object-server.event-handling.alert
The Professional and Enterprise Editions of the Realm Object Server allow you to access and change any shared Realm server-side using the administration token generated when you start the server. This allows access to all data in that Realm.
To retrieve the admin token on Linux:
cat /etc/realm/admin_token.base64
On macOS the token is stored in the realm-object-server
folder within the zip:
cat realm-object-server/admin_token.base64
This can be used to synchronously construct a Realm.Sync.User
object which can be passed into the Realm
constructor to open a connection to any Realm on the server side.
// Open a Realm using the admin user
var adminToken = '3x4mpl3T0k3n…';
var adminUser = Realm.Sync.User.adminUser(adminToken);
var realm = new Realm({
sync: {
user: admin_user,
url: 'realm://object-server-url:9080/my-realm',
},
schema: [{...}
}]
});
Note that when used with an admin user, the Realm URL does not include the ~
character that resolves into the user ID for non-admin authentication; the admin user does not have a user ID. For non-admin authentication, read the Users section above.
React Native ListView
If you’d like to use List
or Results
instances as data for a ListView
, it is highly recommended that you use the ListView
and ListView.DataSource
provided by the realm/react-native
module:
import { ListView } from 'realm/react-native';
The API is exactly the same as React.ListView
, so you can refer to the ListView documentation for usage information.
Encryption
Please take note of the Export Compliance section of our LICENSE, as it places restrictions against the usage of Realm if you are located in countries with an export restriction or embargo from the United States.
Realm supports encrypting the database file on disk with AES-256+SHA2 by supplying a 64-byte encryption key when creating a Realm.
var key = new Int8Array(64); // pupulate with a secure key
var realm = new Realm({schema: [CarObject], encryptionKey: key});
// Use the Realm as normal
var dogs = realm.objects('Car');
This makes it so that all of the data stored on disk is transparently encrypted and decrypted with AES-256 as needed, and verified with a SHA-2 HMAC. The same encryption key must be supplied every time you obtain a Realm instance.
There is a small performance hit (typically less than 10% slower) when using encrypted Realms.
Troubleshooting
Missing Realm Constructor
If your app crashes, telling you that the Realm constructor was not found, there are a few things you can try:
First of all, run react-native link realm
If that doesn’t help, and your problem is on Android, try:
Add the following in your MainApplication.java
file:
import io.realm.react.RealmReactPackage;
And add the RealmReactPackage
to the list of packages:
protected List getPackages() {
return Arrays.asList(
new MainReactPackage(),
new RealmReactPackage() // add this line
);
}
add following two lines in settings.gradle
:
include ':realm'
project(':realm').projectDir = new File(settingsDir, '../node_modules/realm/android')
If your problem is on iOS, try:
- Close all simulators/device builds
- Stop the package manager running in terminal (or better yet, just restart terminal)
- Open the ios folder in your app root in finder
- Go into the build folder (note: you won’t see this build folder in atom, so just right click ios and click open in finder)
- Delete everything inside of the build folder (just move to trash and keep trash around in case you’re worried)
- Run
react-native run-ios
to rebuild the whole thing
Crash Reporting
We encourage you to use a crash reporter in your application. Many Realm operations could potentially fail at runtime (like any other disk IO), so collecting crash reports from your application will help identify areas where either you (or us) can improve error handling and fix crashing bugs.
Most commercial crash reporters have the option of collecting logs. We strongly encourage you to enable this feature. Realm logs metadata information (but no user data) when throwing exceptions and in irrecoverable situations, and these messages can help debug when things go wrong.
Reporting Realm Issues
If you’ve found an issue with Realm, please either file an issue on GitHub or email us at help@realm.io with as much information as possible for us to understand and reproduce your issue.
The following information is very useful to us:
- Goals.
- Expected results.
- Actual results.
- Steps to reproduce.
- Code sample that highlights the issue (full working projects that we can compile ourselves are ideal).
- Version of Realm.
- Crash logs & stack traces. See Crash Reporting above for details.