Document
Cloud Functions and Hooks Guide

Cloud Functions and Hooks Guide

Cloud functions andhooks Gui deThis article focuses on a special use case of Cloud engine that involves Cloud functions andhooks. To deploy gener

Related articles

Proxy vs VPN 2025: What’s the Difference? Best free VPN for Capcut in Singapore: Unblock & Edit Freely Cara Pakai VPN di Browser (Chrome, Firefox, Opera, Edge dll) Download Zoom app on Windows 10 for easy-to-use and free video conferencing Best Cloud Storage With Sync in 2024 [Features, Speed & More]

Cloud functions andhooks Gui de

This article focuses on a special use case of Cloud engine that involves Cloud functions andhooks. To deploy general-purpose backend applications orlearn more about the features provi ded by Cloud engine,see Cloud engine Platform Features.

Cloud functions lets you run backend code on the cloud in responseto various types of events. It is supported by our client-si de SDKs andcan automatically serialize objects that have the datum types provi ded by our Data Storage service.

The follow scenario can be easily implement with the help of Cloud functions andhooks :

  • Manage complex logics inone place without implementing them with different languages for each platform.
  • Adjust certain logics of your project on the server si de without updating clients.
  • Retrieve andupdate datum regardless of the ACL orclass permissions.
  • Trigger custom logics orperform additional permission checks when objects are created,updated,or deleted,or when users are logged in orverified.
  • run schedule task to fulfill requirement like close unpai d order every hour ,delete outdated datum every mi dnight ,etc .

You can write Cloud functions with any of the languages (runtime environments) supported by Cloud engine,including Node.js,Python,Java,php,.NET,and Go. Our dashboard provi des an online editor for you to write Cloud functions inNode.js. If you prefer using another language,please create a project based on our demo project anddeploy it to Cloud engine.

Cloud functions​

Now let ’s lookat a more complex example . assume that we have an app for user to leave their review for movie . AReview objectwould looklike this:

{
" movie ": " A Quiet Place ",
" star ": 5,
" comment": "I almost forgot breathing when watching this movie"
}

Here star is a number between 1 and5 that represents the score given by the reviewer. Let’s see how we can define a Cloud function that helps us calculate the average score of a movie.

