The LeanStorage JavaScript SDK can be used to persist and query data in LeanCloud. The code below shows how you can create an object and store it into the cloud:
// Declare a class
const Todo = AV.Object.extend('Todo');
// Create an object
const todo = new Todo();
// Set values of fields
todo.set('title', 'R&D Weekly Meeting');
todo.set('content', 'All team members, Tue 2pm');
// Save the object to the cloud
todo.save().then((todo) => {
// Execute any logic that should take place after the object is saved
console.log(`Object saved. objectId: ${todo.id}`);
}, (error) => {
// Execute any logic that should take place if the save fails
});
The SDK designed for each language interacts with the same REST API via HTTPS, offering fully functional interfaces for you to manipulate the data in the cloud.
If you are using our JavaScript SDK in your browser-based application, you must configure whitelist domains in your app's Dashboard > Settings > Security before you deploy it to a production environment. Once set up, our backend will only accept requests sent from domains in the whitelist, which prevents unauthorized usage or access to your cloud data on a certain level.
However, since your app is exposed to the Internet, merely setting up whitelist domains can't guarantee the security of your app. LeanCloud offers a variety of ways to help you secure your apps as well as the data stored within them. The following articles give you the necessary information you need to know regarding app security.
Storing data on LeanCloud is built around AV.Object. Each AV.Object contains key-value pairs of JSON-compatible data. This data is schema-free, which means that you don't need to specify ahead of time what keys exist on each AV.Object. Simply set whatever key-value pairs you want, and our backend will store it.
For example, the AV.Object storing a simple todo item may contain the following data:
title: "Email Linda to Confirm Appointment",
isComplete: false,
priority: 2,
tags: ["work", "sales"]
Data Types
AV.Object supports a wide range of data types to be used for each field, including common ones like String, Number, Boolean, Object, Array, and Date. You can nest objects in JSON format to store more structured data within a single Object or Array field.
Special data types supported by AV.Object include Pointer and File, which are used to store a reference to another AV.Object and binary data respectively.
AV.Object also supports GeoPoint, a special data type you can use to store location-based data. See GeoPoints for more details.
Some examples:
// Basic types
const bool = true;
const number = 2018;
const string = `${number} Top Hit Songs`;
const date = new Date();
const array = [string, number];
const object = {
number: number,
string: string
};
// Create an object
const TestObject = AV.Object.extend('TestObject');
const testObject = new TestObject();
testObject.set('testNumber', number);
testObject.set('testString', string);
testObject.set('testDate', date);
testObject.set('testArray', array);
testObject.set('testObject', object);
testObject.save();
We do not recommend storing large pieces of binary data like images or documents with AV.Object. The size of each AV.Object should not exceed 128 kilobytes. We recommend using AV.File for storing images, documents, and other types of files. To do so, create AV.File objects and assign them to fields of AV.Object. See Files for details.
Keep in mind that our backend stores dates in UTC format and the SDK will convert them to local times upon retrieval.
The date values displayed in your app's Dashboard > LeanStorage > Data are also converted to match your operating system's time zone. The only exception is that when you retrieve these date values through our REST API, they will remain in UTC format. You can manually convert them using appropriate time zones when necessary.
To learn about how you can protect the data stored on LeanCloud, see Data and Security.
Creating Objects
The code below creates a new instance of AV.Object with class Todo:
// Create a new subclass of AV.Object
const Todo = AV.Object.extend('Todo');
// Create a new instance of that class
const todo = new Todo();
// You can also use the AV.Object constructor directly
const todo = new AV.Object('Todo');
The constructor takes a class name as a parameter so that the cloud knows the class you are using to create the object. A class in LeanCloud is comparable to a table in a relational database. A class name starts with a letter and can only contain numbers, letters, and underscores.
If you keep seeing Maximum call stack size exceeded exception in your log, chances are AV.Object.extend had been called using the same class name more than once within a loop or a callback. To resolve the issue, either move it out of the loop or the callback to make sure it won't be over-instantiated or switch to JavaScript SDK 1.4 or higher which handles the problem automatically.
If you're using ES6 in your codebase, you can subclass AV.Object with the extends keyword. However, when using extends, the SDK is not automatically aware of your subclasses. You will need to register the subclasses with the SDK:
class Todo extends AV.Object {
// More properties and methods
}
// Register the subclass
AV.Object.register(Todo);
After doing this, you will be able to add additional methods and properties to your subclasses of AV.Object.
Saving Objects
The following code saves a new object with class Todo to the cloud:
// Declare a class
const Todo = AV.Object.extend('Todo');
// Create an object
const todo = new Todo();
// Set values of fields
todo.set('title', 'Sign up for Marathon');
todo.set('priority', 2);
// Save the object to the cloud
todo.save().then((todo) => {
// Execute any logic that should take place after the object is saved
console.log(`Object saved. objectId: ${todo.id}`);
}, (error) => {
// Execute any logic that should take place if the save fails
});
To make sure the object is successfully saved, take a look at Dashboard > LeanStorage > Data > Todo in your app. You should see a new entry of data with something like this when you click on its objectId:
You don't have to create or set up a new class called Todo in Dashboard > LeanStorage > Data before running the code above. If the class doesn't exist, it will be automatically created.
There are several built-in fields that are provided by default which you don't need to specify in your code:
Built-in Field
Type
Description
objectId
String
A unique identifier for each saved object.
ACL
AV.ACL
Access Control List, a special object defining the read and write permissions of other people.
createdAt
Date
The time the object was created.
updatedAt
Date
The time the object was last modified.
Each of these fields is filled in by the cloud automatically and doesn't exist on the local AV.Object until a save operation has been completed.
Field names, or keys, can only contain letters, numbers, and underscores. A custom key can neither start with double underscores __, nor be identical to any system reserved words or built-in field names (ACL, className, createdAt, objectId, and updatedAt) regardless of letter cases.
Values can be strings, numbers, booleans, or even arrays and dictionaries — anything that can be JSON-encoded. See Data Types for more information.
We recommend that you adopt CamelCase naming convention to NameYourClassesLikeThis and nameYourKeysLikeThis, which keeps your code more readable.
Retrieving Objects
If AV.Object is already in the cloud, you can retrieve it using its objectId with the following code:
const query = new AV.Query('Todo');
query.get('582570f38ac247004f39c24b').then((todo) => {
// todo is the instance of the Todo object with objectId 582570f38ac247004f39c24b
const title = todo.get('title');
const priority = todo.get('priority');
// Acquire special properties
const objectId = todo.id;
const updatedAt = todo.updatedAt;
const createdAt = todo.createdAt;
});
After retrieving an object, you can use the get method to acquire the data stored in its fields. Be aware that objectId, updatedAt, and createdAt are 3 special properties that cannot be retrieved using the get method or modified with the set method. Each of these fields is filled in by the cloud only, so they don't exist on AV.Object until a save operation has been completed.
If you try to access a field or property that doesn't exist, the SDK will not raise an error. Instead, it will return undefined.
Sometimes you may want to get all the fields back at one time without calling get on each field of the object. This could be helpful when you implement data binding. To do so, call toJSON on AV.Object:
If you need to refresh a local object with the latest version of it in the cloud, call the fetch method on it:
const todo = AV.Object.createWithoutData('Todo', '582570f38ac247004f39c24b');
todo.fetch().then((todo) => {
// todo is refreshed
});
Keep in mind that any unsaved changes made to the object prior to calling fetch will be discarded. To avoid this, you have the option to provide a list of keys when calling the method so that only the fields being specified are retrieved and refreshed (including special built-in fields such as objectId, createdAt, and updatedAt). Changes made to other fields will remain intact.
const todo = AV.Object.createWithoutData('Todo', '582570f38ac247004f39c24b');
todo.fetch({
keys: 'priority, location'
}).then((todo) => {
// Only priority and location will be retrieved and refreshed
});
Updating Objects
To update an existing object, assign the new data to each field and call the save method. For example:
const todo = AV.Object.createWithoutData('Todo', '582570f38ac247004f39c24b');
todo.set('content', 'Weekly meeting has been rescheduled to Wed 3pm for this week.');
todo.save();
LeanCloud automatically figures out which data has changed and only the fields with changes will be sent to the cloud. The fields you didn't update will remain intact.
To view which attributes have unsaved changes, you can invoke the dirtyKeys method:
todo.dirtyKeys() // ['content']
To revert unsaved changes, you can invoke the revert method.
Invoking revert() without passing any parameter will revoke all unsaved changes.
To specify attributes to be revoked, pass an array as parameter:
todo.revert(['content'])
Updating Data Conditionally
By passing in a query option when saving, you can specify conditions on the save operation so that the object can be updated atomically only when those conditions are met. If no object matches the conditions, the cloud will return error 305 to indicate that there was no update taking place.
For example, in the class Account there is a field called balance, and there are multiple incoming requests that want to modify this field. Since an account cannot have a negative balance, we can only allow a request to update the balance when the amount requested is lower than or equal to the balance:
const account = AV.Object.createWithoutData('Account', '5745557f71cfe40068c6abe0');
// Atomically decrease balance by 100
const amount = -100;
account.increment('balance', amount);
account.save(null, {
// Add the condition
query: new AV.Query('Account').greaterThanOrEqualTo('balance', -amount),
// Return the latest data in the cloud upon completion.
// All the fields will be returned if the object is new,
// otherwise only fields with changes will be returned.
fetchWhenSave: true
}).then((account) => {
console.log(`Balance: ${account.get('balance')}`);
}, (error) => {
if (error.code === 305) {
console.error('Insufficient balance. Operation failed!');
}
});
query option only works for existing objects. In other words, it has no effect on objects that haven't been saved to the cloud yet.
The benefit of using query option instead of combining AV.Query and AV.Object shows up when you have multiple clients trying to update the same field at the same time. The latter way is more cumbersome and may lead to potential inconsistencies.
Updating Counters
Take Twitter as an example, we need to keep track of how many Likes and Retweets a tweet has gained so far. Since a Like or Retweet action can be triggered simultaneously by multiple clients, saving objects with updated values directly can lead to inaccurate results. To make sure that the total number is stored correctly, LeanCloud allows you to atomically increase (or decrease) the value of a number field:
post.increment('likes', 1);
You can specify the amount of increment (or decrement) by providing an additional argument. If the argument is not provided, 1 is used by default.
Updating Arrays
There are several operations that can be used to atomically update an array associated with a given key:
AV.Object.add('arrayKey', value) appends the given object to the end of an array.
AV.Object.addUnique('arrayKey', value) adds the given object into an array only if it is not in it. The object will be inserted at a random position.
AV.Object.remove('arrayKey', value) removes all instances of the given object from an array.
For example, Todo has a field named alarms for keeping track of times at which a user wants to be alerted. The following code adds the times to the alarms field:
const alarm1 = new Date('2018-04-30T07:10:00');
const alarm2 = new Date('2018-04-30T07:20:00');
const alarm3 = new Date('2018-04-30T07:30:00');
const alarms = [alarm1, alarm2, alarm3];
const todo = new AV.Object('Todo');
todo.addUnique('alarms', alarms);
todo.save();
Deleting Objects
The following code deletes a Todo object from the cloud:
const todo = AV.Object.createWithoutData('Todo', '582570f38ac247004f39c24b');
todo.destroy();
You can delete a given field of an object with the unset method:
const todo = AV.Object.createWithoutData('Todo', '582570f38ac247004f39c24b');
// The priority field will be removed
todo.unset('priority');
// Save the object to the cloud
todo.save();
Removing data from the cloud should always be dealt with great caution as it may lead to non-recoverable data loss. We strongly advise that you read ACL Guide to understand the risks thoroughly. You should also consider implementing class-level, object-level, and field-level permissions for your classes in the cloud to guard against unauthorized data operations.
Batch Processing
You can create, save, delete, or fetch multiple objects within a single request:
// Create an array for storing AV.Objects
const objects = [];
// Batch create and update
AV.Object.saveAll(objects);
// Batch delete
AV.Object.destroyAll(objects);
// Batch fetch
AV.Object.fetchAll(objects);
The following code sets isComplete of all Todo objects to be true:
const query = new AV.Query('Todo');
query.find().then((todos) => {
// Get a collection of todos to work on
todos.forEach((todo) => {
// Update value
todo.set('isComplete', true);
});
// Save all at once
AV.Object.saveAll(todos);
});
Although each function call sends multiple operations in one single network request, saving operations and fetching operations are billed as separate API calls for each object in the collection, while deleting operations are billed as a single API call.
Data Models
Objects may have relationships with other objects. For example, in a blogging application, a Post object may have relationships with many Comment objects. LeanCloud supports three kinds of relationships, including one-to-one, one-to-many, and many-to-many.
One-to-One and One-to-Many Relationships
One-to-one and one-to-many relationships are modeled by saving AV.Object as a value in the other object. For example, each Comment in a blogging app might correspond to one Post.
The following code creates a new Post with a single Comment:
// Create a post
const post = new AV.Object('Post');
post.set('title', 'I am starving!');
post.set('content', 'Hmmm, where should I go for lunch?');
// Create a comment
const comment = new AV.Object('Comment');
comment.set('content', 'KFC is the best!');
// Add the post as a property of the comment
comment.set('parent', post);
// This will save both post and comment
comment.save();
Internally, the backend will store the referred-to object with the Pointer type in just one place in order to maintain consistency. You can also link objects using their objectIds like this:
const post = AV.Object.createWithoutData('Post', '57328ca079bc44005c2472d0');
comment.set('post', post);
See Relational Queries for instructions on how to query relational data.
Many-to-Many Relationships
The easiest way to model many-to-many relationships is to use arrays. In most cases, using arrays helps you reduce the number of queries you need to make and leads to better performance. However, if additional properties need to be attached to the relationships between two classes, using join tables would be a better choice. Keep in mind that the additional properties are used to describe the relationships between classes rather than any single class.
We recommend you to use join tables if the total amount of objects of any class exceeds 100.
Modeling relationships among data could be a tough job. We have written a dedicated article to address Data Modeling in greater detail, which we highly recommend you to read.
Queries
We've already seen how you can retrieve a single object from the cloud with AV.Object, but it doesn't seem to be powerful enough when you need to retrieve multiple objects that match certain conditions at once. In such situation, AV.Query would be a more efficient tool you can use.
Basic Queries
The general steps of performing a basic query include:
Creating AV.Query.
Putting conditions on it.
Retrieving an array of objects matching the conditions.
The code below retrieves all Student objects whose lastName is Smith:
const query = new AV.Query('Student');
query.equalTo('lastName', 'Smith');
query.find().then((students) => {
// students is an array of Student objects satisfying conditions
});
Query Constraints
There are several ways to put constraints on the objects found by AV.Object.
The code below filters out objects with Jack as firstName:
query.notEqualTo('firstName', 'Jack');
For sortable types like numbers and strings, you can use comparisons in queries:
// Restricts to age < 18
query.lessThan('age', 18);
// Restricts to age <= 18
query.lessThanOrEqualTo('age', 18);
// Restricts to age > 18
query.greaterThan('age', 18);
// Restricts to age >= 18
query.greaterThanOrEqualTo('age', 18);
You can apply multiple constraints to a single query, and objects will only be in the results if they match all of the constraints. In other words, it's like concatenating constraints with AND:
You can limit the number of results by setting limit (defaults to 100):
// Limit to at most 10 results
query.limit(10);
For performance reasons, the maximum value allowed for limit is 1000, meaning that LeanCloud would only return 1,000 results even it is set to be greater than 1000.
If you need exactly one result, you may use first for convenience:
const query = new AV.Query('Todo');
query.equalTo('priority', 2);
query.first().then((todo) => {
// todo is the first Todo object satisfying conditions
});
You can skip certain number of results by setting skip:
// Skip the first 20 results
query.skip(20);
You can implement pagination in your app by using skip together with limit:
const query = new AV.Query('Todo');
query.equalTo('priority', 2);
query.limit(10);
query.skip(20);
Keep in mind that the higher the skip goes, the slower the query will run. You may consider using createdAt or updatedAt (which are indexed) to set range boundaries for large datasets to make queries more efficient.
For sortable types, you can control the order in which results are returned:
// Sorts the results in ascending order by the createdAt property
query.ascending('createdAt');
// Sorts the results in descending order by the createdAt property
query.descending('createdAt');
You can even attach multiple sorting rules to a single query:
To retrieve objects that have or do not have particular fields:
// Finds objects that have the 'images' field
query.exists('images');
// Finds objects that don't have the 'images' field
query.doesNotExist('images');
You can use the matchesKeyInQuery method to look for objects with values of fields matching those of objects returned by another query.
For example, if you have a Country class matching countries with languages and a Student class matching students with their nationalities:
name
language
US
English
UK
English
China
Chinese
fullName
nationality
John Doe
US
Tom Sawyer
UK
Ming Li
China
The following code looks for all the students who are from English-speaking countries:
const studentQuery = new AV.Query('Student');
const countryQuery = new AV.Query('Country');
// Get all English-speaking countries
countryQuery.equalTo('language', 'English');
// Match Student's nationality with Country's name
studentQuery.matchesKeyInQuery('nationality', 'name', countryQuery);
studentQuery.find().then((students) => {
// students contains John Doe and Tom Sawyer
});
You can restrict the fields returned by providing a list of keys with select. The code below retrieves todos with only the title and content fields (and also special built-in fields such as objectId, createdAt, and updatedAt):
You can add a minus prefix to the attribute name for inverted selection.
For example, if you do not care about the post author, use -author.
The inverted selection also applies to preserved attributes, and can be used with dot notations, e.g. -pubUser.createdAt.
The unselected fields can be fetched later with fetch. See Refreshing Objects.
Queries on String Values
Use startsWith to restrict to string values that start with a particular string. Similar to a LIKE operator in SQL, it is indexed so it is efficient for large datasets:
const query = new AV.Query('Todo');
// SQL equivalent: title LIKE 'lunch%'
query.startsWith('title', 'lunch');
Use contains to restrict to string values that contain a particular string:
const query = new AV.Query('Todo');
// SQL equivalent: title LIKE '%lunch%'
query.contains('title', 'lunch');
Unlike startsWith, contains can't take advantage of indexes, which is not encouraged to be used for large datasets.
Please note that both startsWith and contains perform case-sensitive matching, so the examples above will not look for string values containing Lunch, LUNCH, etc.
If you are looking for string values that do not contain a particular string, use matches with regular expressions:
const query = new AV.Query('Todo');
// 'title' without 'ticket' (case-insensitive)
const regExp = new RegExp('^((?!ticket).)*$', 'i');
query.matches('title', regExp);
However, performing queries with regular expressions as constraints can be very expensive, especially for classes with over 100,000 records. The reason behind this is that queries like this can't take advantage of indexes and will lead to exhaustive scanning of the whole dataset to find the matching objects. We recommend that you take a look at our In-App Searching feature, a full-text search solution we provide to improve your app's searching ability and user experience.
If you are facing performance issues with queries, please refer to Optimizing Performance for possible workarounds and best practices.
Queries on Array Values
The code below looks for all the objects with work as an element of its array field tags:
query.equalTo('tags', 'work');
To look for objects whose array field tags contains three elements:
query.sizeEqualTo('tags', 3);
You can also look for objects whose array field tags contains work, sales, andappointment:
To retrieve objects whose field matches any one of the values in a given list, you can use containedIn instead of performing multiple queries. The code below constructs a query that retrieves todo items with priority to be 1or2:
// Single query
const priorityOneOrTwo = new AV.Query('Todo');
priorityOneOrTwo.containedIn('priority', [1, 2]);
// Mission completed :)
// ---------------
// vs.
// ---------------
// Multiple queries
const priorityOne = new AV.Query('Todo');
priorityOne.equalTo('priority', 1);
const priorityTwo = new AV.Query('Todo');
priorityTwo.equalTo('priority', 2);
const priorityOneOrTwo = AV.Query.or(priorityOne, priorityTwo);
// Kind of verbose :(
Conversely, you can use notContainedIn if you want to retrieve objects that do not match any of the values in a list.
Relational Queries
There are several ways to perform queries for relational data. To retrieve objects whose given field matches a particular AV.Object, you can use equalTo just like how you use it for other data types. For example, if each Comment has a Post object in its post field, you can fetch all the comments for a particular Post with the following code:
const post = AV.Object.createWithoutData('Post', '57328ca079bc44005c2472d0');
const query = new AV.Query('Comment');
query.equalTo('post', post);
query.find().then((comments) => {
// comments contains the comments for the post
});
To retrieve objects whose given field contains AV.Object that matches a different query, you can use matchesQuery. The code below constructs a query that looks for all the comments for posts with images:
const innerQuery = new AV.Query('Post');
innerQuery.exists('image');
const query = new AV.Query('Comment');
query.matchesQuery('post', innerQuery);
To retrieve objects whose given field does not contain AV.Object that matches a different query, use doesNotMatchQuery instead.
Sometimes you may need to look for related objects from different classes without extra queries. In such situations, you can use include on the same query. The following code retrieves the last 10 comments together with the posts related to them:
const query = new AV.Query('Comment');
// Retrieve the most recent ones
query.descending('createdAt');
// Only retrieve the last 10
query.limit(10);
// Include the related post together with each comment
query.include('post');
query.find().then((comments) => {
// comments contains the last 10 comments including the post associated with each
comments.forEach((comment) => {
// This does not require a network access
const post = comment.get('post');
});
});
You can even indicate multi-level associations using dot notations. If you wanted to include the post for each comment as well as the author of the post, you can do:
query.include('post.author');
Feel free to use include as many times as you need for the same query to have multiple fields included. Related objects retrieved in this way also works with AV.Query helpers like first and get.
Multi-level conditions passed into include won't work with any AV.Object contained in an array field. The furthest level that can be reached is the field itself.
You can also use dot notations with select to limit the fields returned from the related objects:
query.select('post.author.firstName');
Caveats about Inner Queries
The backend of LeanCloud is not built on relational databases, which makes it impossible to join tables while querying. For the relational queries mentioned above, what LeanCloud would do is to perform an inner query first (with 100 as default limit and 1000 as maximum) and then insert the result from this query into the outer query. If the number of records matching the inner query exceeds the limit and the outer query contains other constraints, the amount of the records returned in the end could be zero or less than your expectation since only the records within the limit would be inserted into the outer query.
The following actions can be taken to solve the problem:
Make sure the number of records in the result of the inner query is no more than 100. If it is between 100 and 1,000, set 1000 as the limit of the inner query.
Create redundancy for the fields being queried by the inner query on the table for the outer query.
Repeat the same query with different skip values until all the records are gone through (performance issue could occur if the value of skip gets too big).
Counting Objects
If you just need to count how many objects match a query but do not need to retrieve the actual objects, use count instead of find. For example, to count how many todos have been completed:
const query = new AV.Query('Todo');
query.equalTo('isComplete', true);
query.count().then((count) => {
console.log(`${count} todos completed.`);
});
Compound Queries
Compound queries can be used if complex query conditions need to be specified. A compound query is a logical combination (OR or AND) of subqueries.
Note that we do not support GeoPoint or non-filtering constraints (e.g. near, withinGeoBox, limit, skip, ascending, descending, include) in the subqueries of a compound query.
OR-ed Query Constraints
An object will be returned as long as it fulfills any one of the subqueries. The code below constructs a query that looks for all the todos that either have priorities higher than or equal to 3, or are already completed:
const priorityQuery = new AV.Query('Todo');
priorityQuery.greaterThanOrEqualTo('priority', 3);
const isCompleteQuery = new AV.Query('Todo');
isCompleteQuery.equalTo('isComplete', true);
const query = AV.Query.or(priorityQuery, isCompleteQuery);
Queries regarding GeoPoint cannot be present among OR-ed queries.
AND-ed Query Constraints
The effect of using AND-ed query is the same as adding constraints to AV.Query. The code below constructs a query that looks for all the todos that are created between 2016-11-13 and 2016-12-02:
const startDateQuery = new AV.Query('Todo');
startDateQuery.greaterThanOrEqualTo('createdAt', new Date('2016-11-13 00:00:00'));
const endDateQuery = new AV.Query('Todo');
endDateQuery.lessThan('createdAt', new Date('2016-12-03 00:00:00'));
const query = AV.Query.and(startDateQuery, endDateQuery);
While using an AND-ed query by itself doesn't bring anything new comparing to a basic query, to combine two or more OR-ed queries, you have to use AND-ed queries:
const createdAtQuery = new AV.Query('Todo');
createdAtQuery.greaterThanOrEqualTo('createdAt', new Date('2018-04-30'));
createdAtQuery.lessThan('createdAt', new Date('2018-05-01'));
const locationQuery = new AV.Query('Todo');
locationQuery.doesNotExist('location');
var priority2Query = new AV.Query('Todo');
priority2Query.equalTo('priority', 2);
var priority3Query = new AV.Query('Todo');
priority3Query.equalTo('priority', 3);
var priorityQuery = AV.Query.or(priority2Query, priority3Query);
var timeLocationQuery = AV.Query.or(locationQuery, createdAtQuery);
var query = AV.Query.and(priorityQuery, timeLocationQuery);
Optimizing Performance
There are several factors that could lead to potential performance issues when you conduct a query, especially when more than 100,000 records are returned at a time. We are listing some common ones here so you can design your apps accordingly to avoid them:
Querying with "not equal to" or "not include" (index will not work)
Querying on strings with a wildcard at the beginning of the pattern (index will not work)
Using count with conditions (all the entries will be gone through)
Using skip for a large number of entries (all the entries that need to be skipped will be gone through)
Sorting without index (querying and sorting cannot share a composite index unless the conditions used on them are both covered by the same one)
Querying without index (the conditions used on the query cannot share a composite index unless all of them are covered by the same one; additional time will be consumed if excessive data falls under the uncovered conditions)
LiveQuery
LiveQuery is, as its name implies, derived from AV.Query but has enhanced capability. It allows you to automatically synchronize data changes from one client to other clients without writing complex code, making it suitable for apps that need real-time data.
Suppose you are building an app that allows multiple users to edit the same file at the same time. AV.Query would not be an ideal tool since it is based on a pull model and you cannot know when to query from the cloud to get the updates.
To solve this problem, we introduced LiveQuery. This tool allows you to subscribe to the AV.Querys you are interested in. Once subscribed, the cloud will notify clients by generating event messages whenever AV.Objects that match the AV.Query are created or updated, in real-time.
Behind the scene, we use WebSocket connections to have clients and the cloud communicate with each other and maintain the subscription status of clients. In most cases, it isn't necessary to deal with the WebSocket connections directly, so we developed a simple API to help you focus on your business logic rather than technical implementations.
Initializing LiveQuery
To use LiveQuery in your app, go to your app's Dashboard > LeanStorage > Settings and check the Enable LiveQuery option under the Other section, then include the following npm module in your project:
// No need to require leancloud-storage if the following line is present
const AV = require('leancloud-storage/live-query');
Or load it with the following <script> tag:
<!-- No need to load av-min.js if the following script is present -->
<script src="//cdn.jsdelivr.net/npm/leancloud-storage@4.13.1/dist/av-live-query-min.js"></script>
Open the same URL on a different device, enter the same credentials, and hit “Login”.
Create, edit, or delete some items on one device and watch what happens on the other one.
Creating a Subscription
To make a query live, create a normal AV.Query object, put conditions on it if there are, and then subscribe to it:
const query = new AV.Query('Todo');
query.subscribe().then((liveQuery) => {
// Query becomes live after getting a subscription
});
You can't use subqueries or restrict fields being returned when using LiveQuery.
Now you will be able to receive updates related to AV.Object. If an Todo object is created by another client with Update Portfolio as title, the following code can get the new Todo for you:
The following types of data changes can be monitored once a subscription is set up:
create
update
enter
leave
delete
create Event
A create event will be triggered when a new AV.Object is created and it fulfills the AV.Query you subscribed. The object is the new AV.Object being created:
An update event will be triggered when an existing AV.Object fulfilling the AV.Query you subscribed is updated. The object is the AV.Object being updated:
An enter event will be triggered when an existing AV.Object's old value does not fulfill the AV.Query you subscribed but its new value does. The object is the AV.Object entering the AV.Query and its content is the latest value of it:
There is a clear distinction between a create event and an enter event. If an object already exists and later matches the query's conditions, an enter event will be triggered. If an object didn't exist already and is later created, a create event will be triggered.
leave Event
A leave event will be triggered when an existing AV.Object's old value fulfills the AV.Query you subscribed but its new value does not. The object is the AV.Object leaving the AV.Query and its content is the latest value of it:
A delete event will be triggered when an existing AV.Object fulfilling the AV.Query you subscribed is deleted. The object is the objectId of the AV.Object being deleted:
There are different scenarios regarding losing connections:
The connection to the Internet is lost unexpectedly.
The user performs certain operations outside of the app, like switching the app to the background, turning off the phone, or turning on the flight mode.
For the scenarios above, you don't need to do any extra work. As long as the user switches back to the app, the SDK will automatically re-establish the connection.
There is another scenario when the user completely kills the app or closes the web page. In this case, the SDK cannot automatically re-establish the connection. You will have to create subscriptions again by yourself.
Caveats about LiveQuery
Given the real-time feature of LiveQuery, developers may find it tempting to use it for instant messaging. As LiveQuery is neither designed nor optimized for completing such tasks, we discourage such use of this tool, let alone there will be an additional cost for saving message history and rising challenges of code maintenance. We recommend using our real-time messaging solution for this scenario.
Files
AV.File allows you to store application files in the cloud that would otherwise be too large or cumbersome to fit into a regular AV.Object. The most common use case is storing images, but you can also use it for documents, videos, music, and any other binary data.
Creating Files
You can create a file from a base64-encoded string:
const data = { base64: 'TGVhbkNsb3Vk' };
// resume.txt is the file name
const file = new AV.File('resume.txt', data);
You can also create a file from an array of byte values:
const data = [0x4c, 0x65, 0x61, 0x6e, 0x43, 0x6c, 0x6f, 0x75, 0x64];
const file = new AV.File('resume.txt', data);
What's more, you can also create a file from a Blob (in Browser), and Buffer or Stream (in Node.js).
For React Native applications, you can create a file from a local path:
const data = { blob: { uri: localFileUri } }
const file = new AV.File('resume.txt', data)
When creating files from URLs, the SDK will not upload the actual files into the cloud but will store the addresses of the files as strings. This will not lead to actual traffic for uploading files, as opposed to creating files in other ways by doing which the files will be actually stored into the cloud.
LeanCloud will auto-detect the type of the file you are uploading based on the file extension, but you can also specify the Content-Type (commonly referred to as MIME type):
const file = new AV.File('resume.txt', data, 'application/json');
But the most common method for creating files is to upload them from local paths. In a web app, you can first create an input button in the user interface:
<input type="file" id="avatar-upload" />
Then get a reference to that file in a click handler:
const avatarUpload = document.getElementById('avatar-upload');
if (avatarUpload.files.length) {
const localFile = avatarUpload.files[0];
const file = new AV.File('avatar.jpg', localFile);
}
The file we uploaded here is named avatar.jpg. There are a couple of things to note here:
Each file uploaded will get its unique objectId, so it is allowed for multiple files to share the same name.
A correct extension needs to be assigned to each file which the cloud will use to infer the type of a file. For example, if you are storing a PNG image with AV.File, use .png as its extension.
If the file doesn't have an extension and the content type is not specified, LeanCloud defaults the file's type to be application/octet-stream.
You can specify the path of the uploaded file via the key property.
For example, use a robots.txt to restrict search engines from crawling uploaded files:
file.save({ key: 'robots.txt' });
Saving Files
By saving a file, you store it into the cloud and get a permanent URL pointing to it:
file.save().then((file) => {
console.log(`File uploaded. objectId: ${file.id}`);
}, (error) => {
// The file either could not be read or could not be saved to LeanCloud
});
A file successfully uploaded can be found in the _File class and cannot be modified later. If you need to change the file, you have to upload the modified file again and a new objectId and URL will be generated.
To preserve original file name in the resulting url, pass the keepFileName parameter to the save method:
You can associate a file with AV.Object after it is saved:
const Todo = AV.Object.extend('Todo');
const todo = new Todo();
todo.set('title', 'Get Cakes');
// attachments is an Array field
todo.add('attachments', file);
todo.save();
You can also construct a AV.Query to query files:
const query = new AV.Query('_File');
Note that the url field of internal files (files uploaded to LeanCloud file service) is dynamically generated by the cloud, which will switch custom domain names automatically.
Therefore, querying files by the url field is only applicable to external files (files created by saving the external URL directly to the _File table).
Query internal files by the key field (path in URL) instead.
On a related note, if the files are referenced in an array field of AV.Object and you want to get them within the same query for AV.Object, you need to use the include method with AV.Query. For example, if you are retrieving all the todos with the same title Get Cakes and you want to retrieve their related attachments at the same time:
// Get all todos with the same title and contain attachments
const query = new AV.Query('Todo');
query.equalTo('title', 'Get Cakes');
query.exists('attachments');
// Include attachments with each todo
query.include('attachments');
query.find().then((todos) => {
todos.forEach((todo) => {
// Get attachments array for each todo
const attachments = todo.get('attachments');
attachments.forEach((attachment) => {
// Each attachment is an AV.File instance
console.log(`URL of the attachment: ${attachment.get('url')}`);
});
});
});
Upload Progress
You can monitor the progress of uploading and display that to the user:
file.save({
onprogress: (progress) => {
console.log(progress);
// {
// loaded: 1024,
// total: 2048,
// percent: 50
// }
}
}).then((file) => {
// Things to do after saving
});
File Metadata
When uploading a file, you can attach additional properties to it with metaData. A file's metaData cannot be updated once the file is stored to the cloud.
// Set metadata
file.metaData('author', 'LeanCloud');
file.save().then((file) => {
// Get all metadata
const metadata = file.metaData();
// Get author
const author = file.metaData('author');
// Get file name
const fileName = file.get('name');
// Get size (not available for files created from base64-encoded strings or URLs)
const size = file.size();
});
By default, a file is not allowed to be deleted. You can change the setting by going to Dashboard > LeanStorage > Data > _File and select Others > Permission settings > delete.
CDN Support
You can set up your own CDN to improve the speed your users access the files stored on LeanCloud. Take Amazon CloudFront CDN as an example:
Take the domain name from the URL of your AV.File and fill it into CloudFront's Origin Domain Name. Leave the other settings with defaults.
Promises
Each asynchronous method in LeanCloud JavaScript SDK returns a Promise which can be used to handle the completion and exception of the method. The example below updates an AV.Object after it is being queried:
const query = new AV.Query('Todo');
query.equalTo('priority', 1);
// find is asynchronous which gives back a Promise that then can be called on
query.find().then((todos) => {
// Returns a list of objects
const todo = todos[0];
todo.set('notes', 'Needs to be finished today.');
// save is also asynchronous which gives back a Promise that you can return here and chain another Promise afterwards
return todo.save();
}).then(() => {
// The Promise returned by save method
console.log('Successfully updated todo.');
}).catch((error) => {
// Put catch at the very end of Promise chain which catches all the errors
console.error(error);
});
The then Method
Each Promise has a method called then which takes in two callbacks. The first callback is called when the Promise is resolved (runs successfully) while the second one is called when it is rejected (gets error):
Promise allows you to connect asynchronous requests elegantly according to the order they should be called. If the callback of a Promise returns another Promise, the callback in the second then will not be resolved unless the one in the first then is resolved. This is also called Promise Chain.
// Add contents to the page following the order of chapters
const chapterIds = [
'584e1c408e450a006c676162', // Chapter One
'584e1c43128fe10058b01cf5', // Chapter Two
'581aff915bbb500059ca8d0b' // Chapter Three
];
new AV.Query('Chapter').get(chapterIds[0]).then((chapterOne) => {
// Add contents to the page
addHtmlToPage(chapterOne.get('content'));
// Return the new Promise
return new AV.Query('Chapter').get(chapterIds[1]);
}).then((chapterTwo) => {
addHtmlToPage(chapterTwo.get('content'));
return new AV.Query('Chapter').get(chapterIds[2]);
}).then((chapterThree) => {
addHtmlToPage(chapterThree.get('content'));
// Done
});
Error Handling with Promises
If any single Promise in the chain throws an error, all the callbacks following it meant for successful operations will be skipped until an error handling callback is encountered.
It is a common practice to attach an error handling function at the end of a Promise chain.
The code above can be rewritten with catch in the following way:
new AV.Query('Chapter').get(chapterIds[0]).then((chapterOne) => {
addHtmlToPage(chapterOne.get('content'));
// Force an error
throw new Error('Error');
return new AV.Query('Chapter').get(chapterIds[1]);
}).then((chapterTwo) => {
// The code here will be ignored
addHtmlToPage(chapterTwo.get('content'));
return new AV.Query('Chapter').get(chapterIds[2]);
}).then((chapterThree) => {
// The code here will be ignored
addHtmlToPage(chapterThree.get('content'));
}).catch((error) => {
// This error handling function will be called, printing out the error message 'Error'
console.error(error.message);
});
async and await
async and await allows you to use Promise by writing code in a more synchronous manner:
async function example() {
try {
const query = new AV.Query('Todo');
query.equalTo('priority', 1);
const todos = await query.find();
const todo = todos[0];
todo.set('notes', 'Needs to be finished today.');
return await todo.save();
} catch (error) {
console.error(error);
}
}
LeanCloud allows you to associate real-world latitude and longitude coordinates with an object by adding AV.GeoPoint to the AV.Object. By doing so, queries on the proximity of an object to a given point can be performed, allowing you to implement functions like looking for users or places nearby easily.
To associate a point with an object, you need to create the point first. The code below creates AV.GeoPoint with 39.9 as latitude and 116.4 as longitude:
const point = new AV.GeoPoint(39.9, 116.4);
// Other ways of creating the same AV.GeoPoint
const point = new AV.GeoPoint([39.9, 116.4]);
const point = new AV.GeoPoint({ latitude: 39.9, longitude: 116.4 });
Now you can store the point in an object as a regular field:
todo.set('location', point);
Geo Queries
With a number of existing objects with spatial coordinates, you can find out which of them are closest to a given point, or are contained within a particular area. This can be done by adding another restriction to AV.Query using near. The code below returns a list of Todo objects with location closest to a given point:
const query = new AV.Query('Todo');
const point = new AV.GeoPoint(39.9, 116.4);
query.near('location', point);
// Limit to 10 results
query.limit(10);
query.find().then((todos) => {
// todos is an array of Todo objects satisfying conditions
});
Additional sorting conditions like ascending and descending will gain higher priorities than the default order by distance.
To have the results limited within a certain distance, check out withinKilometers, withinMiles, and withinRadians in our API docs.
You can also query for the set of objects that are contained within a rectangular bounding box with withinGeoBox:
const query = new AV.Query('Todo');
const southwest = new AV.GeoPoint(30, 115);
const northeast = new AV.GeoPoint(40, 118);
query.withinGeoBox('location', southwest, northeast);
Caveats about GeoPoints
There are a couple of things to keep in mind:
Each AV.Object may only have one field containing AV.GeoPoint object.
Points should not exceed the extreme ends of the ranges. Latitude should be between -90.0 and 90.0. Longitude should be between -180.0 and 180.0. Attempting to set latitude or longitude out of bounds will cause an error.
Users
At the core of many apps, there is a notion of user accounts that allows users to access their information in a secure manner. We provide a specialized user class called AV.User which automatically handles much of the functionality required for user account management in your app.
AV.User is a subclass of AV.Object. Therefore, all the methods that work for AV.Object also work for AV.User. The only difference is that AV.User has some additional features specific to user accounts. Each app has a dedicated _User class for storing AV.User.
User Properties
AV.User offers the following fields that AV.Object does not have:
username: The username of the user.
password: The password of the user.
email: The email address of the user.
emailVerified: Whether the user has verified the email address with LeanCloud or not.
mobilePhoneNumber: The mobile phone number of the user.
mobilePhoneVerified: Whether the user has verified the mobile phone number with LeanCloud or not.
We'll go through each of these in detail as we run through the various use cases for users.
Signing up
When a user first opens your app, you may want them to sign up for an account. The following code shows a typical sign-up process with username and password:
// Create an instance
const user = new AV.User();
// Same as user.set('username', 'Tom')
user.setUsername('Tom');
user.setPassword('cat!@#123');
// Optional
user.setEmail('tom@leancloud.rocks');
user.setMobilePhoneNumber('+19490008888');
// Other fields can be set in the same way as AV.Object
user.set('gender', 'secret');
user.signUp().then((user) => {
// Sign-up completed
console.log(`User created. objectId: ${user.id}`);
}, (error) => {
// Sign-up failed (often because the username is already taken)
});
A new AV.User should always be created using signUp rather than save. Subsequent updates to a user can be done with save.
If the code returns the error 202, it means that a user with the same username already exists in _User table and the client should prompt the user to try a different username. It is also required that each email or mobilePhoneNumber appears only once in the corresponding column. Otherwise, error 203 or 214 will occur. You may ask a user to sign up with an email address and make the username to be the same as the email. By doing so, the user can directly reset their password with email.
When creating a user with username and password, the SDK sends the password to the cloud in plaintext through HTTPS and the password will be hashed once it arrives to the cloud. (The cloud has no restrictions on password length and complexity.) We never store passwords in plaintext, nor will we ever transmit passwords back to the client in plaintext. Our hashing algorithm guarantees that the original password cannot be retrieved by rainbow table attack and even app developers themselves will not be able to see the password. Keep in mind that the password should not be hashed by the client, or the password reset function will not work.
Signing up with Phones
For a mobile app, it's also common to ask users to sign up with their phone numbers instead of usernames and passwords. There are two basic steps in it. First, ask the user to enter a phone number that can receive text messages. When the user clicks on the "Get Verification Code" button, call the following method to have a 6-digit verification code sent to the phone number the user just entered:
AV.Cloud.requestSmsCode('+19490008888');
After the verification code is entered by the user, call the following method to finish signing up:
The username will be the same as mobilePhoneNumber and a password will be generated by LeanCloud automatically.
If you wish to let the user specify their own password, you can let them fill in a password together with their mobile phone number, and then follow the process of registering with username and password described in the previous section,
submitting the user's mobile phone number as the value of both the username and the mobilePhoneNumber fields.
If you wish, you can also check the following options in "Dashboard > LeanStorage > Users > Settings": "Do not allow users with unverified phone numbers to log in", and/or "Allow users with verified phone numbers to login with SMS".
Phone Number Format
A phone number that AV.User accepts should have a leading plus sign (+) immediately followed by the country code and the phone number without any dashes, spaces, or other non-numeric characters. For instance, +8618200008888 is a valid China number (86 is the country code) and +19490008888 is a valid US or Canada number (1 is the country code).
For a list of countries and regions that LeanCloud can reach out through SMS, please refer to the Pricing page on our website.
Logging in
The code below logs a user in with username and password:
AV.User.logIn('Tom', 'cat!@#123').then((user) => {
// Logged in successfully
}, (error) => {
// Failed to log in (the password may be incorrect)
});
Logging in with Emails
The code below logs a user in with email and password:
AV.User.loginWithEmail('tom@leancloud.rocks', 'cat!@#123').then((user) => {
// Logged in successfully
}, (error) => {
// Failed to log in (the password may be incorrect)
});
Logging in with Phones
If you are allowing users to sign up with their phone numbers, you can also let them log in with either a password or a verification code sent via text message. The code below logs a user in with phone number and password:
AV.User.logInWithMobilePhone('+19490008888', 'cat!@#123').then((user) => {
// Logged in successfully
}, (error) => {
// Failed to log in (the password may be incorrect)
});
By default, LeanCloud allows a user to log in to their account as long as the phone number and the password are correct even when the ownership of the phone hasn't been verified. To make your app more secure, you can choose to allow only those who have their phones verified to log in. The option can be found in Dashboard > LeanStorage > Settings.
You may also let a user in with a verification code sent to their phone, which is useful when the user forgets the password and does not want to reset it at the moment. Similar to the steps of signing a user up with phone numbers, ask the user to enter the phone number associated with the account, and call the following method once the user clicks on the "Get Verification Code" button:
AV.User.requestLoginSmsCode('+19490008888');
After the verification code is entered by the user, call the following method to finish logging in:
AV.User.logInWithMobilePhoneSmsCode('+19490008888', '123456').then((user) => {
// Logged in successfully
}, (error) => {
// Verification code is incorrect
});
Sandbox Phone Number
During the development of your application, you may need to test the sign-up or log-in related API intensively with your phone. As there are, however, limits to how quickly messages can be sent into the carrier networks, your testing pace can be significantly affected.
To work around it, you can set up a sandbox phone number in Dashboard > Messaging > SMS > Settings. LeanCloud will issue a fixed verification code to go with that sandbox phone number. Whenever LeanCloud detects such combination of data, the user will be let right in authenticated without any connections to the carrier networks being made.
On a related note, a sandbox phone number also comes in handy for iOS apps that allow users to log in with SMS code. This is because Apple may ask developers to provide a fixed combination of phone number and verification code for them to review the app as a normal user. Failure to do so may result in their app being rejected by the App Store.
For more details regarding the limitations of sending and receiving SMS messages, see SMS Guide.
Single Device Sign-on
In some scenarios you may want to restrict a user's account to be logged on by no more than one device at a time. That is, when a user logs in to the app on a new device, all the previous sessions on other devices will become invalid. Here's the instruction about how you can implement this feature with LeanCloud:
Create a new class that keeps track of each user's credentials and device information.
Each time when a user logs in on a device, update the device information of this user to be the current device.
When the app running on another device is opened, check if the device matches the one stored in the cloud. If it does not, log the user out.
User Account Lockout
If the wrong password or verification code is entered for an account for more than 6 times within 15 minutes, the account will be disabled temporarily and the error { "code": 1, "error": "You have exceeded the maximum number of login attempts, please try again later, or consider resetting your password." } will be returned.
The account will be automatically recovered 15 minutes after the last attempt and the process cannot be expedited through SDK or REST API. While the account is disabled, the user cannot be logged in even though the correct credentials are provided. The restriction applies to both client-side SDKs and LeanEngine.
Verifying Emails
You can request that your users have their email addresses verified before they can log in or access certain functions in your app. This makes it harder for spam users to abuse your app. By default, each user has an emailVerified field which becomes false when the user first signs up or has their email address changed. In your app's Dashboard > LeanStorage > Settings, you can enable Send verification emails when users register or change email addresses from clients so that when a user signs up or changes their email address, an email containing a verification link will be sent out automatically. You can find the option to prevent users with unverified email addresses from logging in on the same page.
If a user forgets to click on the link and needs to have their account verified later, the following code can be used to send a new email:
The emailVerified will become true after the link is clicked on. This field can never be true when the email field is empty.
Verifying Phone Numbers
Similar to Verifying Emails, you can also request that your users have their phone numbers verified before they can log in or access certain functions in your app. By default, each user has a mobilePhoneVerified field which becomes false when the user first signs up or has their phone number changed.
In your app's Dashboard > LeanStorage > User > Setting, you can find the option to prevent users with unverified phone numbers from logging in on the same page.
You can also initiate a verification request at anytime with the following code:
AV.User.requestMobilePhoneVerify('+19490008888');
After the verification code is entered by the user, call the following method and the user's mobilePhoneVerified will become true:
AV.User.verifyMobilePhone('123456').then(() => {
// mobilePhoneVerified is set to true
}, (error) => {
// Verification code is incorrect
});
Verify Phone Numbers Before Updating and Binding
LeanCloud also supports verifying the number before a user binds or updates a number.
AV.User.requestChangePhoneNumber('+19490008888');
AV.User.changePhoneNumber('+19490008888', '123456').then(() => {
// update local data
const currentUser = AV.User.current();
currentUser.setMobilePhoneNumber('+19490008888');
}, (error) => {
// verification code is invalid
});
Current User
After a user is logged in, LeanCloud SDK automatically stores the session information of this user in the client so that the user does not need to log in each time they open the client. The following code checks if there is a user logged in:
const currentUser = AV.User.current();
if (currentUser) {
// Redirect to the home page
} else {
// Show the sign-up or log-in page
}
The session information of a user will remain in the client until the user is logged out:
A session token will be returned to the client after a user is logged in. It will be cached by our SDK and will be used for authenticating requests made by the same AV.User in the future. The session token will be included in the header of each HTTP request made from the client, which helps the cloud identify the AV.User sending the request.
Below are the situations when you may need to log a user in with session token:
A session token is already cached on the client which can be used to automatically log the user in (you can use AV.User.current().getSessionToken() to get the session token of the current user).
A WebView within the app needs to know the current user.
The user is logged in on the server side using your own authentication routines and the server is able to provide the session token to the client.
The code below logs a user in with session token (the session token will be validated before proceeding):
AV.User.become('anmlwi96s381m6ca7o7266pzf').then((user) => {
// Logged in successfully
}, (error) => {
// Session token is invalid
});
For security reasons, please avoid passing session tokens as a part of URLs in non-private environments. This increases the risk that they will be captured by attackers.
If Log out the user when password is updated is checked on in Dashboard > LeanStorage > Settings, the session token of a user will be reset in the cloud after this user changes the password and the client needs to prompt the user to log in again. Otherwise, 403 (Forbidden) will be returned as an error.
The code below checks if a session token is valid:
const currentUser = AV.User.current();
currentUser.isAuthenticated().then((authenticated) => {
if (authenticated) {
// The session token is valid
} else {
// The session token is invalid
}
});
Resetting Passwords
It's quite common for the users of an app to forget their passwords. LeanCloud provides a number of ways for them to reset their passwords.
Here is the flow of resetting password with email:
The user enters the email address used for the account.
LeanCloud sends an email to the address including a link for resetting password.
After the user clicks on the link, a new page will pop up, asking for a new password.
The password will be reset after the user enters a new password.
To start with, ask the user to enter the email used for the account, and call the function below:
The code above will check if there is a user in the _User table that has the email to be the same as the one provided and will send them a password reset email if so. As mentioned previously, you can make the username of each user to be the same as their email, or collect the email separately and store it in the email field.
The content of the password reset email is fully customizable. You can go to your app's Dashboard > Settings > Email templates and modify the corresponding template.
Alternatively, you can ask for the mobile phone number instead of the email to reset their password:
The user enters the mobile phone number used for the account.
LeanCloud sends a short message to the number including a verification code.
The user types in the verification code and a new password.
The code below sends a verification code to a number:
The code above will check if there is a user in the _User table that has the mobilePhoneNumber to be the same as the one provided and will send them a verification code if so.
By changing the settings in Dashboard > LeanStorage > Settings, you can restrict the use of the mobile phone number for the above flow only if the mobilePhoneVerified field is true.
The code below resets the password of a user after they enter the verification code and a new password:
AV.User.resetPasswordBySmsCode('123456', 'cat!@#123').then(() => {
// Password is updated
}, (error) => {
// Verification code is incorrect
});
Queries on Users
To query for users, you can simple create a new AV.Query for _User:
const userQuery = new AV.Query('_User');
For security reasons, the _User table of each new app has its find permission disabled by default. Each user can only access their own data in _User table and cannot access that of others. If you need to allow each user to view other users' data, we recommend that you create a new table to store such data and enable the find permission of this table. You may also encapsulate queries on users within LeanEngine and avoid opening up find permissions of _User tables.
See Security of User Objects for other restrictions applied to the _User table, and Data and Security for more information regarding class-level permission settings.
Associations
Associations involving AV.User works in the same way as basic AV.Object. The code below saves a new book for an author and retrieves all the books written by that author:
const Book = AV.Object.extend('Book');
const book = new Book();
const author = AV.User.current();
book.set('title', 'My Fifth Book');
book.set('author', author);
book.save().then((book) => {
// Find all the books by the same author
const query = new AV.Query('Book');
query.equalTo('author', author);
query.find().then((books) => {
// books is an array of Book objects by the same author
});
});
Security of User Objects
The AV.User class is secured by default. You are not able to invoke any save- or delete-related methods unless the AV.User was obtained using an authenticated method like logIn or signUp. This ensures that each user can only update their own data.
The reason behind this is that most data stored in AV.User can be very personal and sensitive, such as mobile phone number, social network account ID, etc. Even the app's owner should avoid tampering with these data for the sake of user's privacy.
The code below illustrates this security policy:
const user = AV.User.logIn('Tom', 'cat!@#123').then((user) => {
// Attempt to change username
user.set('username', 'Jerry');
// Password is hashed and an empty string will be returned
const password = user.get('password');
// Save changes
user.save().then((user) => {
// This will work since the user is authenticated
// Get the user with a non-authenticated method
const query = new AV.Query('_User');
query.get(user.objectId).then((unauthenticatedUser) => {
unauthenticatedUser.set('username', 'Toodle');
unauthenticatedUser.save().then((unauthenticatedUser) => { }, (error) => {
// This will cause error since the user is unauthenticated
});
});
});
});
The AV.User obtained from AV.User.current() will always be authenticated.
To check if AV.User is authenticated, you can invoke the isAuthenticated method. You do not need to check if AV.User is authenticated if it is obtained via an authenticated method.
As a reminder, the user's password can be set when signing up but cannot be modified and saved to the cloud afterward unless the user requests it to be reset. It will not be cached on the client and will show as null when being retrieved from the cloud after the user is logged in.
Security of Other Objects
For each given object, you can specify which users are allowed to read it and which are allowed to modify it. To support this type of security, each object has an access control list, implemented by the AV.ACL class. More details can be found in ACL Guide.
Linking Users
LeanCloud allows you to link your users with services like GitHub, Twitter, and Facebook (commonly known as social networking services, or SNS), allowing your users to sign up or log into your application using their existing identities. For example, to sign up or log in with a user's GitHub account, your code will look like this:
LeanCloud then verifies that the provided authData is valid and checks if a user is already associated with it. If so, it returns the status code 200 OK along with the details (including a sessionToken for the user).
If the authData is not linked to any account, you will instead receive the status code 201 Created, indicating that a new user has been created. The body of the response contains objectId, createdAt, sessionToken, and an automatically-generated unique username. For example:
{
"username": "k9mjnl7zq9mjbc7expspsxlls",
"objectId": "5b029266fb4ffe005d6c7c2e",
"createdAt": "2018-05-21T09:33:26.406Z",
"updatedAt": "2018-05-21T09:33:26.575Z",
"sessionToken": "…",
// authData won't be returned in most cases
// See explanations below
"authData": {
// …
}
// …
}
The authData field won't be returned to the client unless the current user owns it.
To ensure that each AV.User is linked to each service account only once, a unique index needs to be created for the authData.<SERVICE_NAME>.uid key in the _User class.
Authentication Data
authData is a JSON object with the names of services as keys and the details as values. You are responsible for completing the authentication flow (usually through OAuth 1.0 or 2.0) to obtain the details from the service provider which is required for linking.
A user who has GitHub linked may have the following object as authData:
LeanCloud automatically validates the access tokens for certain services to prevent data forge attack. When the validation fails, LeanCloud will respond with invalid authData error and the linking will not be established. For services that are not recognized by LeanCloud, you are responsible for validating access tokens by yourself. You can turn off the Validate access tokens when logging in with third-party accounts option in your app's Dashboard > LeanStorage > Settings if you prefer not to have LeanCloud validate access tokens for you.
Linking with Existing Users
An existing user can link their third-party accounts.
Once linked, the third-party account information will be added to the authData attribute of that user.
With the support of anonymous users, you can have your users try the application without signing up or logging in. The code below creates an anonymous user:
AV.User.loginAnonymously().then((user) => {
// user is the new anonymous user
});
You can add new properties or fields to an anonymous user just like with a normal user, such as username, password, email, etc. You can also convert an anonymous user to a normal user by going through the same sign-up process as you do with a normal user. An anonymous user can:
The code below sets a username and password for an anonymous user:
// currentUser is the anonymous user
const currentUser = AV.User.current();
user.setUsername('Tom');
user.setPassword('cat!@#123');
user.signUp().then((user) => {
// currentUser has become a normal user
}, (error) => {
// Sign-up failed (often because the username is already taken)
});
The code below checks if the current user is anonymous:
const currentUser = AV.User.current();
if (currentUser.isAnonymous()) {
// currentUser is anonymous
} else {
// currentUser is not anonymous
}
If an anonymous user is not converted to a normal user before they log out, they will not be able to log in to the same account later and the data stored in that account cannot be retrieved anymore.
Roles
As your app grows in scope and user-base, you may find yourself needing more coarse-grained control over access to pieces of your data than user-linked ACLs can provide. To address this requirement, LeanCloud supports a form of role-based access control. Check the detailed ACL Guide to learn how to set it up for your objects.
In-App Searching
In-App Searching offers a better way to search through the information contained within your apps. It's built with search engine capabilities that you can easily tap into your app. Effective and useful searching functionality in your app is crucial for helping users find what they need. For more details, see In-App Searching Guide.
In-App Socializing
In-app socializing offers features like following, timeline, status update, interaction, messaging, etc. For more details, see In-App Socializing Guide.
Push Notifications
You can send notifications to mobile devices in JavaScript SDK.
For example, to send push notifications to all devices subscribed to the public channel:
If you wish to debug WebView directly in your mobile devices, remote debugging needs to be configured for generating WebView. Refer to Google's official documentation for more details.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
yourWebView.setWebContentsDebuggingEnabled(true);
}
Keep in mind that this method is only supported on Android 4.4 and above.
When developing the UI of your app with WebView, Native makes use of Hybrid for the app running on your phone. We recommend that you first design the UI of your app with Chrome Developer Tools. After the UI is done, start working on data binding with Native during which you can debug WebView on your phone with Remote debugger. This will save you a lot of time on developing and debugging. If you choose to develop UI with Remote debugger as well, you will end up spending more time on it.
As a security practice to prevent JavaScript from calling Java functions for accessing the file system of an Android device, Android 4.2 and later versions only allow WebView to access the methods exposed by the annotation @JavascriptInterface. If you have users using Android 4.2 or above, make sure the annotation is added or Uncaught TypeError might be triggered.
LeanStorage JavaScript Guide
The LeanStorage JavaScript SDK can be used to persist and query data in LeanCloud. The code below shows how you can create an object and store it into the cloud:
The SDK designed for each language interacts with the same REST API via HTTPS, offering fully functional interfaces for you to manipulate the data in the cloud.
Installing SDK
See How to Install the JavaScript SDK.
Web Security
If you are using our JavaScript SDK in your browser-based application, you must configure whitelist domains in your app's Dashboard > Settings > Security before you deploy it to a production environment. Once set up, our backend will only accept requests sent from domains in the whitelist, which prevents unauthorized usage or access to your cloud data on a certain level.
However, since your app is exposed to the Internet, merely setting up whitelist domains can't guarantee the security of your app. LeanCloud offers a variety of ways to help you secure your apps as well as the data stored within them. The following articles give you the necessary information you need to know regarding app security.
Objects
AV.Object
Storing data on LeanCloud is built around
AV.Object
. EachAV.Object
contains key-value pairs of JSON-compatible data. This data is schema-free, which means that you don't need to specify ahead of time what keys exist on eachAV.Object
. Simply set whatever key-value pairs you want, and our backend will store it.For example, the
AV.Object
storing a simple todo item may contain the following data:Data Types
AV.Object
supports a wide range of data types to be used for each field, including common ones likeString
,Number
,Boolean
,Object
,Array
, andDate
. You can nest objects in JSON format to store more structured data within a singleObject
orArray
field.Special data types supported by
AV.Object
includePointer
andFile
, which are used to store a reference to anotherAV.Object
and binary data respectively.AV.Object
also supportsGeoPoint
, a special data type you can use to store location-based data. See GeoPoints for more details.Some examples:
We do not recommend storing large pieces of binary data like images or documents with
AV.Object
. The size of eachAV.Object
should not exceed 128 kilobytes. We recommend usingAV.File
for storing images, documents, and other types of files. To do so, createAV.File
objects and assign them to fields ofAV.Object
. See Files for details.Keep in mind that our backend stores dates in UTC format and the SDK will convert them to local times upon retrieval.
The date values displayed in your app's Dashboard > LeanStorage > Data are also converted to match your operating system's time zone. The only exception is that when you retrieve these date values through our REST API, they will remain in UTC format. You can manually convert them using appropriate time zones when necessary.
To learn about how you can protect the data stored on LeanCloud, see Data and Security.
Creating Objects
The code below creates a new instance of
AV.Object
with classTodo
:The constructor takes a class name as a parameter so that the cloud knows the class you are using to create the object. A class in LeanCloud is comparable to a table in a relational database. A class name starts with a letter and can only contain numbers, letters, and underscores.
If you keep seeing
Maximum call stack size exceeded
exception in your log, chances areAV.Object.extend
had been called using the same class name more than once within a loop or a callback. To resolve the issue, either move it out of the loop or the callback to make sure it won't be over-instantiated or switch to JavaScript SDK 1.4 or higher which handles the problem automatically.If you're using ES6 in your codebase, you can subclass
AV.Object
with theextends
keyword. However, when usingextends
, the SDK is not automatically aware of your subclasses. You will need to register the subclasses with the SDK:After doing this, you will be able to add additional methods and properties to your subclasses of
AV.Object
.Saving Objects
The following code saves a new object with class
Todo
to the cloud:To make sure the object is successfully saved, take a look at Dashboard > LeanStorage > Data >
Todo
in your app. You should see a new entry of data with something like this when you click on itsobjectId
:You don't have to create or set up a new class called
Todo
in Dashboard > LeanStorage > Data before running the code above. If the class doesn't exist, it will be automatically created.There are several built-in fields that are provided by default which you don't need to specify in your code:
objectId
String
ACL
AV.ACL
createdAt
Date
updatedAt
Date
Each of these fields is filled in by the cloud automatically and doesn't exist on the local
AV.Object
until a save operation has been completed.Field names, or keys, can only contain letters, numbers, and underscores. A custom key can neither start with double underscores
__
, nor be identical to any system reserved words or built-in field names (ACL
,className
,createdAt
,objectId
, andupdatedAt
) regardless of letter cases.Values can be strings, numbers, booleans, or even arrays and dictionaries — anything that can be JSON-encoded. See Data Types for more information.
We recommend that you adopt CamelCase naming convention to
NameYourClassesLikeThis
andnameYourKeysLikeThis
, which keeps your code more readable.Retrieving Objects
If
AV.Object
is already in the cloud, you can retrieve it using itsobjectId
with the following code:After retrieving an object, you can use the
get
method to acquire the data stored in its fields. Be aware thatobjectId
,updatedAt
, andcreatedAt
are 3 special properties that cannot be retrieved using theget
method or modified with theset
method. Each of these fields is filled in by the cloud only, so they don't exist onAV.Object
until a save operation has been completed.If you try to access a field or property that doesn't exist, the SDK will not raise an error. Instead, it will return
undefined
.Sometimes you may want to get all the fields back at one time without calling
get
on each field of the object. This could be helpful when you implement data binding. To do so, calltoJSON
onAV.Object
:Refreshing Objects
If you need to refresh a local object with the latest version of it in the cloud, call the
fetch
method on it:Keep in mind that any unsaved changes made to the object prior to calling
fetch
will be discarded. To avoid this, you have the option to provide a list of keys when calling the method so that only the fields being specified are retrieved and refreshed (including special built-in fields such asobjectId
,createdAt
, andupdatedAt
). Changes made to other fields will remain intact.Updating Objects
To update an existing object, assign the new data to each field and call the
save
method. For example:LeanCloud automatically figures out which data has changed and only the fields with changes will be sent to the cloud. The fields you didn't update will remain intact.
To view which attributes have unsaved changes, you can invoke the
dirtyKeys
method:To revert unsaved changes, you can invoke the
revert
method. Invokingrevert()
without passing any parameter will revoke all unsaved changes. To specify attributes to be revoked, pass an array as parameter:Updating Data Conditionally
By passing in a
query
option when saving, you can specify conditions on the save operation so that the object can be updated atomically only when those conditions are met. If no object matches the conditions, the cloud will return error305
to indicate that there was no update taking place.For example, in the class
Account
there is a field calledbalance
, and there are multiple incoming requests that want to modify this field. Since an account cannot have a negative balance, we can only allow a request to update the balance when the amount requested is lower than or equal to the balance:query
option only works for existing objects. In other words, it has no effect on objects that haven't been saved to the cloud yet.The benefit of using
query
option instead of combiningAV.Query
andAV.Object
shows up when you have multiple clients trying to update the same field at the same time. The latter way is more cumbersome and may lead to potential inconsistencies.Updating Counters
Take Twitter as an example, we need to keep track of how many Likes and Retweets a tweet has gained so far. Since a Like or Retweet action can be triggered simultaneously by multiple clients, saving objects with updated values directly can lead to inaccurate results. To make sure that the total number is stored correctly, LeanCloud allows you to atomically increase (or decrease) the value of a number field:
You can specify the amount of increment (or decrement) by providing an additional argument. If the argument is not provided,
1
is used by default.Updating Arrays
There are several operations that can be used to atomically update an array associated with a given key:
AV.Object.add('arrayKey', value)
appends the given object to the end of an array.
AV.Object.addUnique('arrayKey', value)
adds the given object into an array only if it is not in it. The object will be inserted at a random position.
AV.Object.remove('arrayKey', value)
removes all instances of the given object from an array.
For example,
Todo
has a field namedalarms
for keeping track of times at which a user wants to be alerted. The following code adds the times to the alarms field:Deleting Objects
The following code deletes a
Todo
object from the cloud:You can delete a given field of an object with the
unset
method:Removing data from the cloud should always be dealt with great caution as it may lead to non-recoverable data loss. We strongly advise that you read ACL Guide to understand the risks thoroughly. You should also consider implementing class-level, object-level, and field-level permissions for your classes in the cloud to guard against unauthorized data operations.
Batch Processing
You can create, save, delete, or fetch multiple objects within a single request:
The following code sets
isComplete
of allTodo
objects to betrue
:Although each function call sends multiple operations in one single network request, saving operations and fetching operations are billed as separate API calls for each object in the collection, while deleting operations are billed as a single API call.
Data Models
Objects may have relationships with other objects. For example, in a blogging application, a
Post
object may have relationships with manyComment
objects. LeanCloud supports three kinds of relationships, including one-to-one, one-to-many, and many-to-many.One-to-One and One-to-Many Relationships
One-to-one and one-to-many relationships are modeled by saving
AV.Object
as a value in the other object. For example, eachComment
in a blogging app might correspond to onePost
.The following code creates a new
Post
with a singleComment
:Internally, the backend will store the referred-to object with the
Pointer
type in just one place in order to maintain consistency. You can also link objects using theirobjectId
s like this:See Relational Queries for instructions on how to query relational data.
Many-to-Many Relationships
The easiest way to model many-to-many relationships is to use arrays. In most cases, using arrays helps you reduce the number of queries you need to make and leads to better performance. However, if additional properties need to be attached to the relationships between two classes, using join tables would be a better choice. Keep in mind that the additional properties are used to describe the relationships between classes rather than any single class.
We recommend you to use join tables if the total amount of objects of any class exceeds 100.
Modeling relationships among data could be a tough job. We have written a dedicated article to address Data Modeling in greater detail, which we highly recommend you to read.
Queries
We've already seen how you can retrieve a single object from the cloud with
AV.Object
, but it doesn't seem to be powerful enough when you need to retrieve multiple objects that match certain conditions at once. In such situation,AV.Query
would be a more efficient tool you can use.Basic Queries
The general steps of performing a basic query include:
AV.Query
.The code below retrieves all
Student
objects whoselastName
isSmith
:Query Constraints
There are several ways to put constraints on the objects found by
AV.Object
.The code below filters out objects with
Jack
asfirstName
:For sortable types like numbers and strings, you can use comparisons in queries:
You can apply multiple constraints to a single query, and objects will only be in the results if they match all of the constraints. In other words, it's like concatenating constraints with
AND
:You can limit the number of results by setting
limit
(defaults to100
):For performance reasons, the maximum value allowed for
limit
is1000
, meaning that LeanCloud would only return 1,000 results even it is set to be greater than1000
.If you need exactly one result, you may use
first
for convenience:You can skip certain number of results by setting
skip
:You can implement pagination in your app by using
skip
together withlimit
:Keep in mind that the higher the
skip
goes, the slower the query will run. You may consider usingcreatedAt
orupdatedAt
(which are indexed) to set range boundaries for large datasets to make queries more efficient.For sortable types, you can control the order in which results are returned:
You can even attach multiple sorting rules to a single query:
To retrieve objects that have or do not have particular fields:
You can use the
matchesKeyInQuery
method to look for objects with values of fields matching those of objects returned by another query.For example, if you have a
Country
class matching countries with languages and aStudent
class matching students with their nationalities:The following code looks for all the students who are from English-speaking countries:
You can restrict the fields returned by providing a list of keys with
select
. The code below retrieves todos with only thetitle
andcontent
fields (and also special built-in fields such asobjectId
,createdAt
, andupdatedAt
):You can add a minus prefix to the attribute name for inverted selection. For example, if you do not care about the post author, use
-author
. The inverted selection also applies to preserved attributes, and can be used with dot notations, e.g.-pubUser.createdAt
.The unselected fields can be fetched later with
fetch
. See Refreshing Objects.Queries on String Values
Use
startsWith
to restrict to string values that start with a particular string. Similar to aLIKE
operator in SQL, it is indexed so it is efficient for large datasets:Use
contains
to restrict to string values that contain a particular string:Unlike
startsWith
,contains
can't take advantage of indexes, which is not encouraged to be used for large datasets.Please note that both
startsWith
andcontains
perform case-sensitive matching, so the examples above will not look for string values containingLunch
,LUNCH
, etc.If you are looking for string values that do not contain a particular string, use
matches
with regular expressions:However, performing queries with regular expressions as constraints can be very expensive, especially for classes with over 100,000 records. The reason behind this is that queries like this can't take advantage of indexes and will lead to exhaustive scanning of the whole dataset to find the matching objects. We recommend that you take a look at our In-App Searching feature, a full-text search solution we provide to improve your app's searching ability and user experience.
If you are facing performance issues with queries, please refer to Optimizing Performance for possible workarounds and best practices.
Queries on Array Values
The code below looks for all the objects with
work
as an element of its array fieldtags
:To look for objects whose array field
tags
contains three elements:You can also look for objects whose array field
tags
containswork
,sales
, andappointment
:To retrieve objects whose field matches any one of the values in a given list, you can use
containedIn
instead of performing multiple queries. The code below constructs a query that retrieves todo items withpriority
to be1
or2
:Conversely, you can use
notContainedIn
if you want to retrieve objects that do not match any of the values in a list.Relational Queries
There are several ways to perform queries for relational data. To retrieve objects whose given field matches a particular
AV.Object
, you can useequalTo
just like how you use it for other data types. For example, if eachComment
has aPost
object in itspost
field, you can fetch all the comments for a particularPost
with the following code:To retrieve objects whose given field contains
AV.Object
that matches a different query, you can usematchesQuery
. The code below constructs a query that looks for all the comments for posts with images:To retrieve objects whose given field does not contain
AV.Object
that matches a different query, usedoesNotMatchQuery
instead.Sometimes you may need to look for related objects from different classes without extra queries. In such situations, you can use
include
on the same query. The following code retrieves the last 10 comments together with the posts related to them:You can even indicate multi-level associations using dot notations. If you wanted to include the post for each comment as well as the author of the post, you can do:
Feel free to use
include
as many times as you need for the same query to have multiple fields included. Related objects retrieved in this way also works withAV.Query
helpers likefirst
andget
.Multi-level conditions passed into
include
won't work with anyAV.Object
contained in an array field. The furthest level that can be reached is the field itself.You can also use dot notations with
select
to limit the fields returned from the related objects:Caveats about Inner Queries
The backend of LeanCloud is not built on relational databases, which makes it impossible to join tables while querying. For the relational queries mentioned above, what LeanCloud would do is to perform an inner query first (with
100
as defaultlimit
and1000
as maximum) and then insert the result from this query into the outer query. If the number of records matching the inner query exceeds thelimit
and the outer query contains other constraints, the amount of the records returned in the end could be zero or less than your expectation since only the records within thelimit
would be inserted into the outer query.The following actions can be taken to solve the problem:
1000
as thelimit
of the inner query.skip
values until all the records are gone through (performance issue could occur if the value ofskip
gets too big).Counting Objects
If you just need to count how many objects match a query but do not need to retrieve the actual objects, use
count
instead offind
. For example, to count how many todos have been completed:Compound Queries
Compound queries can be used if complex query conditions need to be specified. A compound query is a logical combination (
OR
orAND
) of subqueries.Note that we do not support
GeoPoint
or non-filtering constraints (e.g.near
,withinGeoBox
,limit
,skip
,ascending
,descending
,include
) in the subqueries of a compound query.OR-ed Query Constraints
An object will be returned as long as it fulfills any one of the subqueries. The code below constructs a query that looks for all the todos that either have priorities higher than or equal to
3
, or are already completed:Queries regarding
GeoPoint
cannot be present among OR-ed queries.AND-ed Query Constraints
The effect of using AND-ed query is the same as adding constraints to
AV.Query
. The code below constructs a query that looks for all the todos that are created between2016-11-13
and2016-12-02
:While using an AND-ed query by itself doesn't bring anything new comparing to a basic query, to combine two or more OR-ed queries, you have to use AND-ed queries:
Optimizing Performance
There are several factors that could lead to potential performance issues when you conduct a query, especially when more than 100,000 records are returned at a time. We are listing some common ones here so you can design your apps accordingly to avoid them:
count
with conditions (all the entries will be gone through)skip
for a large number of entries (all the entries that need to be skipped will be gone through)LiveQuery
LiveQuery is, as its name implies, derived from
AV.Query
but has enhanced capability. It allows you to automatically synchronize data changes from one client to other clients without writing complex code, making it suitable for apps that need real-time data.Suppose you are building an app that allows multiple users to edit the same file at the same time.
AV.Query
would not be an ideal tool since it is based on a pull model and you cannot know when to query from the cloud to get the updates.To solve this problem, we introduced LiveQuery. This tool allows you to subscribe to the
AV.Query
s you are interested in. Once subscribed, the cloud will notify clients by generating event messages wheneverAV.Object
s that match theAV.Query
are created or updated, in real-time.Behind the scene, we use WebSocket connections to have clients and the cloud communicate with each other and maintain the subscription status of clients. In most cases, it isn't necessary to deal with the WebSocket connections directly, so we developed a simple API to help you focus on your business logic rather than technical implementations.
Initializing LiveQuery
To use LiveQuery in your app, go to your app's Dashboard > LeanStorage > Settings and check the Enable LiveQuery option under the Other section, then include the following npm module in your project:
Or load it with the following
<script>
tag:See Installing SDK for more details.
Demo
We’ve made a demo app called “LeanTodo” which shows the functionality of LiveQuery. If you’d like to try it:
Creating a Subscription
To make a query live, create a normal
AV.Query
object, put conditions on it if there are, and then subscribe to it:You can't use subqueries or restrict fields being returned when using LiveQuery.
Now you will be able to receive updates related to
AV.Object
. If anTodo
object is created by another client withUpdate Portfolio
astitle
, the following code can get the newTodo
for you:If someone updates this
Todo
by changing itscontent
toAdd my recent paintings
, the following code can get the updated version for you:Event Handling
The following types of data changes can be monitored once a subscription is set up:
create
update
enter
leave
delete
create
EventA
create
event will be triggered when a newAV.Object
is created and it fulfills theAV.Query
you subscribed. Theobject
is the newAV.Object
being created:update
EventAn
update
event will be triggered when an existingAV.Object
fulfilling theAV.Query
you subscribed is updated. Theobject
is theAV.Object
being updated:enter
EventAn
enter
event will be triggered when an existingAV.Object
's old value does not fulfill theAV.Query
you subscribed but its new value does. Theobject
is theAV.Object
entering theAV.Query
and its content is the latest value of it:There is a clear distinction between a
create
event and anenter
event. If an object already exists and later matches the query's conditions, anenter
event will be triggered. If an object didn't exist already and is later created, acreate
event will be triggered.leave
EventA
leave
event will be triggered when an existingAV.Object
's old value fulfills theAV.Query
you subscribed but its new value does not. Theobject
is theAV.Object
leaving theAV.Query
and its content is the latest value of it:delete
EventA
delete
event will be triggered when an existingAV.Object
fulfilling theAV.Query
you subscribed is deleted. Theobject
is theobjectId
of theAV.Object
being deleted:Unsubscribing
You can cancel a subscription to stop receiving events regarding
AV.Query
. After that, you won't get any events from the subscription.Losing Connections
There are different scenarios regarding losing connections:
For the scenarios above, you don't need to do any extra work. As long as the user switches back to the app, the SDK will automatically re-establish the connection.
There is another scenario when the user completely kills the app or closes the web page. In this case, the SDK cannot automatically re-establish the connection. You will have to create subscriptions again by yourself.
Caveats about LiveQuery
Given the real-time feature of LiveQuery, developers may find it tempting to use it for instant messaging. As LiveQuery is neither designed nor optimized for completing such tasks, we discourage such use of this tool, let alone there will be an additional cost for saving message history and rising challenges of code maintenance. We recommend using our real-time messaging solution for this scenario.
Files
AV.File
allows you to store application files in the cloud that would otherwise be too large or cumbersome to fit into a regularAV.Object
. The most common use case is storing images, but you can also use it for documents, videos, music, and any other binary data.Creating Files
You can create a file from a base64-encoded string:
You can also create a file from an array of byte values:
What's more, you can also create a file from a Blob (in Browser), and Buffer or Stream (in Node.js).
For React Native applications, you can create a file from a local path:
You can also create a file from a URL:
When creating files from URLs, the SDK will not upload the actual files into the cloud but will store the addresses of the files as strings. This will not lead to actual traffic for uploading files, as opposed to creating files in other ways by doing which the files will be actually stored into the cloud.
LeanCloud will auto-detect the type of the file you are uploading based on the file extension, but you can also specify the
Content-Type
(commonly referred to as MIME type):But the most common method for creating files is to upload them from local paths. In a web app, you can first create an input button in the user interface:
Then get a reference to that file in a click handler:
The file we uploaded here is named
avatar.jpg
. There are a couple of things to note here:objectId
, so it is allowed for multiple files to share the same name.AV.File
, use.png
as its extension.application/octet-stream
.You can specify the path of the uploaded file via the
key
property. For example, use arobots.txt
to restrict search engines from crawling uploaded files:Saving Files
By saving a file, you store it into the cloud and get a permanent URL pointing to it:
A file successfully uploaded can be found in the
_File
class and cannot be modified later. If you need to change the file, you have to upload the modified file again and a newobjectId
and URL will be generated.To preserve original file name in the resulting url, pass the
keepFileName
parameter to thesave
method:You can associate a file with
AV.Object
after it is saved:You can also construct a
AV.Query
to query files:Note that the
url
field of internal files (files uploaded to LeanCloud file service) is dynamically generated by the cloud, which will switch custom domain names automatically. Therefore, querying files by theurl
field is only applicable to external files (files created by saving the external URL directly to the_File
table). Query internal files by thekey
field (path in URL) instead.On a related note, if the files are referenced in an array field of
AV.Object
and you want to get them within the same query forAV.Object
, you need to use theinclude
method withAV.Query
. For example, if you are retrieving all the todos with the same titleGet Cakes
and you want to retrieve their related attachments at the same time:Upload Progress
You can monitor the progress of uploading and display that to the user:
File Metadata
When uploading a file, you can attach additional properties to it with
metaData
. A file'smetaData
cannot be updated once the file is stored to the cloud.Deleting Files
The code below deletes a file from the cloud:
By default, a file is not allowed to be deleted. You can change the setting by going to Dashboard > LeanStorage > Data >
_File
and select Others > Permission settings >delete
.CDN Support
You can set up your own CDN to improve the speed your users access the files stored on LeanCloud. Take Amazon CloudFront CDN as an example:
AV.File
and fill it into CloudFront's Origin Domain Name. Leave the other settings with defaults.Promises
Each asynchronous method in LeanCloud JavaScript SDK returns a
Promise
which can be used to handle the completion and exception of the method. The example below updates anAV.Object
after it is being queried:The
then
MethodEach
Promise
has a method calledthen
which takes in two callbacks. The first callback is called when thePromise
isresolved
(runs successfully) while the second one is called when it isrejected
(gets error):The second callback is optional.
You may also implement your logic with
catch
:Chaining Promises Together
Promise allows you to connect asynchronous requests elegantly according to the order they should be called. If the callback of a
Promise
returns anotherPromise
, the callback in the secondthen
will not be resolved unless the one in the firstthen
is resolved. This is also called Promise Chain.Error Handling with Promises
If any single
Promise
in the chain throws an error, all the callbacks following it meant for successful operations will be skipped until an error handling callback is encountered.It is a common practice to attach an error handling function at the end of a
Promise
chain.The code above can be rewritten with
catch
in the following way:async
andawait
async
andawait
allows you to use Promise by writing code in a more synchronous manner:Further reading on Promises:
GeoPoints
LeanCloud allows you to associate real-world latitude and longitude coordinates with an object by adding
AV.GeoPoint
to theAV.Object
. By doing so, queries on the proximity of an object to a given point can be performed, allowing you to implement functions like looking for users or places nearby easily.To associate a point with an object, you need to create the point first. The code below creates
AV.GeoPoint
with39.9
aslatitude
and116.4
aslongitude
:Now you can store the point in an object as a regular field:
Geo Queries
With a number of existing objects with spatial coordinates, you can find out which of them are closest to a given point, or are contained within a particular area. This can be done by adding another restriction to
AV.Query
usingnear
. The code below returns a list ofTodo
objects withlocation
closest to a given point:Additional sorting conditions like
ascending
anddescending
will gain higher priorities than the default order by distance.To have the results limited within a certain distance, check out
withinKilometers
,withinMiles
, andwithinRadians
in our API docs.You can also query for the set of objects that are contained within a rectangular bounding box with
withinGeoBox
:Caveats about GeoPoints
There are a couple of things to keep in mind:
AV.Object
may only have one field containingAV.GeoPoint
object.-90.0
and90.0
. Longitude should be between-180.0
and180.0
. Attempting to set latitude or longitude out of bounds will cause an error.Users
At the core of many apps, there is a notion of user accounts that allows users to access their information in a secure manner. We provide a specialized user class called
AV.User
which automatically handles much of the functionality required for user account management in your app.AV.User
is a subclass ofAV.Object
. Therefore, all the methods that work forAV.Object
also work forAV.User
. The only difference is thatAV.User
has some additional features specific to user accounts. Each app has a dedicated_User
class for storingAV.User
.User Properties
AV.User
offers the following fields thatAV.Object
does not have:username
: The username of the user.password
: The password of the user.email
: The email address of the user.emailVerified
: Whether the user has verified the email address with LeanCloud or not.mobilePhoneNumber
: The mobile phone number of the user.mobilePhoneVerified
: Whether the user has verified the mobile phone number with LeanCloud or not.We'll go through each of these in detail as we run through the various use cases for users.
Signing up
When a user first opens your app, you may want them to sign up for an account. The following code shows a typical sign-up process with username and password:
A new
AV.User
should always be created usingsignUp
rather thansave
. Subsequent updates to a user can be done withsave
.If the code returns the error
202
, it means that a user with the sameusername
already exists in_User
table and the client should prompt the user to try a different username. It is also required that eachemail
ormobilePhoneNumber
appears only once in the corresponding column. Otherwise, error203
or214
will occur. You may ask a user to sign up with an email address and make theusername
to be the same as theemail
. By doing so, the user can directly reset their password with email.When creating a user with username and password, the SDK sends the password to the cloud in plaintext through HTTPS and the password will be hashed once it arrives to the cloud. (The cloud has no restrictions on password length and complexity.) We never store passwords in plaintext, nor will we ever transmit passwords back to the client in plaintext. Our hashing algorithm guarantees that the original password cannot be retrieved by rainbow table attack and even app developers themselves will not be able to see the password. Keep in mind that the password should not be hashed by the client, or the password reset function will not work.
Signing up with Phones
For a mobile app, it's also common to ask users to sign up with their phone numbers instead of usernames and passwords. There are two basic steps in it. First, ask the user to enter a phone number that can receive text messages. When the user clicks on the "Get Verification Code" button, call the following method to have a 6-digit verification code sent to the phone number the user just entered:
After the verification code is entered by the user, call the following method to finish signing up:
The
username
will be the same asmobilePhoneNumber
and apassword
will be generated by LeanCloud automatically.If you wish to let the user specify their own password, you can let them fill in a password together with their mobile phone number, and then follow the process of registering with username and password described in the previous section,
submitting the user's mobile phone number as the value of both the
username
and themobilePhoneNumber
fields.If you wish, you can also check the following options in "Dashboard > LeanStorage > Users > Settings": "Do not allow users with unverified phone numbers to log in", and/or "Allow users with verified phone numbers to login with SMS".
Phone Number Format
A phone number that
AV.User
accepts should have a leading plus sign (+
) immediately followed by the country code and the phone number without any dashes, spaces, or other non-numeric characters. For instance,+8618200008888
is a valid China number (86
is the country code) and+19490008888
is a valid US or Canada number (1
is the country code).For a list of countries and regions that LeanCloud can reach out through SMS, please refer to the Pricing page on our website.
Logging in
The code below logs a user in with username and password:
Logging in with Emails
The code below logs a user in with email and password:
Logging in with Phones
If you are allowing users to sign up with their phone numbers, you can also let them log in with either a password or a verification code sent via text message. The code below logs a user in with phone number and password:
By default, LeanCloud allows a user to log in to their account as long as the phone number and the password are correct even when the ownership of the phone hasn't been verified. To make your app more secure, you can choose to allow only those who have their phones verified to log in. The option can be found in Dashboard > LeanStorage > Settings.
You may also let a user in with a verification code sent to their phone, which is useful when the user forgets the password and does not want to reset it at the moment. Similar to the steps of signing a user up with phone numbers, ask the user to enter the phone number associated with the account, and call the following method once the user clicks on the "Get Verification Code" button:
After the verification code is entered by the user, call the following method to finish logging in:
Sandbox Phone Number
During the development of your application, you may need to test the sign-up or log-in related API intensively with your phone. As there are, however, limits to how quickly messages can be sent into the carrier networks, your testing pace can be significantly affected.
To work around it, you can set up a sandbox phone number in Dashboard > Messaging > SMS > Settings. LeanCloud will issue a fixed verification code to go with that sandbox phone number. Whenever LeanCloud detects such combination of data, the user will be let right in authenticated without any connections to the carrier networks being made.
On a related note, a sandbox phone number also comes in handy for iOS apps that allow users to log in with SMS code. This is because Apple may ask developers to provide a fixed combination of phone number and verification code for them to review the app as a normal user. Failure to do so may result in their app being rejected by the App Store.
For more details regarding the limitations of sending and receiving SMS messages, see SMS Guide.
Single Device Sign-on
In some scenarios you may want to restrict a user's account to be logged on by no more than one device at a time. That is, when a user logs in to the app on a new device, all the previous sessions on other devices will become invalid. Here's the instruction about how you can implement this feature with LeanCloud:
User Account Lockout
If the wrong password or verification code is entered for an account for more than 6 times within 15 minutes, the account will be disabled temporarily and the error
{ "code": 1, "error": "You have exceeded the maximum number of login attempts, please try again later, or consider resetting your password." }
will be returned.The account will be automatically recovered 15 minutes after the last attempt and the process cannot be expedited through SDK or REST API. While the account is disabled, the user cannot be logged in even though the correct credentials are provided. The restriction applies to both client-side SDKs and LeanEngine.
Verifying Emails
You can request that your users have their email addresses verified before they can log in or access certain functions in your app. This makes it harder for spam users to abuse your app. By default, each user has an
emailVerified
field which becomesfalse
when the user first signs up or has their email address changed. In your app's Dashboard > LeanStorage > Settings, you can enable Send verification emails when users register or change email addresses from clients so that when a user signs up or changes their email address, an email containing a verification link will be sent out automatically. You can find the option to prevent users with unverified email addresses from logging in on the same page.If a user forgets to click on the link and needs to have their account verified later, the following code can be used to send a new email:
The
emailVerified
will becometrue
after the link is clicked on. This field can never betrue
when theemail
field is empty.Verifying Phone Numbers
Similar to Verifying Emails, you can also request that your users have their phone numbers verified before they can log in or access certain functions in your app. By default, each user has a
mobilePhoneVerified
field which becomesfalse
when the user first signs up or has their phone number changed. In your app's Dashboard > LeanStorage > User > Setting, you can find the option to prevent users with unverified phone numbers from logging in on the same page.You can also initiate a verification request at anytime with the following code:
After the verification code is entered by the user, call the following method and the user's
mobilePhoneVerified
will becometrue
:Verify Phone Numbers Before Updating and Binding
LeanCloud also supports verifying the number before a user binds or updates a number.
Current User
After a user is logged in, LeanCloud SDK automatically stores the session information of this user in the client so that the user does not need to log in each time they open the client. The following code checks if there is a user logged in:
The session information of a user will remain in the client until the user is logged out:
Setting The Current User
A session token will be returned to the client after a user is logged in. It will be cached by our SDK and will be used for authenticating requests made by the same
AV.User
in the future. The session token will be included in the header of each HTTP request made from the client, which helps the cloud identify theAV.User
sending the request.Below are the situations when you may need to log a user in with session token:
AV.User.current().getSessionToken()
to get the session token of the current user).The code below logs a user in with session token (the session token will be validated before proceeding):
For security reasons, please avoid passing session tokens as a part of URLs in non-private environments. This increases the risk that they will be captured by attackers.
If Log out the user when password is updated is checked on in Dashboard > LeanStorage > Settings, the session token of a user will be reset in the cloud after this user changes the password and the client needs to prompt the user to log in again. Otherwise,
403 (Forbidden)
will be returned as an error.The code below checks if a session token is valid:
Resetting Passwords
It's quite common for the users of an app to forget their passwords. LeanCloud provides a number of ways for them to reset their passwords.
Here is the flow of resetting password with email:
To start with, ask the user to enter the email used for the account, and call the function below:
The code above will check if there is a user in the
_User
table that has theemail
to be the same as the one provided and will send them a password reset email if so. As mentioned previously, you can make theusername
of each user to be the same as theiremail
, or collect the email separately and store it in theemail
field.The content of the password reset email is fully customizable. You can go to your app's Dashboard > Settings > Email templates and modify the corresponding template.
Alternatively, you can ask for the mobile phone number instead of the email to reset their password:
The code below sends a verification code to a number:
The code above will check if there is a user in the
_User
table that has themobilePhoneNumber
to be the same as the one provided and will send them a verification code if so.By changing the settings in Dashboard > LeanStorage > Settings, you can restrict the use of the mobile phone number for the above flow only if the
mobilePhoneVerified
field istrue
.The code below resets the password of a user after they enter the verification code and a new password:
Queries on Users
To query for users, you can simple create a new
AV.Query
for_User
:For security reasons, the
_User
table of each new app has itsfind
permission disabled by default. Each user can only access their own data in_User
table and cannot access that of others. If you need to allow each user to view other users' data, we recommend that you create a new table to store such data and enable thefind
permission of this table. You may also encapsulate queries on users within LeanEngine and avoid opening upfind
permissions of_User
tables.See Security of User Objects for other restrictions applied to the
_User
table, and Data and Security for more information regarding class-level permission settings.Associations
Associations involving
AV.User
works in the same way as basicAV.Object
. The code below saves a new book for an author and retrieves all the books written by that author:Security of User Objects
The
AV.User
class is secured by default. You are not able to invoke any save- or delete-related methods unless theAV.User
was obtained using an authenticated method likelogIn
orsignUp
. This ensures that each user can only update their own data.The reason behind this is that most data stored in
AV.User
can be very personal and sensitive, such as mobile phone number, social network account ID, etc. Even the app's owner should avoid tampering with these data for the sake of user's privacy.The code below illustrates this security policy:
The
AV.User
obtained fromAV.User.current()
will always be authenticated.To check if
AV.User
is authenticated, you can invoke theisAuthenticated
method. You do not need to check ifAV.User
is authenticated if it is obtained via an authenticated method.As a reminder, the user's password can be set when signing up but cannot be modified and saved to the cloud afterward unless the user requests it to be reset. It will not be cached on the client and will show as
null
when being retrieved from the cloud after the user is logged in.Security of Other Objects
For each given object, you can specify which users are allowed to read it and which are allowed to modify it. To support this type of security, each object has an access control list, implemented by the
AV.ACL
class. More details can be found in ACL Guide.Linking Users
LeanCloud allows you to link your users with services like GitHub, Twitter, and Facebook (commonly known as social networking services, or SNS), allowing your users to sign up or log into your application using their existing identities. For example, to sign up or log in with a user's GitHub account, your code will look like this:
LeanCloud then verifies that the provided
authData
is valid and checks if a user is already associated with it. If so, it returns the status code200 OK
along with the details (including asessionToken
for the user).If the
authData
is not linked to any account, you will instead receive the status code201 Created
, indicating that a new user has been created. The body of the response containsobjectId
,createdAt
,sessionToken
, and an automatically-generated uniqueusername
. For example:The
authData
field won't be returned to the client unless the current user owns it.To ensure that each
AV.User
is linked to each service account only once, a unique index needs to be created for theauthData.<SERVICE_NAME>.uid
key in the_User
class.Authentication Data
authData
is a JSON object with the names of services as keys and the details as values. You are responsible for completing the authentication flow (usually through OAuth 1.0 or 2.0) to obtain the details from the service provider which is required for linking.A user who has GitHub linked may have the following object as
authData
:LeanCloud automatically validates the access tokens for certain services to prevent data forge attack. When the validation fails, LeanCloud will respond with
invalid authData
error and the linking will not be established. For services that are not recognized by LeanCloud, you are responsible for validating access tokens by yourself. You can turn off the Validate access tokens when logging in with third-party accounts option in your app's Dashboard > LeanStorage > Settings if you prefer not to have LeanCloud validate access tokens for you.Linking with Existing Users
An existing user can link their third-party accounts. Once linked, the third-party account information will be added to the
authData
attribute of that user.For example, to link a GitHub account:
Unlinking
Similarly, a third-party account can be unlinked.
For example, the code below unlinks a user's GitHub account:
Anonymous Users
With the support of anonymous users, you can have your users try the application without signing up or logging in. The code below creates an anonymous user:
You can add new properties or fields to an anonymous user just like with a normal user, such as
username
,password
,email
, etc. You can also convert an anonymous user to a normal user by going through the same sign-up process as you do with a normal user. An anonymous user can:The code below sets a username and password for an anonymous user:
The code below checks if the current user is anonymous:
If an anonymous user is not converted to a normal user before they log out, they will not be able to log in to the same account later and the data stored in that account cannot be retrieved anymore.
Roles
As your app grows in scope and user-base, you may find yourself needing more coarse-grained control over access to pieces of your data than user-linked ACLs can provide. To address this requirement, LeanCloud supports a form of role-based access control. Check the detailed ACL Guide to learn how to set it up for your objects.
In-App Searching
In-App Searching offers a better way to search through the information contained within your apps. It's built with search engine capabilities that you can easily tap into your app. Effective and useful searching functionality in your app is crucial for helping users find what they need. For more details, see In-App Searching Guide.
In-App Socializing
In-app socializing offers features like following, timeline, status update, interaction, messaging, etc. For more details, see In-App Socializing Guide.
Push Notifications
You can send notifications to mobile devices in JavaScript SDK.
For example, to send push notifications to all devices subscribed to the
public
channel:Here is another example sending a push notification to a specific user:
Please refer to the JavaScript SDK API Documentation and the Push Notifications Guide for more information.
WebView
LeanStorage JavaScript SDK can be used in a WebView like PhoneGap, Cordova, WeChat WebView, etc.
Android WebView
If you are using Android WebView, the following configurations need to be done when creating WebView in your Native code.
Our JS SDK uses
window.localStorage
which means thatlocalStorage
of WebView needs to be enabled:If you wish to debug WebView directly in your mobile devices, remote debugging needs to be configured for generating WebView. Refer to Google's official documentation for more details.
Keep in mind that this method is only supported on Android 4.4 and above.
When developing the UI of your app with WebView, Native makes use of Hybrid for the app running on your phone. We recommend that you first design the UI of your app with Chrome Developer Tools. After the UI is done, start working on data binding with Native during which you can debug WebView on your phone with Remote debugger. This will save you a lot of time on developing and debugging. If you choose to develop UI with Remote debugger as well, you will end up spending more time on it.
As a security practice to prevent JavaScript from calling Java functions for accessing the file system of an Android device, Android 4.2 and later versions only allow WebView to access the methods exposed by the annotation
@JavascriptInterface
. If you have users using Android 4.2 or above, make sure the annotation is added orUncaught TypeError
might be triggered.