{"id":13749513,"url":"https://github.com/jasonkneen/RESTe","last_synced_at":"2025-05-09T12:33:04.407Z","repository":{"id":27798131,"uuid":"31287259","full_name":"jasonkneen/RESTe","owner":"jasonkneen","description":"A simple JavaScript REST / API helper for Titanium ","archived":false,"fork":false,"pushed_at":"2025-01-27T13:19:07.000Z","size":812,"stargazers_count":127,"open_issues_count":2,"forks_count":35,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-05-01T11:47:56.944Z","etag":null,"topics":["android","ios","native-javascript","titanium","titanium-mobile"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"JetBrains/swot","license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jasonkneen.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-02-24T23:21:27.000Z","updated_at":"2025-01-31T01:08:56.000Z","dependencies_parsed_at":"2024-04-08T11:49:39.476Z","dependency_job_id":null,"html_url":"https://github.com/jasonkneen/RESTe","commit_stats":{"total_commits":229,"total_committers":19,"mean_commits":"12.052631578947368","dds":"0.34497816593886466","last_synced_commit":"e81ff2da40858a660367843fb1ce458957b52a9d"},"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jasonkneen%2FRESTe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jasonkneen%2FRESTe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jasonkneen%2FRESTe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jasonkneen%2FRESTe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jasonkneen","download_url":"https://codeload.github.com/jasonkneen/RESTe/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253251855,"owners_count":21878581,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","ios","native-javascript","titanium","titanium-mobile"],"created_at":"2024-08-03T07:01:03.894Z","updated_at":"2025-05-09T12:33:04.113Z","avatar_url":"https://github.com/jasonkneen.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# RESTe\n\n## FUTURE BREAKING CHANGE\n\nIn 1.4.5, a new option to support error objects is available. It's off by default and can be switched on by setting:\n\n```JS\n.errorsAsObjects = true\n```\nin 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!\n\n## Important note on JSON data\n\nRESTe 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.\n\nAlso, make sure you're not using the **app/models** folder or the **\u003cCollection / Model src=\"etc\"/\u003e** tags -- you *only* need to define your models in the RESTe config, and then use the **dataCollection** property in the repeater.\n\n## Why?\n\nI 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.\n\n### The Old Way\n\nSo previously I'd end up writing methods like this:\n\n```JS\nexports.getPreviousLocations = function(callback) {\n    var Rest = new Api(Alloy.CFG.baseURL + \"users/\" + token + \"/previouslocations\");\n\n    Rest.get(function(e) {\n        processResponse(e, function() {\n            callback(e.result);\n        });\n    });\n\n};\n```\n\nor a POST one like this:\n\n```JS\nexports.updateUser = function(name, email, password, callback) {\n    var Rest = new Api(Alloy.CFG.baseURL + \"users/\" + token);\n\n    Rest.post(JSON.stringify({\n        \"name\" : name,\n        \"email\" : email,\n        \"password\" : password\n\n    }), function(e) {\n\n        processResponse(e, function() {\n            callback(e);\n        });\n    });\n};\n```\n\n_(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.)_\n\n## A New Way - Using RESTe\n\nThe 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.\n\nThe main things I wanted to achieve were:-\n\n* Simple to implement in an new project, or replace an existing API layer\n* Supports headers, tokens, events\n* Support for Alloy Collections / Models\n* Minimal code\n\n## Quick Start\n\n(If your Titanium app does not use node modules already, follow the guide here )\n\n[Using NPM Packages in Titanium](https://blog.axway.com/mobile-apps/using-npm-packages-in-titanium)\n\n* [Install from NPM the latest version within your app/lib folder](https://www.npmjs.com/package/reste)\nor\n* [Download the latest version](https://github.com/jasonkneen/reste) and place in your project (lib folder for Alloy).\n\n\n\n\nIdeally 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:-\n\n```javascript\nvar reste = require(\"reste\");\nvar api = new reste();\n\n// now we can do our one-time configure\napi.config({\n    debug: true, // allows logging to console of ::REST:: messages\n    errorsAsObjects: true, // Default: false. New in 1.4.5, will break 1.4.4 apps that handle errors\n    autoValidateParams: false, // set to true to throw errors if \u003cparam\u003e url properties are not passed\n    validatesSecureCertificate: false, // Optional: If not specified, default behaviour from http://goo.gl/sJvxzS is kept.\n    timeout: 4000,\n    url: \"https://api.parse.com/1/\",\n    requestHeaders: {\n        \"X-Parse-Application-Id\": \"APPID\",\n        \"X-Parse-REST-API-Key\": \"RESTID\",\n        \"Content-Type\": \"application/json\"\n    },\n    methods: [{\n        name: \"courses\",\n        post: \"functions/getCourses\",\n        onError: function(e, callback, globalOnError){\n        \talert(\"There was an error getting the courses!\");\n        }\n    }, {\n        name: \"getVideos\",\n        get: \"classes/videos\"\n    }, {\n        name: \"getVideoById\",\n        get: \"classes/videos/\u003cvideoId\u003e\"\n    }, {\n        name: \"addVideo\",\n        post: \"classes/videos\"\n    }],\n    onError: function(e, retry) {\n        var dialog = Ti.UI.createAlertDialog({\n            title: \"Connection error\",\n            message: \"There was an error connecting to the server, check your network connection and  retry.\",\n            buttonNames: ['Retry']\n        });\n\n        dialog.addEventListener(\"click\", function() {\n            retry();\n        });\n        dialog.show();\n    },\n    onLoad: function(e, callback) {\n        callback(e);\n    }\n});\n```\n\n**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.\n\n### Hooks: beforePost, beforeSend\n\nA 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.\n\n#### beforePost:\n\nThis 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.\n\n**Example:**\n```javascript\n{\n    ...\n    beforePost: function(params, callback) {\n      params.something = 'else';\n      callback(params);\n    },\n    ...\n}\n```\n\n#### beforeSend:\nThis 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.\n\n**Example:**\n```javascript\n{\n    ...\n    beforeSend: function(data, callback) {\n      if (Ti.Network.online) {\n        callback(data);\n       } else {\n         alert(\"No internet connection!\");\n         callback({\n           skip: true,\n           error: \"no_internet\"\n         });\n         // will call your success method and pass `error:no_internet` to it\n       }\n     },\n     ...\n}\n```\n### Errors\n\nBy default RESTe returns the body of the response from the API when an error occurs using `responseText` from the HTTP Client.\n\nYou 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.\n\nExample of a the object returned including the error and the response body:\n```javascript\n{\n    \"success\": false,\n    \"code\": 401,\n    \"content\": {\n        \"error\": \"invalid_credentials\",\n        \"message\": \"The user credentials were incorrect.\"\n    },\n    \"source\": \"[object TiNetworkHTTPClient]\",\n    \"type\": \"error\",\n    \"error\": \"HTTP error\",\n    \"url\": \"http://lorem.ipsum.com\"\n}\n```\n\nThis is really useful if you need to access both the HTTP status code as well as the potential error message returned from the API.\n\n### onError() and onLoad()\n\nYou 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.\n\nNote, 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.\n\nBy 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).\n\nIn this example we have a function in the same file as the RESTe config like this:\n\n```javascript\nfunction globalError(e, retry) {\n    var dialog = Ti.UI.createAlertDialog({\n        title: \"Connection error\",\n        message: \"There was an error connecting to the server, check your network connection and  retry.\",\n        buttonNames: ['Retry']\n    });\n\n    dialog.addEventListener(\"click\", function() {\n        retry();\n    });\n    dialog.show();\n}\n```\nand in our RESTe config we have:\n\n```javascript\n     methods: [{\n        name: \"courses\",\n        post: \"functions/getCourses\",\n        onError: function(e, callback) {\n\t\tif (e.message) {\n\t\t\talert(e.message);\n\t\t} else {\n\t\t\tglobalError(e, callback);\n\t\t}        \n    }, {\n        ...\n    }],\n    onError: globalError,\n```\n\nIf 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.\n\nOnce you've done all this (and assuming no errors), you'll have new methods available:\n\n```javascript\napi.getVideos(function(videos) {\n    // do stuff with the videos here\n});\n```\n\nOr call a method with a specific Id:\n\n```javascript\napi.getVideoById({\n    videoId: \"fUAM4ZFj9X\"    \n}, function(video) {\n    // do stuff with the video\n});\n```\n\nFor a post request, you could do the following:\n\n```javascript\napi.addVideo({\n    body: {\n        categoryId: 1,\n        name: \"My Video\"\n    }\n}, function(video) {\n    // do stuff with the video\n});\n```\n\nHere's a **PUT** request example, passing an id (you'd need to ensure you have a `objectId` attribute in the method definition:\n\n```javascript\napi.updateVideo({\n  objectId: \"123\",\n  body: {\n    categoryId: 2,\n    name: \"My Video2\"\n  }\n}, function(video) {\n  // do stuff with the video\n});\n```\n\n## Local definitions\n\nThose apply when you decide to set those at a method definition level (for one endpoint only).\n\n### onError() and onLoad()\n\nYou can also pass the **onLoad** and **onError** handlers within each method - to have a unique response from each. In all cases you always get two params which are the **response** and the **original callback** so you can pass it through, or stop the call. Again with **onError** you can perform a **retry()** at a local level.\n\n### Override the base URL\n\nSince version 1.3.6 it's now possible to have a complete URL in the method definition, for example, if you're using a base URL (`url` top setting) and methods for your primary API, you might want to access another service for Push or Geocoding etc.\n\nIn this instance, you would specify a method and specify the **GET**, **PUT** etc as the full URL including the `http://` or `https://` intro. RESTe will ignore the base URL and any global request headers, and use your \"local\" URL entirely -- so add any headers required to the method definition.\n\n```javascript\napi.config({\n    ...\n    }, {\n        name: \"pushNotification\",\n        post: \"http://another.api.service.com/push\"\n    }, {\n    ...\n});\n```\n\n### Override or add request headers\n\nYou can override or add new headers for each method (or endpoint) locally.\n\nYou can also use functions for those which will be executed every time this method is used from RESTe, giving you the ability to have dynamic parameters here. Pretty useful for `Authorization` headers using dynamic tokens persisted somewhere else for example.\n\n```javascript\n...\n{\n    name: \"getAccounts\",\n    get: \"user/accounts\",\n    headers: {\n        \"Authorization\": function(){\n            return \"Something\";\n        }\n    }\n}\n...\n```\n\n**Pro tip:** If for whatever reason you need some settings to be more dynamic (maybe using global functions), you can even have self executed functions for any of those. Something like :\n\n```javascript\napi.config({\n    ...\n    }, {\n        name: \"pushNotification\",\n        post: (function(){ return \"some/endpoint\"; })()\n    }, {\n    ...\n});\n```\n\n## Promise support with q.js\n\n[Download the q.js](https://github.com/kriskowal/q) and place in your project (lib folder for Alloy). Then pass it to config as Q property.\n\n```javascript\napi.config({\n    Q: require('q'),\n    ...\n});\n```\n\nExamples using Promise\n\n```javascript\napi.getVideos().then(function(videos){}).then(...).catch(...);\n```\n\nOr call a method with a specific Id:\n\n```javascript\napi.getVideoById({\n    videoId: \"fUAM4ZFj9X\"\n}).then(function(video) {\n    // do stuff with the video\n});\n```\n\n## Helper functions\n\nThere are a couple of new functions to help in a couple of areas -- firstly, being able to swap out the base URL of your API -- useful if you're developing and need to switch servers in the app. The second method supports clearing any cookies from the RESTe http client.\n\nThe following will temporarily change the config base URL:\n\n```javascript\napi.setUrl(\"http://whatever\");\n```\n\n(this is lost if you restart the app)\n\nThe following will clear any cookies from the baseUrl:\n\n```javascript\napi.clearCookies();\n```\n\n## Alloy Collections and Model support\n\nRESTe supports collection and model generation. So it supports creating and managing collections and models, binding, and CRUD methods to Create, Update and Delete models.\n\n**NOTE**: If you are using the Alloy Collections and Model Support of RESTe, you should **not** use the Alloy Model / Collection definitions -- so you shouldn't have an app/models folder with models defined. You must also **not** use the \u003cCollection src=\"etc\"/\u003e notation in the XML -- you *just* use the dataCollection binding in a repeating element.\n\nYou can also now perform transform functions at a global (config) level or locally in a controller / view -- this is really useful if you use Alloy and pass models to views using **$model**\n\nIn the following example, we've defined a method called **getExpenseQueueFull** elsewhere in the config that gets expense details, and then defined a **transform** function in the config:\n\n```javascript\n    models: [{\n        name: \"expense\",\n        id: \"unid\",\n        read: \"getExpenseById\",\n        content: \"retArray\",\n        transform: function(m) {\n            m = m.toJSON();\n            m.hotelAllowance \u0026\u0026 (m.hotelAllowance = \"£\" + parseFloat(m.hotelAllowance).toFixed(2));\n            m.mileage \u0026\u0026 (m.mileage = \"£\" + parseFloat(m.mileage).toFixed(2));\n            m.other \u0026\u0026 (m.other = \"£\" + parseFloat(m.other).toFixed(2));\n            m.total \u0026\u0026 (m.total = \"£\" + parseFloat(m.total).toFixed(2));\n            return m;\n        },\n        collections: [{\n            name: \"expenses\",\n            content: \"retArray\",\n            read: \"getExpensesQueueFull\"\n        }],\n    }\n```\n\nSo now whenever you want to transform the model, you can do so within a local transform function as follows:\n\n```javascript\nfunction transform(model) {\n    var m = model.transform(model);\n    return m;\n}\n```\n\nYou can also pass an optional transform parameter in the transform function, which will override the global transform method.\n\n### Defining methods with models / collections\n\nUsing the following config you can configure end points that will still work as normal RESTe methods, but also give you collections and model support for (C)reate, (R)ead, (U)pdate, (D)elete. For Collections I use an array of collections so you can have multiple endpoints configured if different collections using the same model. This enables use of for example, Alloy.Collections.locations (for all locations) and Alloy.Collections.locationsByName (for locations by a specific parameter).\n\n(Ideally this should be more elegant, allowing the single locations collection in this case to be used to filter content but I needed a way to make this API independant and it's the best I can do for now!)\n\n```javascript\n    models: [{\n        name: \"location\",\n        id: \"objectId\",\n        read: \"getLocation\",\n        //content: \"results\" \u003c- use this is your method returns an array object\n        create: \"createLocation\",        \n        update: \"updateLocation\",\n        delete: \"deleteLocation\",\n        collections: [{\n            name: \"locations\",\n            content: \"results\",\n            read: \"getLocations\"\n        }, {\n            name: \"locationsByName\",\n            content: \"results\",\n            read: \"getLocationsByName\"\n        }],\n    }],\n    methods: [{\n        name: \"getLocations\",\n        get: \"classes/locations\"\n    }, {\n        name: \"getLocation\",\n        get: \"classes/locations/\u003cid\u003e\"\n    },{\n        name: \"getLocationsByName\",\n        get: 'classes/locations?where={\"name\": \"\u003cname\u003e\"}'\n    }, {\n        name: \"updateLocation\",\n        put: \"classes/locations/\u003cid\u003e\"\n    }, {\n        name: \"createLocation\",\n        post: \"classes/locations/\"\n    }, {\n        name: \"deleteLocation\",\n        delete: \"classes/locations/\u003cid\u003e\"\n    }]\n```\n\n### Using models / collections\n\nIn the example above, I can refresh the data for a collection by using:\n\n```javascript\nAlloy.Collections.locations.fetch();\n```\n\nand bind it to a tableview as follows:\n\n```xml\n\u003cTableView dataCollection=\"locations\" onClick=\"selectLocation\"\u003e\n    \u003cTableViewRow id=\"locationRow\" model=\"{objectId}\" \u003e\n        \u003cLabel class=\"title\" top=\"10\"left=\"20\" text=\"{name}\"/\u003e\n        \u003cLabel class=\"subTitle\" bottom=\"10\" left=\"20\" text=\"{address}\"/\u003e\n    \u003c/TableViewRow\u003e\n \u003c/TableView\u003e\n```\n\nYou could also send parameters like follows:\n\n```javascript\nAlloy.Collections.locationsByName.fetch({\n\t\t\t\t\tname: \"home\"\n\t\t\t\t\t});\n```\n\nTo sort a collection, you need to set the comparator to the collection. Don't do this in the API configuration, but on the collection itself before you fetch it, like shown in the example below.\n\nCalling the sort function at any time after the fetch will try to sort.\n\n```js\nAlloy.Collections.locations.comparator = function(a, b){\n\t// do your sorting here, a \u0026 b will be models\n};\n\nAlloy.Collections.locations.fetch({\n\tsuccess: function(a,b,c){\n\t\tAlloy.Collections.locations.sort();\n\t}\n});\n```\n\n### Creating new models and collections\n\nRESTe provides a couple of useful helper functions to create new models and collections - this is useful if you want to take an array of objects and turn them into a collection for easy binding.\n\n```javascript\n.createModel(name, attributes)\n.createCollection(name, array)\n```\nEach return either a model, or collection that can then be used with Alloy.\n\nWhen working with created models, you can define an instance of a model that you've specified in the config, and if that supports CRUD functions, you can pass options when creating, saving, updating and deleting.\n\nSo for example:\n\n```javascript\nvar user = Alloy.Globals.reste.createModel(\"user\");\n\nuser.save({\n            username: $.email.value,\n            firstname: $.firstname.value,\n            lastname: $.lastname.value,\n            email: $.email.value,\n            password: $.password.value\n        }, {\n            success: function(e, response) {\n                console.log(\"User saved!\");\n                console.log(user.toJSON());\n            },\n            error: function(e, response) {\n                console.log(\"Error saving user!\");\n                console.log(response);\n            }\n});\n```\n\n## License\n\n\u003cpre\u003e\nCopyright Jason Kneen\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\u003c/pre\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjasonkneen%2FRESTe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjasonkneen%2FRESTe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjasonkneen%2FRESTe/lists"}