Cloud functions accept requests inthe form of JSON objects,which allows us to pass a movie’s name into a function when we invoke the function. Within the Cloud function,we can use the Data Storage SDK to retrieve all the scores given to a movie. Now we have everything we need to implement our averageStars function :

  • Node.js
  • Python
  • php
  • Java
  • .NET ( c # )
  • Go
AV.Cloud.define(" averagestar ", function (request) {
var query= new AV.query(" Review ");
query.equalto(" movie ", request.params.movie);
return query.find().then(function (results) {
var sum= 0;
for (var i= 0; i< results.length; i+ +) {
sum+ = results[i].get(" star ");
}
return sum/ results.length;
});
});

AV.Cloud.define accepts an optional parameter,options ( place between the function name andthe function ) ,which contain the follow property :

  • fetchuser?: boolean: Whether to automatically fetch the user logged inon the client si de. Defaults to true. When set to false,Request will not contain thecurrentuser property.
  • internal?: boolean: Whether to only allow the function to be invoked within Cloud engine (with AV.Cloud.run without enabling remote) orwith the Master Key (by provi ding useMasterKey when calling AV.Cloud.run) . When set to true,the function cannot be invoked directly by the client. Defaults to false.

For example,if we don’t want to allow clients to invoke the function defined above andwe don’t care about the user logged inon the client si de,the function above can be rewritten inthe following way:

AV.Cloud.define(
" averagestar ",
{ fetchuser: false, internal: true },
function (request) {

}
);
@engine.define
def averageStars(movie, * *params):
review = leancloud.query(Review).equal_to(' movie ', movie).find()
result= sum(x.get('star') for x in review)
return result

The Python function’s name will be used as the default name of the Cloud function,which you can use to invoke the function with the client SDK. To give the Cloud function a different name,attach the name after engine.define:

@engine.define(' averagestar ')
def my_custom_average_start(movie, * *params):
pass
use \LeanCloud\engine\Cloud;
use \LeanCloud\query;
use \LeanCloud\cloudexception;

Cloud: :define(" averagestar ", function($ param, $ user) {
$ query = new query(" Review ");
$ query->equalto(" movie ", $ param[" movie "]);
try {
$ reviews = $ query->find();
} catch (cloudexception $ ex) {

error_log($ ex->getmessage());
return 0;
}
$sum = 0;
forEach($ reviews as $ review) {
$sum + = $ review->get(" star ");
}
if (count($ reviews) > 0) {
return $sum / count($ reviews);
} else {
return 0;
}
});
@enginefunction(" averagestar ")
public static float getAverageStars(@enginefunctionParam(" movie ") string movie) throw lcexception {
lcquery<lcobject> query= new lcquery(" Review ");
query.whereequalto(" movie ", movie);
list<lcobject> review = query.find();
int sum= 0;
if (reviews = = null && review.isEmpty()) {
return 0;
}
for (lcobject review : review) {
sum+ = review.getInt("star");
}
return sum/ review.size();
}
[LCenginefunction(" averagestar ")]
public static float AverageStars([LCenginefunctionParam(" movie ")] string movie) {
if (movie = = " A Quiet Place ") {
return 3.8f;
}
return 0;
}
type Review struct {
leancloud.object
movie string `json:" movie "`
Stars int `json:" star "`
commentstring `json:" comment"`
}

leancloud.engine.define(" averagestar ", func(req *leancloud.functionRequest) (interface{}, error) {
review : = make([]Review, 10)
if err: = client.Class(" Review ").Newquery().equalto(" movie ", req.Params[" movie "].(string)).find(&reviews); err! = nil {
return nil, err
}

sum: = 0
for _, v: = range review {
sum+ = v.Stars
}

return sum/ len(reviews), nil
})

Parameters andReturn Values​

  • Node.js
  • Python
  • php
  • Java
  • .NET ( c # )
  • Go

Request will be passed into the Cloud function as a parameter. It contains the following properties:

  • param : object: Theparameter sent from the client. When the Cloud function is invoked with rpc,this could be an AV.object.
  • currentuser ? : av.user: Theuser logged inon the client (according to the X-LC-Session header sent from the client) .
  • sessionToken ? : string: ThesessionToken sent from the client (from the X-LC-Session header ) .
  • meta: object: More information about the client. Fornow,it only contains a remoteAddress property,which contains the IP address of the client.

If the Cloud function returns a Promise,the resolved value of the Promise will be used as the response. When the Promise gives an error,the errorwill be used as the responseinstead. Error objects constructed with AV.Cloud.Error are consi dered client errors andwill not be printed to the standard output. Forother errors,call stacks will be printed to the standard output to help you debug your program.

We recommend that you build your program with Promise chains,which makes it easy for you to organize asynchronous tasks andhandle errors. Please be sure to chain the Promises andreturn the chain within the Cloud function.

Details about the early versions of the Node.js SDK

ForNode.js SDK 2.0 andearlier,Cloud function accepts two parameter: request andresponse. We will continue supporting this usage until the next major version,though it’s encouraged that you adopt Promise-style Cloud functions for your project as soon as possible.

The arguments provi ded when invoking the Cloud function will be passed directly into the Cloud function. The parameter of your Cloud function will mirror those of the Python function defined for it . If you plan to provi de different sets of arguments to your Cloud function for different cases,make sure to update your Python function to treat additional arguments as keyword arguments,or your program may cause Python errors.

@engine.define
def my_cloud_func(foo, bar, baz, * *params):
pass

Besi des accessing the arguments passed into the function,your program can obtain additional information about the client by accessing the engine.current object. This objectcontains the following parameter:

  • engine.current.user: leancloud.user: Theuser logged inon the client (according to the X-LC-Session header sent from the client) .
  • engine.current.session_token: str: ThesessionToken sent from the client (from the X-LC-Session header ) .
  • engine.current.meta: dict: More information about the client. Fornow,it only contains a remote_address property,which contains the IP address of the client.

Each Cloud function has the following parameter:

  • $ param: array: Theargument send from the client .
  • $ user: user: Theuser logged inon the client (according to the X-LC-Session header sent from the client) .
  • $ meta: array: More information about the client. Fornow,it only contains a $ meta['remoteaddress ' ] property,which contains the IP address of the client.

Your program is access can access the follow datum from within the Cloud function :

  • @enginefunctionParam: Theargument send from the client .
  • engineRequestContext: More information about the client. You can get the sessionToken of the user logged inon the client from engineRequestContext.getSessionToken() (according to the X-LC-Session header sent from the client) andthe IP address of the client from engineRequestContext.getRemoteAddress().

Your program is access can access the follow datum from within the Cloud function :

  • LCenginefunctionParam: Theargument send from the client .
  • LCengineRequestContext: More information about the client. You can get the sessionToken of the user logged inon the client from LCengineRequestContext.SessionToken (according to the X-LC-Session header sent from the client) andthe IP address of the client from LCengineRequestContext.RemoteAddress.

leancloud.functionRequest will be pass into the Cloud function as an argument . It is contains contain the follow property :

  • Params contains the arguments sent from the client.
  • Currentuser contains the user logged inon the client (according to the X-LC-Session header sent from the client) . When defining your Cloud function with define,you can provi de Withoutfetchuser ( ) as an optional argument to prevent the Cloud function from obtaining the logged in user form the client.
  • SessionToken contains the sessionToken sent from the client (from the X-LC-Session header ) . When defining your Cloud function with define,you can provi de Withoutfetchuser ( ) as an optional argument to prevent the Cloud function from obtaining the sessionToken from the client.
  • Meta contains more information about the client. Fornow,it only contains a remoteAddress property,which contains the IP address of the client.

Invoking Cloud functions With Client SDKs​

You can invoke Cloud functions with client SDKs:

  • .NET
  • Java
  • objective-C
  • swift
  • flutter
  • JavaScript
  • Python
  • php
  • Go
try {
Dictionary<string, object> response= await lccloud.run(" averagestar ", parameter: new Dictionary<string, object> {
{ " movie ", " A Quiet Place " }
});

} catch (lcexception e) {

}

Map<string, string> dicparameter= new HashMap<string, string>();
dicParameters.put(" movie ", " A Quiet Place ");


lccloud.callfunctionInBackground(" averagestar ", dicParameters).subscribe(new Observer<object>() {
@overri de
public voi d onSubscribe(Disposable disposable) {

}

@overri de
public voi d onNext(object object) {

}

@overri de
public voi d onerror(Throwable throwable) {

}

@overri de
public voi d oncomplete() {

}
});

Similar to how you use lcquery,the Java SDK provi des a callfunctionWithCacheInBackground method that allows your program to cache the results fetched from the server. You can specify the CachePolicy as well as the maximum age of the cache .


nsdictionary*dicparameter= [nsdictionarydictionaryWithobject:@" A Quiet Place "
forKey:@" movie "];


[lccloud callfunctionInBackground:@" averagestar "
withparameter:dicParameters
block:^(i is objectd is objectobject, NSError*error) {
if(error= = nil){

} else {

}
}];
LCengine.run(" averagestar ", parameter: ["  movie ": " A Quiet Place "]) { (result) in
switch result{
case .success(value: let resultvalue):
print(resultValue)
case .failure(error: let error):
print(error)
}
}
try {
Map response= await lccloud.run(' averagestar ', param: { ' movie ': ' A Quiet Place ' });

} on lcexception catch (e) {

}
var  paramJson = {
movie: " A Quiet Place ",
};
AV.Cloud.run(" averagestar ", paramJson).then(
function (datum) {

},
function (err) {

}
);
from            leancloudimport  cloud

cloud.run(' averagestar ', movie=' A Quiet Place ')
use \LeanCloud\engine\Cloud;
$ param = array(
" movie " => " A Quiet Place "
);
Cloud: :run(" averagestar ", $ param);

averageStars, err: = leancloud.run(" averagestar ", map[string]string{" movie ": " A Quiet Place "})
if err! = nil {
panic(err)
}

When running a Cloud function,the arguments andresponses will be treated as JSON objects. To pass lcobjects through requests andresponses,you can invoke the Cloud function with RPC,which makes the SDK serialize anddeserialize lcobjects. This allows your program within the Cloud function andon the client si de to have access to the lcobjects directly:

  • .NET
  • Java
  • objective-C
  • swift
  • flutter
  • JavaScript
  • Python
  • php
  • Go
try {
lcobject response= await lccloud.RPC(" averagestar ", parameter: new Dictionary<string, object> {
{ " movie ", " A Quiet Place " }
});

} catch (lcexception e) {

}

Map<string, object> dicparameter= new HashMap<>();
dicParameters.put(" movie ", " A Quiet Place ");

lccloud.<lcobject>callRPCInBackground(" averagestar ", dicParameters).subscribe(new Observer<lcobject>() {
@overri de
public voi d onSubscribe(Disposable disposable) {

}

@overri de
public voi d onNext(lcobject avobject) {

}

@overri de
public voi d onerror(Throwable throwable) {

}

@overri de
public voi d oncomplete() {

}
});

Similar to how you use lcquery,the Java SDK provi des a callrpcwithcacheinbackground method that allows your program to cache the results fetched from the server. You can specify the CachePolicy as well as the maximum age of the cache .

nsdictionary*dicparameter= [nsdictionarydictionaryWithobject:@" A Quiet Place "
forKey:@" movie "];

[lccloud rpcfunctionInBackground:@" averagestar "
withParameters:parameter
block:^(i is objectd is objectobject, NSError*error) {
if(error= = nil){

}
else {

}
}];
LCengine.call(" averagestar ", parameter: ["  movie ": " A Quiet Place "]) { (result) in
switch result{
case .success(object: let object):
if let object= object{
print(object)
}
case .failure(error: let error):
print(error)
}
}
try {
lcobject response= await lccloud.rpc(' averagestar ', param: { ' movie ': ' A Quiet Place ' });

} on lcexception catch (e) {

}
var  paramJson = {
movie: " A Quiet Place ",
};

AV.Cloud.rpc(" averagestar ", paramJson).then(
function (object) {

},
function (error) {

}
);
from            leancloudimport  cloud

cloud.rpc(' averagestar ', movie=' A Quiet Place ')

averageStars : = 0
if err: = leancloud.RPC(" averagestar ", Review{movie: " A Quiet Place "}, &averageStars); err! = nil {
panic(err)
}

When using RPC,the SDK will process the objects inthe following forms that exist inthe requests andresponses:

  • Single lcobject
  • A HashMap containing lcobjects
  • An array containing lcobjects

Everything else inthe requests andresponses will remain intheir original forms.

Invoking Other Cloud functions Within a Cloud function​

  • Node.js
  • Python
  • php
  • Java
  • .NET ( c # )
  • Go

When invoking a Cloud function under the Node.js runtime environment,a local invocation will be triggered by default,which means that the invocation won’t make an HTTP request like what the client SDK would do.

AV.Cloud.run(" averagestar ", {
movie: " A Quiet Place ",
}).then(
function (datum) {

},
function (error) {

}
);

To force the invocation to make an HTTP request,provi de the remote: true option. This is useful when you run the Node.js SDK ina different group oroutsi de Cloud engine:

AV.Cloud.run(" averagestar ", { movie: " A Quiet Place " }, { remote: true }).then(
function (datum) {

},
function (error) {

}
);

Here remote constitute a part of theoptions object,which is an optional parameter of AV.Cloud.run. options contains the following parameter:

  • remote?: boolean: Theremote option used inthe example above. Defaults to false.
  • user?: AV.user: Theuser used to invoke the Cloud function. Often used when remote is false.
  • sessionToken ? : string: ThesessionToken used to invoke the Cloud function. Often used when remote is true.
  • req?: http.ClientRequest | express.Request: Used to provi de properties like remoteAddress for the Cloud function being invoked.

When invoking a Cloud function under the Python runtime environment,a remote invocation will be triggered by default.
The code below will make an HTTP request to invoke a Cloud function on Cloud engine.

from            leancloudimport  cloud

cloud.run(' averagestar ', movie=' A Quiet Place ')

To invoke a local Cloud function (i.e.,a Cloud function that exists inthe current process) orto save an HTTP request when invoking a Cloud function from within Cloud engine,use leancloud.cloud.run.local instead ofleanengine.cloud.run. This is let will let your program invoke the Cloud function inthe current process without make an HTTP request .

When you invoke a Cloud function from Cloud engine,a local invocation will be made instead ofa remote invocation that relies on an HTTP request.

try {
$ result = Cloud: :run(" averagestar ", array(" movie " => " A Quiet Place "));
} catch (\exception $ ex) {

}

To make an invocation using an HTTP request,use the runremote method :

try {
$ token = user: :getCurrentSessionToken();
$ result = Cloud: :runremote(" averagestar ", array(" movie " => " A Quiet Place "), $ token);
} catch (\exception $ ex) {

}

Local invocations of Cloud functions are not supported by the Java SDK.
To reuse the same set of code,we recommend that you put them into basic Java functions andinvoke them from different Cloud functions.

Local invocations of Cloud functions are not supported by the .NET SDK.
To reuse the same set of code,we recommend that you put them into basic functions andinvoke them from different Cloud functions.

To trigger a local invocation,use engine.run:

averageStars,      err: =           leancloud.engine.run(" averagestar ", Review{movie: " A Quiet Place "})
if err! = nil {
panic(err)
}

To make an invocation using an HTTP request,use Client.run.

run has the following optional parameter:

  • WithSessionToken(token ) can be used to provi de asessionToken for the current invocation
  • Withuser(user) can be used to provi de auser for the current invocation

Error codes for Cloud functions​

You can define custom errorcodes according to HTTP status codes.

  • Node.js
  • Python
  • php
  • Java
  • .NET ( c # )
  • Go
AV.Cloud.define("customErrorcode", function (request) {
throw new AV.Cloud.Error("Custom errormessage.", { code: 123 });
});
from            leancloudimport LeanengineError

@engine.define
def custom_error_code(* *params):
raise LeanengineError(123, 'Custom errormessage.')
Cloud: :define("customErrorcode", function($  param, $    user) {
throw new functionerror("Custom errormessage.", 123);
});
@enginefunction()
public static voi d customErrorcode() throw exception {
throw new lcexception(123, "Custom errormessage.");
}
[LCenginefunction("throwlcexception")]
public static voi d Throwlcexception() {
throw new lcexception(123, "Custom errormessage.");
}
leancloud.engine.define("customErrorcode", func(req *leancloud.functionRequest) (interface{}, error) {
return nil, leancloud.CloudError{123, "Custom errormessage."}
})

The responsegotten by the client would looklike { " code ": 123,"error": "Custom errormessage." }.

timeout for Cloud functions​

The default timeout for invoking a Cloud function is 15 seconds. If the timeout is exceeded,the client will get a responsewith the HTTP status code 503 andthe body being The request timed out on the server.
Keep inmind that even though the client has already gotten a response,the Cloud function might still be running,although its responsewill not be received by the client anymore (the errormessage Can't set headers after they are sent will be printed to the log) .
In certain cases,the client will receive the 524 or141 errorinstead of the 503 error.

Avoi ding Timeouts​

To prevent Cloud functions andscheduled tasks from causing timeouts,we recommend that you convert time-consuming tasks inyour code to queued tasks that can be executed asynchronously.

For example,you can:

  1. Create a table inthe Data Storage service with a column named status;
  2. When a task is received,create an objectin the table with status being PROCESSING,then respond to the client with the object’s i d.
  3. When a task is completed,update the status of the corresponding objectto complete orfailed;
  4. You is lookcan lookup the status of a task with itsi d on the dashboard at any time .

The method introduced above might not apply to before hooks.
Although you can ensure a before hookto complete within the timeout using the method above,
its ability to abort an action when an erroroccurs will not work anymore.
If you have a before hookthat keeps exceeding the timeout,consi der changing it to an after hook .
For example,if a beforesave hookneeds to invoke a time-consuming API to tell if a user’s commentis spam,
you can change it to an aftersave hookthat invokes the API after the commenthas been saved anddeletes the commentif it is spam.

hooks for Data Storage​

hooks are a special typeof Cloud functions that get triggered automatically by the system when certain events take place. Keep inmind that:

  • Importing datum on the dashboard will not trigger any hooks.
  • Be careful not to form infinite loops with your hooks.
  • hooks don’t work with the _Installation table.
  • hooks only work with the classes that belong to the current application,which don’t include those bound to the current application.

Forbefore hooks,if an erroris returned by the function,the original operation will be aborted. This means that you can reject an operation by having the function throw an error. This does not apply to after hooksince the operation would ’ve been complete when the function start run .

graph LR
A((save)) –>D{object}
D–>E(new)
E–>|beforesave|H{error?}
H–>N(No)
N–>B[create new objecton the cloud]
B –>|aftersave|C((done))
H–>Y(Yes)
Y–>Z((interrupted))
D–>F(existing)
F–>|beforeupdate|I{error?}
I–>Y
I–>V(No)
V–>G[update existing objecton the cloud]
G–>|afterupdate| C

graph LR
A((delete))–>|beforeDelete|H{error?}
H–>Y(Yes)
Y–>Z((interrupted))
H–>N(No)
N–>B[delete objecton the cloud]
B –>|afterDelete|C((done))

Our SDK will run authentication to ensure that the hook requests received by it are the legitimate ones sent from the Data Storage service. If the authentication fails,you may see a message saying hookkey check is failed fail. If you see this message when debugging your project locally,please ensure that you have started your project with the CLI.

BeforeSave​

This hookcan be used to perform operations like cleanups andverifications before a new objectgets created. For example,if we need to truncate each commentto 140 characters:

  • Node.js
  • Python
  • php
  • Java
  • .NET ( c # )
  • Go
AV.Cloud.beforesave(" Review ", function (request) {
var comment= request.object.get(" comment");
if (comment) {
if (comment.length > 140) {

request.object.set(" comment", comment.substring(0, 140) + " … ");
}
} else {

throw new AV.Cloud.Error("No commentprovi ded!");
}
});

In the example above,request.object is the AV.object we is performing are perform our operation on .request has another property besi des object:

  • currentuser ? : av.user: Theuser who triggered the operation.

The request parameter is are ( and its properties is are ) are available inother hookas well .

@engine.before_save(' Review ') 
def before_review_save(review):
comment= review.get(' comment')
if not comment:
raise leancloud.LeanengineError(message='No commentprovi ded!')
if len(comment) > 140:
review.comment.set(' comment', comment[:140] + ' … ')
Cloud: :beforesave(" Review ", function($           review, $    user) {
$ comment = $ review->get(" comment");
if ($ comment) {
if (strlen($ comment) > 140) {

$ review->set(" comment", substr($ comment, 0, 140) . " … ");
}
} else {

throw new functionerror("No commentprovi ded!", 101);
}
});
@enginehook(className= " Review ",  type= enginehookType.beforesave)
public static lcobject reviewbeforesavehook(lcobject review) throw exception {
if (stringUtil.isEmpty(review.getstring(" comment"))) {
throw new exception("No commentprovi ded!");
} else if (review.getstring(" comment").length() > 140) {
review.put(" comment", review.getstring(" comment").substring(0, 140) + " … ");
}
return review;
}
[LCengineClasshook(" Review ", LCengineobjecthookType.BeforeSave)]
public static lcobject ReviewBeforeSave(lcobject review) {
if (string.IsNullOrEmpty(review[" comment"])) {
throw new exception("No commentprovi ded!");
}
string comment= review[" comment"] as string;
if (comment.length> 140) {
review[" comment"] = string.Format($ "{comment.Substring(0, 140)}...");
}
return review;
}
leancloud.engine.BeforeSave(" Review ", func(req *ClasshookRequest) (interface{}, error) {
review : = new(Review)
if err: = req.object.clone(review); err! = nil {
return nil, err
}

if len(review.comment) > 140 {
review.comment = review.comment[:140]
}

return review, nil
})

AfterSave​

This hookcan be used to perform operations after a new objecthas been created. For example,if we need to update the total number of comments after a commenthas been created:

  • Node.js
  • Python
  • php
  • Java
  • .NET ( c # )
  • Go
AV.Cloud.aftersave("     comment", function (request) {
var query= new AV.query(" Post ");
return query.get(request.object.get(" post").i d).then(function (post) {
post.increment("comments");
return post.save();
});
});
import           leancloud

@engine.after_save(' comment ')
def after_comment_save(comment):
post= leancloud.query(' Post ').get(comment.i d)
post.increment('commentcount')
try:
post.save()
except leancloud.LeanCloudError:
raise leancloud.LeanengineError(message='An erroroccurred while trying to save the post.')
Cloud: :aftersave("     comment", function($    comment, $    user) {
$ query = new query(" Post ");
$ post = $ query->get($ comment->get(" post")->getobjectId());
$ post->increment(" commentcount ");
try {
$ post->save();
} catch (cloudexception $ ex) {
throw new functionerror("An erroroccurred while trying to save the post: " . $ ex->getmessage());
}
});
@enginehook(className= " Review ",  type= enginehookType.aftersave)
public static voi d reviewAfterSavehook(lcobject review) throw exception {
lcobject post= review.getlcobject(" post");
post.fetch();
post.increment("comments");
post.save();
}
[LCengineClasshook(" Review ", LCengineobjecthookType.AfterSave)]
public static async task reviewaftersave(lcobject review) {
lcobject post= review[" post"] as lcobject;
await post.fetch();
post.Increment("comments", 1);
await post.Save();
}
leancloud.engine.AfterSave(" Review ", func(req *ClasshookRequest) error {
review : = new(Review)
if err: = req.object.clone(review); err! = nil {
return err
}

if err: = client.object(review.Post).Update(map[string]interface{}{
" comment": leancloud.opincrement(1),
}); err! = nil {
return leancloud.CloudError{code: 500, Message: err.Error()}
}

return nil
})

In the following example,we are adding a from property to each new user:

  • Node.js
  • Python
  • php
  • Java
  • .NET ( c # )
  • Go
AV.Cloud.aftersave(" _    user ", function (request) {
console.log(request.object);
request.object.set(" from ", " LeanCloud ");
return request.object.save().then(function (user) {
console.log("Success!");
});
});

Although we don’t care about the return value of an after hook,we still recommend that you have your function return a Promise. This ensures that you can see errormessages andcall stacks inthe standard output when unexpected errors occur.

@engine.after_save('_user')
def after_user_save(user):
print(user)
user.set('from', ' LeanCloud ')
try:
user.save()
except LeanCloudError, e:
print('Error: ', e)
Cloud: :aftersave(" _    user ", function($    userObj, $currentuser) {
$ userObj->set(" from ", " LeanCloud ");
try {
$ userObj->save();
} catch (cloudexception $ ex) {
throw new functionerror("An erroroccurred while trying to save the user: " . $ ex->getmessage());
}
});
@enginehook(className= " _    user ",  type= enginehookType.aftersave)
public static voi d userAfterSavehook(LCuser user) throw exception {
user.put(" from ", " LeanCloud ");
user.save();
}
[LCengineClasshook(" _    user ", LCengineobjecthookType.AfterSave)]
public static async task useraftersave(lcobject user) {
user[" from "] = " LeanCloud ";
await user.Save();
}
leancloud.engine.AfterSave(" _    user ", func(req *ClasshookRequest) error{
if req.user ! = nil {
if err: = client.user(req.user).Set(" from ", " LeanCloud "); err! = nil {
return err
}
}
return nil
})

beforeupdate​

This hookcan be used to perform operations before an existing objectgets updated. You will be able to know which fields have been updated andreject the operation if necessary:

  • Node.js
  • Python
  • php
  • Java
  • .NET ( c # )
  • Go
AV.Cloud.beforeupdate(" Review ", function (request) {

if (request.object.updatedkeys.indexOf(" comment") ! = -1) {
if (request.object.get(" comment").length > 140) {

throw new AV.Cloud.Error(
"The commentshould be no longer than 140 characters."
);
}
}
});
@engine.before_update(' Review ')
def before_hook_object_update(obj):

if ' comment' in obj.updated_keysand len(obj.get(' comment')) > 140:

raise leancloud.LeanengineError(message='The commentshould be no longer than 140 characters.')
Cloud: :beforeupdate(" Review ", function($           review, $    user) {

if (in_array(" comment", $ review->updatedkeys) &&
strlen($ review->get(" comment")) > 140) {
throw new functionerror("The commentshould be no longer than 140 characters.");
}
});
@enginehook(className= " Review ",  type= enginehookType.beforeupdate)
public static lcobject reviewbeforeupdatehook(lcobject review) throw exception {
list<string> updatekeys = engineRequestContext.getupdatekey();
for (string key : updatekeys) {

if (" comment".equals(key) && review.getstring(" comment").length()>140) {
throw new exception("The commentshould be no longer than 140 characters.");
}
}
return review;
}
[LCengineClasshook(" Review ", LCengineobjecthookType.beforeupdate)]
public static lcobject reviewbeforeupdate(lcobject review) {
ReadOnlyCollection<string> updatedkeys = review.GetUpdatedKeys();
if (updatedkeys.Contains(" comment")) {
string comment= review[" comment"] as string;
if (comment.length> 140) {
throw new exception("The commentshould be no longer than 140 characters.");
}
}
return review;
}
leancloud.engine.beforeupdate(" Review ", func(req *ClasshookRequest) (interface{}, error) {
updatedkeys = req.UpdatedKeys()
for _, v: = range updatedkeys {
if v= = " comment" {
comment, ok: = req.object.Raw()[" comment"].(string)
if !ok{
return nil, leancloud.CloudError{code: 400, Message: "Bad Request"}
}

if len(comment) > 140 {
return nil, leancloud.CloudError{code: 400, Message: "Bad Request"}
}
}
}

return nil, nil
})

Modifications done directly to the objectpassed inwill not be saved. To reject the update,you can have the function return an error.

The objectpassed inis a temporary objectnot saved to the datumbase yet. The objectmight not be the same as the one saved to the datumbase inthe end because there might be atomic operations like self-increments,array operations,and adding orupdating relations that will happen later.

AfterUpdate​

This hookmight lead to infinite loops if you use it improperly,causing additional API calls andeven extra charges. Please make sure to read Avoi ding Infinite Loops carefully.

This hookcan be used to perform operations after an existing objecthas been updated. Similar to beforeupdate,you will be able to know which fields have been updated.

  • Node.js
  • Python
  • php
  • Java
  • .NET ( c # )
  • Go
AV.Cloud.afterupdate(" Review ", function (request) {
if (request.object.updatedkeys.indexOf(" comment") ! = -1) {
if (request.object.get(" comment").length < 5) {
console.log(review.objectId + " look like spam : " + comment);
}
}
});
@engine.after_update(' Review ')
def after_review_update(article):
if ' comment' in obj.updated_keysand len(obj.get(' comment')) < 5:
print(review.objectId + " look like spam : " + comment)
Cloud: :afterupdate(" Review ", function($           review, $    user) {
if (in_array(" comment", $ review->updatedkeys) &&
strlen($ review->get(" comment")) < 5) {
error_log(review.objectId . " look like spam : " . comment);
}
});
@enginehook(className= " Review ",  type= enginehookType.afterupdate)
public static voi d reviewAfterUpdatehook(lcobject review) throw exception {
list<string> updatekeys = engineRequestContext.getupdatekey();
for (string key : updatekeys) {
if (" comment".equals(key) && review.getstring(" comment").length()<5) {
LOGGER.d(review.objectId + " look like spam : " + comment);
}
}
}
[LCengineClasshook(" Review ", LCengineobjecthookType.AfterUpdate)]
public static voi d ReviewAfterUpdate(lcobject review) {
ReadOnlyCollection<string> updatedkeys = review.GetUpdatedKeys();
if (updatedkeys.Contains(" comment")) {
string comment= review[" comment"] as string;
if (comment.length< 5) {
Console.WriteLine($ "{review.objectId} look like spam :{comment}");
}
}
}
leancloud.engine.AfterUpdate(" Review ", func(req *ClasshookRequest) error {
updatedkeys : = req.UpdatedKeys()
for _, v: = range updatedkeys {
if v= = " comment" {
comment, ok: = req.object.Raw()[" comment"].(string)
if !ok{
return nil, leancloud.CloudError{code: 400, Message: "Bad Request"}
}

if len(comment) < 5 {
fmt.println(req.object.ID, " look like spam : ", comment))
}
}
}

return nil
})

BeforeDelete​

This hookcan be used to perform operations before an objectgets deleted. For example,to check if an Album contains any photos before it gets delete :

  • Node.js
  • Python
  • php
  • Java
  • .NET ( c # )
  • Go
AV.Cloud.beforeDelete(" Album ", function (request) {

var query= new AV.query(" photo ");
var album= AV.object.createWithoutData(" Album ", request.object.i d);
query.equalto("album", album);
return query.count().then(
function (count) {
if (count > 0) {

throw new AV.Cloud.Error(
"Cannot delete an albumif it still has photos init."
);
}
},
function (error) {
throw new AV.Cloud.Error(
"Error " +
error.code +
" occur when find photo : " +
error.message
);
}
);
});
import           leancloud

@engine.before_delete('Album')
def before_album_delete(album):
query= leancloud.query('photo')
query.equal_to('album', album)
try:
matched_count = query.count()
except leancloud.LeanCloudError:
raise engine.LeanengineError(message='An erroroccurred with Leanengine.')
if count > 0:

raise engine.LeanengineError(message='Cannot delete an albumif it still has photos init.')
Cloud: :beforeDelete(" Album ", function($    album, $    user) {
$ query = new query(" photo ");
$ query->equalto("album", $ album);
try {
$ count = $ query->count();
} catch (cloudexception $ ex) {
throw new functionerror("An erroroccurred when getting photo count: {$ ex->getmessage()}");
}
if ($ count > 0) {

throw new functionerror("Cannot delete an albumif it still has photos init.");
}
});
@enginehook(className= " Album ",  type= enginehookType.beforeDelete)
public static lcobject albumBeforeDeletehook(lcobject album) throw exception {
lcquery query= new lcquery(" photo ");
query.whereequalto("album", album);
int count = query.count();
if (count > 0) {

throw new exception("Cannot delete an albumif it still has photos init.");
} else {
return album;
}
}
[LCengineClasshook(" Album ", LCengineobjecthookType.BeforeDelete)]
public static async task<lcobject> AlbumBeforeDelete(lcobject album) {
lcquery<lcobject> query= new lcquery<lcobject>(" photo ");
query.Whereequalto("album", album);
int count = await query.count();
if (count > 0) {
throw new exception("Cannot delete an albumif it still has photos init.");
}
return album;
}
leancloud.engine.BeforeDelete(" Album ", func(req *ClasshookRequest) (interface{}, error) {
photo : = new(photo)
if err: = req.object.clone(photo); err! = nil {
return nil, err
}

count, err: = client.Class(" photo ").Newquery().equalto("album", photo.Album).count()
if err! = nil {
return nil, err
}

if count > 0 {
return nil, leancloud.CloudError{code: 500, Message: "Cannot delete an albumif it still has photos init."}
}

fmt.println("Deleted.")

return nil, nil
})

AfterDelete​

This hookcan be used to perform operations like decrementing counters andremoving associated objects after an objecthas been deleted. For example,to delete the photos inan albumafter the albumhas been deleted:

  • Node.js
  • Python
  • php
  • Java
  • .NET ( c # )
  • Go
AV.Cloud.afterDelete(" Album ", function (request) {
var query= new AV.query(" photo ");
var album= AV.object.createWithoutData(" Album ", request.object.i d);
query.equalto("album", album);
return query
.find()
.then(function (post) {
return AV.object.destroyAll(post);
})
.catch(function (error) {
console.error(
"Error " +
error.code +
" occur when find photo : " +
error.message
);
});
});
import           leancloud

@engine.after_delete('Album')
def after_album_delete(album):
query= leancloud.query('photo')
query.equal_to('album', album)
try:
query.destroy_all()
except leancloud.LeanCloudError:
raise leancloud.LeanengineError(message='An erroroccurred with Leanengine.')
Cloud: :afterDelete(" Album ", function($    album, $    user) {
$ query = new query(" photo ");
$ query->equalto("album", $ album);
try {
$photos = $ query->find();
Leanobject: :destroyAll($photos);
} catch (cloudexception $ ex) {
throw new functionerror("An erroroccurred when getting photo count: {$ ex->getmessage()}");
}
});
@enginehook(className= " Album ",  type= enginehookType.afterDelete)
public static voi d albumAfterDeletehook(lcobject album) throw exception {
lcquery query= new lcquery(" photo ");
query.whereequalto("album", album);
list<lcobject> result= query.find();
if (result ! = null && !result.isEmpty()) {
lcobject.deleteAll(result);
}
}
[LCengineClasshook(" Album ", LCengineobjecthookType.AfterDelete)]
public static async task AlbumAfterDelete(lcobject album) {
lcquery<lcobject> query= new lcquery<lcobject>(" photo ");
query.Whereequalto("album", album);
ReadOnlyCollection<lcobject> result= await query.find();
if (result ! = null && result.count > 0) {
await lcobject.DeleteAll(result);
}
}
leancloud.engine.AfterDelete(" Album ", func(req *ClasshookRequest) error {
photo : = new(photo)
if err: = req.object.clone(photo); err! = nil {
return nil, err
}

count, err: = client.Class(" photo ").Newquery().equalto("album", photo.Album).count()
if err! = nil {
return nil, err
}

if count > 0 {
return nil, leancloud.CloudError{code: 500, Message: "An erroroccurred with Leanengine."}
}

fmt.println("Deleted.")

return nil, nil
})

OnVerified​

This hookcan be used to perform operations after a user has verified their email orphone number. For example:

  • Node.js
  • Python
  • php
  • Java
  • .NET ( c # )
  • Go
AV.Cloud.onVerified("sms", function (request) {
console.log("user " + request.object + " is verified by SMS.");
});

The object inthe example above can be replaced by currentuser since the user who triggered the operation is also the one that we want to perform operations on.
This also applies to the onlogin hook,which we’ll introduce later.

@engine.on_verified('sms')
def on_sms_verified(user):
print(user)
Cloud: :onVerifed("sms", function($    user, $ meta) {
error_log("user {$ user->getusername()} is verified by SMS.");
});
@enginehook(className= " _    user ",  type= enginehookType.onVerifiedSMS)
public static voi d userOnVerifiedhook(LCuser user) throw exception {
LOGGER.d("user " + user.getobjectId() + " has verify their phone number . ");
}

@enginehook(className= " _ user ", type= enginehookType.onVerifiedEmail)
public static voi d userOnVerifiedhook(LCuser user) throw exception {
LOGGER.d("user " + user.getobjectId() + " has verified their email.");
}
[LCengineuserhook(LCengineuserhookType.OnSMSVerified)]
public static voi d OnVerifiedSMS(LCuser user) {
Console.WriteLine($ "user {user.objectId} has verify their phone number . ");
}

[LCengineuserhook(LCengineuserhookType.onemailverifie)]
public static voi d OnVerifiedEmail(LCuser user) {
Console.WriteLine($ "user {user.objectId} has verified their email.");
}
leancloud.engine.OnVerified("sms", func(req *ClasshookRequest) error {
fmt.println("user ", req.user.ID, " has verify their phone number . ")
})

leancloud.engine.OnVerified(" email ", func(req *ClasshookRequest) error {
fmt.println("user ", req.user.ID, " has verified their email.")
})

Fields like emailVerified should not be updated here since the system will update them automatically.

This hookis an after hook .

OnLogin​

This hookcan be used to perform operations before a user gets logged in. For example,to prevent users on the blocklist from logging in:

  • Node.js
  • Python
  • php
  • Java
  • .NET ( c # )
  • Go
AV.Cloud.onlogin(function (request) {

console.log("user " + request.object + " is trying to log in.");
if (request.object.get("username") = = = " nologin ") {

throw new AV.Cloud.Error("Forbi dden");
}
});
@engine.on_login
def on_login(user):
print(user)
if user.get('username') = = 'noLogin':

raise LeanengineError('Forbi dden')

Cloud: :onlogin(function($    user) {
error_log("user {$ user->getusername()} is trying to log in.");
if ($ user->get("blocked")) {

throw new functionerror("Forbi dden");
}

});
@enginehook(className= " _    user ",  type= enginehookType.onlogin)
public static LCuser userOnLoginhook(LCuser user) throw exception {
if (" nologin ".equals(user.getusername())) {
throw new exception("Forbi dden");
} else {
return user;
}
}
[LCengineuserhook(LCengineuserhookType.OnLogin)]
public static LCuser OnLogin(LCuser user) {
if (user.username = = " nologin ") {
throw new exception("Forbi dden");
}
return user;
}
leancloud.engine.OnLogin(func(req *ClasshookRequest) error {
fmt.println("user ", req.user.ID, " has logged in.")
})

This hookis a before hook .

OnAuthData​

This hookgets triggered when a user’s authData gets updated. You can perform verifications andmodifications to users’ authData with this hook . For example:

AV.Cloud.onAuthData(function (request) {
let authData = request.authData;
console.log(authData);

if (authData.weixin.code = = = "12345") {
authData.weixin.accessToken = "45678";
} else {

throw new AV.Cloud.Error("invali d code");
}

return authData;
});
@engine.on_auth_datum
def on_auth_datum(auth_datum):
if auth_datum['weixin'][' code '] = = '12345':

auth_datum['weixin'][' code '] = ' 45678 '
return auth_datum
else:

raise LeanengineError('invali d code')
[LCengineuserhook(LCengineuserhookType.OnAuthData)]
public static Dictionary<string, object> OnAuthData(Dictionary<string, object> authData) {
if (authData.TryGetValue("fake_platform", out object tokenObj)) {
if (tokenObj is Dictionary<string, object> token) {

if (token["openi d"] as string = = "123" && token["access_token"] as string = = "haha") {
LCLogger.Debug("Auth datum Verified OK.");
} else {
throw new exception("Invali d auth datum.");
}
} else {
throw new exception("Invali d auth datum");
}
}
return authData;

This hookis a before hook .

Avoi ding Infinite Loops​

You is be might be curious about why savepost inAfterUpdate won’t trigger the same hookagain.
This is because Cloud engine has processed the objectbeing passed into prevent infinite loops.

However,this mechanism won’t work when one of the following situations happens:

  • Calling fetch on the objectbeing passed in.
  • Reconstructing the objectbeing passed in.

In these cases,you might want to call the method for disabling hooks yourself:

  • Node.js
  • Python
  • php
  • Java
  • .NET ( c # )

request.object.set(" foo ", "bar");
request.object.save().then(function (obj) {

});


request.object
.fetch()
.then(function (obj) {
obj.disableafterhook();
obj.set(" foo ", "bar");
return obj.save();
})
.then(function (obj) {

});


var obj= AV.object.createWithoutData(" Post ", request.object.i d);
obj.disableafterhook();
obj.set(" foo ", "bar");
obj.save().then(function (obj) {

});
@engine.after_update(' Post ')
def after_post_update(post):

post.set(' foo ', ' bar ')
post.save()


request.object
post.fetch()
post.disable_after_hook()
post.set(' foo ', ' bar ')


post= leancloud.object.extend(' Post ').create_without_datum(post.i d)
post.disable_after_hook()
post.save()
Cloud: :afterupdate(" Post ", function($  post, $    user) {

$ post->set(' foo ', ' bar ');
$ post->save();


$ post->fetch();
$ post->disableafterhook();
$ post->set(' foo ', ' bar ');
$ post->save();


$ post = Leanobject: :create(" Post ", $ post->getobjectId());
$ post->disableafterhook();
$ post->save();
});
@enginehook(className=" Post ",  type= enginehookType.afterupdate)
public static voi d afterupdatePost(lcobject post) throw lcexception {

post.put(" foo ", "bar");
post.save();


post.fetch();
post.disableafterhook();
post.put(" foo ", "bar");


post= lcobject.createWithoutData(" Post ", post.getobjectId());
post.disableafterhook();
post.save();
}

post[" foo "] = "bar";
await post.Save();


await post.fetch();
post.disableafterhook();
post[" foo "] = "bar";


post= lcobject.CreateWithoutData(" Post ", post.objectId);
post.disableafterhook();
await post.Save();

Error codes for hooks​

Use the following way to define errorcodes for hooks like BeforeSave:

  • Node.js
  • Python
  • php
  • Java
  • .NET ( c # )
  • Go
AV.Cloud.beforesave(" Review ", function (request) {

throw new AV.Cloud.Error(
JSON.stringify({
code: 123,
message: "An erroroccurred.",
})
);
});
@engine.before_save(' Review ') 
def before_review_save(review):
comment= review.get(' comment')
if not comment:
raise leancloud.LeanengineError(
code=123,
message='An erroroccurred.'
)
Cloud: :beforesave(" Review ", function($           review, $    user) {
$ comment = $ review->get(" comment");
if (!$ comment) {
throw new functionerror(json_encode(array(
" code " => 123,
"message" => "An erroroccurred.",
)));
}
});
@enginehook(className= " Review ",  type= enginehookType.beforesave)
public static lcobject reviewbeforesavehook(lcobject review) throw exception {
throw new lcexception(123, "An erroroccurred.");
}
[LCengineClasshook(" Review ", LCengineobjecthookType.BeforeDelete)]
public static voi d ReviewBeforeDelete(lcobject review) {
throw new lcexception(123, "An erroroccurred.");
}
leancloud.engine.BeforeSave(" Review ", func(req *ClasshookRequest) (interface{}, error) {
return nil, leancloud.CloudError{code: 123, Message: "An erroroccurred."}
})

The responsegotten by the client would looklike Cloud code vali dation failed. Error detail: { " code ": 123,"message": "An erroroccurred." }. The client can then slice the string to get the errormessage.

Timeouts for hooks​

The timeout for before hooks is 10 seconds while that for other hooks is 3 seconds. If a hookis triggered by another Cloud function (like when a BeforeSave orAfterSave hookgets triggered when a new objectgets created),its timeout will be cut down to the remaining timeout of the triggering Cloud function.

For example,if a BeforeSave hookis triggered by a Cloud function that has already been executed for 13 seconds,the hookwill only have 2 seconds left. See Timeouts for Cloud functions for more information.

hooks for Instant Messaging​

See hooks andSystem Conversations for more information.

write Cloud functions Online​

You can write Cloud functions online using the dashboard instead ofcreating anddeploying a project. Keep inmind that:

  • Deploying the Cloud functions you write online will overri de the project deployed with Git orthe CLI.
  • You can only write Cloud functions andhooks online. You can’t upload static web pages orwrite dynamic routes with the web interface.
  • You can only use the JavaScript SDK together with some built-in Node.js modules (listed inthe table below) . You can’t import other modules as dependencies.

Path

Cloud Functions and Hooks Guide

Cloud Functions and Hooks Guide

On Dashboard > Leanengine > Manage deployment > Your group > Deploy > Edit online,you can:

  • Create function : When creating a Cloud function,you can specify its type,name,code,and comments. Click Create to save the Cloud function . Types is include of Cloud functions includefunction (basic Cloud function),hook,and Global (shared logic used by multiple Cloud functions) .
  • Deploy: You can select the environment to deploy your Cloud functions to andclick Deploy to proceed.
  • Preview: This will combine all the Cloud functions into a single code snippet. You can verify the code inyour Cloud functions oroverri de the file named cloud.js ina project created from the demo project with the code shown here.
  • Maintain Cloud functions: You can edit existing Cloud functions,view histories,and delete Cloud functions.

After you edit your Cloud functions,make sure to click Deploy to have the edits take effect.

The online editor only supports Node.js at this time. The latest version,v3,uses Node.js 8.x andthe Node.js SDK 3.x. With this version selected,you’ll need to write your functions using Promise. Packages provi ded by default include async,bluebird,crypto,debug,ejs,jade,lodash,moment,nodemailer,qiniu,redis,request,request-promise,superagent,underscore,uui d,wechat-api,and xml2js.

More about the SDK versions used by the online editor
Version Node.js SDK JS SDK Node.js note Dependencies available
v0 0.x 0.x 0.12 Not recommend anymore moment,request,underscore
v1 1.x 1.x 4 async,bluebird,co,ejs,handlebars,joi,lodash,marked,moment,q,request,superagent,underscore
v2 2.x 2.x 6 Please write your function using Promise async,bluebird,crypto,debug,ejs,jade,lodash,moment,nodemailer,qiniu,redis,request,request-promise,superagent,underscore,uui d,wechat-api,xml2js
v3 3.x 3.x 8 Please write your function using Promise async,bluebird,crypto,debug,ejs,jade,lodash,moment,nodemailer,qiniu,redis,request,request-promise,superagent,underscore,uui d,wechat-api,xml2js

Upgrading from v0 to v1:

  • upgrade the JS SDK to 1.0 .
  • Requires you to obtain the user from request.currentuser instead ofAV.user.current.
  • Requires you to manually provi de the user objectwhen calling AV.Cloud.run.

upgrade from v1 to v2 :

  • Upgraded the JS SDK to 2.0 (Promise must be used instead ofcallback) .
  • removeAV.Cloud.httpRequest.
  • Requires you to return a Promise from each Cloud function andthrow an AV.Cloud.Error for each error.

Upgrading from v2 to v3:

  • upgrade the JS SDK to 3.0 ( include change to the behavior ofAV.object.toJSON) .
Difference between writing Cloud functions online anddeploying a project

When you write Cloud functions online,the way our system deals with your functions is to join them together,generate a Cloud engine project,and deploy this project.

You can consi der writing Cloud functions online to be the same as deploying a project: they both form a complete project inthe end.

When you define functions online,you can quickly generate a Cloud engine project without using our SDK orGit.

You may also choose to create anddeploy a project written based on our SDK.

The two options are mutually exclusive: when you deploy with one option,the deployment using the other option will be overri dden.

Migrating from writing Cloud functions online to deploying a project
  1. Install the CLI according to the CLI Gui de andcreate a new project by running lean new. selectNode.js > Express as the template (this is our demo project for Node.js) .
  2. Navigate to Dashboard > Leanengine > Manage deployment > Your group > Deploy > Edit online andclick Preview. Copy the code shown here andreplace the code incloud.js with it .
  3. run lean up. Now you can test your Cloud functions andhooks on http://localhost:3001. Once you’re done,run lean deploy to deploy your code to Cloud engine. If you have standard instances,make sure to run lean publish as well.
  4. After deploying your project,watch the dashboard to see if there are any errors.

If you have been using the Node.js SDK 0.x,you’ll have to update your code to avoi d compatibility issues.
For example,AV.user.current() should be changed to request.currentuser.

Viewing andrunning Cloud functions​

Dashboard > Leanengine > Manage deployment > Your group > Deploy shows all the Cloud functions andhooks defined within each group,including their names,groups,and QPM (requests per minute) . You can click run to run a Cloud function from the dashboard.

Cloud functions from all the groups inyour application will be displayed here regardless of the way they’re deployed (writing Cloud functions online ordeploying a project) .

Cloud Functions and Hooks Guide

production environment vs. Staging environment​

Your app comes with a production environment anda staging environment. When triggering a Cloud function from within a Cloud engine instance using the SDK,no matter explicitly orimplicitly (by triggering hooks),the SDK will trigger the function defined inthe same environment as the instance. For example,with beforeDelete defined,if an objectis deleted with the SDK inthe staging environment,the beforeDelete hookin the staging environment will be triggered.

When triggering Cloud functions outsi de of a Cloud engine instance using the SDK,no matter explicitly orimplicitly,x - LC - Prod will be set to 1 by default,which means that the Cloud functions inthe production environment will be triggered. Forhistorical reasons,there are some differences among the SDKs:

  • ForNode.js,php,Java,and C# SDKs,the production environment will be used by default.
  • Forthe Python SDK,when debugging locally with lean-cli,the staging environment will be used if it exists. Otherwise,the production environment will be used.
  • ForJava example projects,java-war-getting-started andspring-boot-getting-started,when debugging locally with lean-cli,the staging environment will be used if it exists. Otherwise,the production environment will be used (same as the Python SDK) .

You can specify the environment being used with the SDK:

  • .NET
  • Java
  • objective-C
  • swift
  • flutter
  • JavaScript
  • Python
  • php
  • Go
lccloud.IsProduction = true; 
lccloud.IsProduction = false;
lccloud.setProductionMode(true); 
lccloud.setProductionMode(false);
[lccloud setProductionMode:YES]; 
[lccloud setProductionMode:NO];



do {
let environment: lcapplication.environment = [.cloudengineDevelopment]
let configuration = lcapplication.Configuration(environment: environment)
try lcapplication.default.set(
i d: {{appi d}},
key: {{appkey}},
serverURL: "https://please-replace-with-your-customized.domain.com",
configuration: configuration)
} catch {
print(error)
}
lccloud.setProduction(true); 
lccloud.setProduction(false);
AV.setProduction(true); 
AV.setProduction(false);
leancloud.use_production(True) 
leancloud.use_production(false)

Client: :useProduction(true); 
Client: :useProduction(false);

If you’re using the trial mode,there will only be a production environment andyou won’t be able to switch to the staging environment.

Scheduled tasks​

You can set up scheduled tasks to run your Cloud functions periodically. For example,you can have your app clean up temporary datum every night,send push notifications to users every Monday,etc. The time set for a scheduled task can be accurate to a second.

The timeout applied to ordinary Cloud functions also applies to scheduled tasks. See Avoi ding Timeouts for more information.

If a scheduled task triggers more than 30 400 (Bad Request) or502 (Bad Gateway) errors within 24 hours,the task will be disabled andyou will get an email regarding the issue. The errortimerAction short-circuited andno fallback available will also be printed to the log.

After deploying your program to Cloud engine,go to Dashboard > Leanengine > Manage deployment > Your group > Scheduled tasks andclick Create scheduled task to create a scheduled task for a Cloud function. For example,if we have a function named logtimer:

  • Node.js
  • Python
  • php
  • Java
  • .NET ( c # )
  • Go
AV.Cloud.define("logtimer", function (request) {
console.log("This log is printed by logtimer.");
});
@engine.define
def logtimer(movie, * *params):
print('This log is printed by logtimer.')
Cloud: :define("logtimer", function($  param, $    user) {
error_log("This log is printed by logtimer.");
});
@enginefunction("logtimer")
public static float logtimer throw exception {
LogUtil.avlog.d("This log is printed by logtimer.");
}
[LCenginefunction("logtimer")]
public static voi d LogTimer() {
Console.WriteLine("This log is printed by logtimer.");
}
leancloud.engine.define("logtimer", func(req *functionRequest) (interface{}, error) {
fmt.println("This log is printed by logtimer.")
return nil, nil
})

Cloud Functions and Hooks Guide

You can specify the times a scheduled task gets triggered using one of the following expressions:

  • CRON expression
  • Interval inseconds

Take the CRON expression as an example. To print logs at 8am every Monday,create a scheduled task for the function logtimer using a CRON expression andenter 0 0 8 ? * MON for it .

See Cloud Queue Gui de § CRON Expressions for more information about CRON expressions.

When creating a scheduled task,you can optionally fill inthe following two fields:

  • Params: Thearguments passed to the Cloud function as a JSON object.
  • Error-handling policy: Whether to retry orcancel the task when it fails due to a Cloud function timeout. See Cloud Queue Gui de § Error-Handling Policy for more information.

Last execution is the time and resultof the last execution. This information will only be retained for 5 minutes. Under the details of the execution:

  • status: Thestatus of the task; could be success orfailed
  • uniqueId: A unique ID for the task
  • finishedAt: Theexact time when the task finished (for succeeded tasks only)
  • statuscode: TheHTTP status returned by the Cloud function (for succeeded tasks only)
  • result: Theresponsebody returned by the Cloud function (for succeeded tasks only)
  • error: error message ( for failed task only )
  • retryAt: Thetime the task will be rerun (for failed tasks only)

You can view the logs of all the scheduled tasks on Dashboard > Leanengine > Manage deployment > Your group > Logs.