RESTe
A simple JavaScript REST / API helper for Titanium
Install / Use
/learn @jasonkneen/RESTeREADME
RESTe
FUTURE BREAKING CHANGE
In 1.4.5, a new option to support error objects is available. It's off by default and can be switched on by setting:
.errorsAsObjects = true
in the RESTe config. This will ensure you get full objects back for errors so you can access status codes, the http object itself. For legacy support this is off by default but will be the default in future versions so please, update your apps!
Important note on JSON data
RESTe tries to make sense of the data that comes back but currently it will have problems with invalid JSON data. If you're having any issues with data not being rendered / bound, check it's valid JSON so everything is wrapped as a string. JSON has real issues with numbers -- if it's a normal number it's fine but putting in say 000000 for a property can cause issues in parsing.
Also, make sure you're not using the app/models folder or the <Collection / Model src="etc"/> tags -- you only need to define your models in the RESTe config, and then use the dataCollection property in the repeater.
Why?
I build a lot of apps that integrate with APIs. These could be written using the open-source Parse Server or a hosted service, but more often they are custom APIs written by another developer. I used to use a basic api.js library to handle the API integration, but this typically involved writing my own module for the API in question, requiring the api.js module, and writing specific methods for the app.
The Old Way
So previously I'd end up writing methods like this:
exports.getPreviousLocations = function(callback) {
var Rest = new Api(Alloy.CFG.baseURL + "users/" + token + "/previouslocations");
Rest.get(function(e) {
processResponse(e, function() {
callback(e.result);
});
});
};
or a POST one like this:
exports.updateUser = function(name, email, password, callback) {
var Rest = new Api(Alloy.CFG.baseURL + "users/" + token);
Rest.post(JSON.stringify({
"name" : name,
"email" : email,
"password" : password
}), function(e) {
processResponse(e, function() {
callback(e);
});
});
};
(The processResponse function was written to try to parse the data as it came back, check for success / results etc - but even with that I was finding myself duplicating a lot of code.)
A New Way - Using RESTe
The idea behind RESTe was to have a single JS library I could drop in a project, apply a simple config, and have it generate the methods for me.
The main things I wanted to achieve were:-
- Simple to implement in an new project, or replace an existing API layer
- Supports headers, tokens, events
- Support for Alloy Collections / Models
- Minimal code
Quick Start
(If your Titanium app does not use node modules already, follow the guide here )
Using NPM Packages in Titanium
- Install from NPM the latest version within your app/lib folder or
- Download the latest version and place in your project (lib folder for Alloy).
Ideally you should put your RESTe config in alloy.js OR in an app/lib file that is called from alloy.js or before you intend to use any colletions / models:-
var reste = require("reste");
var api = new reste();
// now we can do our one-time configure
api.config({
debug: true, // allows logging to console of ::REST:: messages
errorsAsObjects: true, // Default: false. New in 1.4.5, will break 1.4.4 apps that handle errors
autoValidateParams: false, // set to true to throw errors if <param> url properties are not passed
validatesSecureCertificate: false, // Optional: If not specified, default behaviour from http://goo.gl/sJvxzS is kept.
timeout: 4000,
url: "https://api.parse.com/1/",
requestHeaders: {
"X-Parse-Application-Id": "APPID",
"X-Parse-REST-API-Key": "RESTID",
"Content-Type": "application/json"
},
methods: [{
name: "courses",
post: "functions/getCourses",
onError: function(e, callback, globalOnError){
alert("There was an error getting the courses!");
}
}, {
name: "getVideos",
get: "classes/videos"
}, {
name: "getVideoById",
get: "classes/videos/<videoId>"
}, {
name: "addVideo",
post: "classes/videos"
}],
onError: function(e, retry) {
var dialog = Ti.UI.createAlertDialog({
title: "Connection error",
message: "There was an error connecting to the server, check your network connection and retry.",
buttonNames: ['Retry']
});
dialog.addEventListener("click", function() {
retry();
});
dialog.show();
},
onLoad: function(e, callback) {
callback(e);
}
});
IMPORTANT: You can't put the config in the same file as a controller that is binding with Alloy. This is because Alloy will attempt to resolve any references for dataCollection before the config is ready -- so for best results put the config into alloy.js directly OR require it from a lib/whatever.js file from alloy.js.
Hooks: beforePost, beforeSend
A couple of useful hooks or events can be used within your RESTe global configuration. Those hooks will happen before specific calls are made. They will be executed before any request is sent allowing you to a) change the parameters or b) stop the call happening.
beforePost:
This one is quite useful if you need to change the parameters which are going to be used for the request. You might for example -- if you're using Parse Server -- want to strip out certain parameters from models before sending them.
Example:
{
...
beforePost: function(params, callback) {
params.something = 'else';
callback(params);
},
...
}
beforeSend:
This is similar to beforePost but works for all requests (GET, PUT, DELETE, POST). If you specify both beforePost and beforeSend then beforePost will go first, then beforeSend.
Example:
{
...
beforeSend: function(data, callback) {
if (Ti.Network.online) {
callback(data);
} else {
alert("No internet connection!");
callback({
skip: true,
error: "no_internet"
});
// will call your success method and pass `error:no_internet` to it
}
},
...
}
Errors
By default RESTe returns the body of the response from the API when an error occurs using responseText from the HTTP Client.
You can change this by specifying errorsAsObjects: true within your RESTe config so you get the full error object back. This will include the error object as well as the body response from the API which will be accessible from the content property on the object returned.
Example of a the object returned including the error and the response body:
{
"success": false,
"code": 401,
"content": {
"error": "invalid_credentials",
"message": "The user credentials were incorrect."
},
"source": "[object TiNetworkHTTPClient]",
"type": "error",
"error": "HTTP error",
"url": "http://lorem.ipsum.com"
}
This is really useful if you need to access both the HTTP status code as well as the potential error message returned from the API.
onError() and onLoad()
You can pass the optional onError and onLoad handlers, which will intercept the error or retrieved data before it's passed to the calling function's callback. This way you can change, test, do-what-you-want-with-it before passing it on.
Note, in the onError handler, you can (as of 1.2.0) also handle any network errors better -- in the example above a retry method is returned so you can check the error, display a specific one, or handle any network issues, and if required, issue a retry() which will attempt the last call again.
By default, a local error handler will override the global error handler. So if you want to use both (so have local fire first and then pass off to global, then handle this within your own app).
In this example we have a function in the same file as the RESTe config like this:
function globalError(e, retry) {
var dialog = Ti.UI.createAlertDialog({
title: "Connection error",
message: "There was an error connecting to the server, check your network connection and retry.",
buttonNames: ['Retry']
});
dialog.addEventListener("click", function() {
retry();
});
dialog.show();
}
and in our RESTe config we have:
methods: [{
name: "courses",
post: "functions/getCourses",
onError: function(e, callback) {
if (e.message) {
alert(e.message);
} else {
globalError(e, callback);
}
}, {
...
}],
onError: globalError,
If you specify parameters required e.g. videoId then RESTe will automatically check for these in the parameters passed to the method, and raise an error if they're missing.
Once you've done all this (and assuming no errors), you'll have new methods available:
api.getVideos(function(videos) {
// do stuff with the videos here
});
Or call a method with a specific Id:
api.getVideoById({
videoId: "fUAM4ZFj9X"
}, function(video) {
// do stuff with the video
});
For a post request, you could do the following:
api.addVideo({
body: {
categoryId: 1,
name: "My Video"
}
}, function(video) {
// do stuff with the video
});
Here's a PUT request example, passing an id (you'd need to ensure you have a objectId attribute in the method definition:
Related Skills
node-connect
350.1kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
109.9kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
350.1kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
350.1kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
