{"id":18067385,"url":"https://github.com/natevw/fermata","last_synced_at":"2025-04-05T09:10:48.460Z","repository":{"id":57234492,"uuid":"1551766","full_name":"natevw/fermata","owner":"natevw","description":"Succinct native REST client, for client-side web apps and node.js. Turns URLs into (optionally: magic!) JavaScript objects.","archived":false,"fork":false,"pushed_at":"2021-06-08T19:05:33.000Z","size":169,"stargazers_count":330,"open_issues_count":22,"forks_count":24,"subscribers_count":15,"default_branch":"master","last_synced_at":"2025-03-29T08:06:45.277Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/natevw.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}},"created_at":"2011-03-31T16:51:01.000Z","updated_at":"2025-02-14T11:33:54.000Z","dependencies_parsed_at":"2022-09-11T07:00:57.727Z","dependency_job_id":null,"html_url":"https://github.com/natevw/fermata","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/natevw%2Ffermata","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/natevw%2Ffermata/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/natevw%2Ffermata/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/natevw%2Ffermata/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/natevw","download_url":"https://codeload.github.com/natevw/fermata/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247312085,"owners_count":20918344,"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":[],"created_at":"2024-10-31T07:09:08.116Z","updated_at":"2025-04-05T09:10:48.440Z","avatar_url":"https://github.com/natevw.png","language":"JavaScript","readme":"# Fermata #\n\nFermata is a JavaScript REST library that lets you simply state your HTTP requests using clean syntax.\n\nFeatures:\n\n* cleanly build URL strings (optional dot syntax — \u003ci\u003enode.js\u003c/i\u003e, \u003ci\u003esupporting browsers\u003c/i\u003e) and send asynchronous HTTP requests\n* automatic conversion of JSON request/response data\n* easily send raw data and form requests (including files!)\n* full customization of request (method, headers, data) when necessary\n* easy to add custom initialization and transport handlers\n* OAuth 1.0a support — \u003ci\u003enode.js\u003c/i\u003e\n\nFermata is a no-hassle library, compatible with all modern browsers *and* node.js\n\n\n## Why? ##\n\nFermata magically provides a clean JavaScript interface for direct access to any REST interface.\n\nIt does this by taking away the pain of URL strings and giving back a polished server response.\nFermata works well in modern browsers and even better in [node.js](http://nodejs.org/).\nIts API naturally matches the authoritative HTTP documentation, so you always have access to each of your REST interfaces' latest and greatest features.\nThe simple plugin interface makes it easy to provide site-specific defaults, and/or support servers that don't use the standard JSON data format.\n\nThe differences are subtle, but the result is magic!\nIn the end, Fermata makes URLs so elegant that there is no need to use — or maintain! — some one-off \"wrapper library\" for every different service.\n\n\n## Magic? ##\n\nFor production apps you'll want this file on your own server, but for quick **in-browser** development you can simply include:\n\n    \u003cscript src=\"https://raw.github.com/natevw/fermata/master/fermata.js\"\u003e\u003c/script\u003e\n\nThis will make Fermata available through a single global variable on the window: `fermata`\n\nTo make Fermata available under **node.js**, simply:\n\n    $ npm install fermata\n\nThe examples below assume you import Fermata's module via `var fermata = require('fermata');`\n\nAlrighty then?\n\n### Let's GET started ###\n\nSo you need to fetch a JSON resource from \"http://youraccount.example.com/api/v3/frobbles\"?\n\nIn Fermata, that's just:\n\n    var site = fermata.json(\"http://youraccount.example.com\");\n    site.api.v3.frobbles.get(function (err, data/*, headers*/) {\n       if (!err) console.log(\"The first Frobble is named\", data[0].name);\n    });\n\n***Fermata turns even URLs themselves into native JavaScript objects!***\nEach path part becomes a property, and so slashes in HTTP paths simply turn into dot operators on JavaScript objects. When you pass a callback function, Fermata uses the last method call as the request's method.\nIt really couldn't *get* much cleaner.\n\nNeed to add query parameters?\n\n    var newAPI = site.api.v4;     // reuses the base URL from above\n    newAPI.frobbles({ perPage: 10, page: myPageNum }).get(myPageHandler);\n\nThis does a `GET` on `http://youraccount.example.com/api/v4/frobbles?perPage=10\u0026page=N`, then asynchronously passes the response data to the `myPageHandler` callback function after automatically converting the raw JSON response text into a ready-to-use object.\n\n### Browser behind the times? ###\n\nUnfortunately, the examples above will only work in node.js, Firefox 4+ and ([soon](http://code.google.com/p/v8/issues/detail?id=1543)) Chrome. But don't worry!\nIn browsers without JavaScript's upcoming [Proxy](http://wiki.ecmascript.org/doku.php?id=harmony:proxies) feature you just need to use parentheses to form URLs, instead of dots:\n\n    var newAPI = site('api')('v4');\n    newAPI('frobbles')({ perPage: 10, page: myPageNum }).get(myPageHandler);\n\nNote how the dot syntax does still work for the final `.get`; Fermata provides fallbacks for the basic HTTP methods until browsers catch up.\n\nThere's no harm in always using parentheses — you'll need them for adding query parameters or avoiding path component escaping anyway. The following all return the same URL string:\n\n    site.api.v4['cannot-dot']({key:\"val\"})()      // requires harmony-proxies support\n    site(['api/v4'])('cannot-dot')({key:\"val\"})()\n    site(['api/v4', 'cannot-dot'])({key:\"val\"})()\n    site('api', 'v4', 'cannot-dot', {key:\"val\"})()\n\n\n\n### PUT ###\n\nOf course, it's also easy to *update* a REST resource with Fermata. Let's set some configuration on \"http://youraccount.example.com/api/v3/whoozits/\u0026lt;ID\u0026gt;/repertoire\":\n\n    (site.api.v3.whoozits[i].repertoire).put({\n        tricks: [1,2,3,4]\n    }, function (error, result) {\n        if (!error) {\n            console.log(\"Configuration accepted for Whoozit #\" + i);\n        } else {\n            console.warn(error);\n        }\n    });\n\n\nYou can send data by passing it to the request method before your callback function (and headers by passing them before the data, but plugins usually handle that for you...).\nJust like Fermata converts the server's raw response into a JavaScript object, Fermata can convert data dictionaries into a variety of raw formats for you: JSON, x-www-form-urlencoded, form-data...\n\n### POST ###\n\nWhen the HTTP documentation says something like, \"To create a Quibblelog, POST it to /utils/quibblelogger\":\n\n    site.utils.quibblelogger.post({ message: \"All your base.\", level: 'stern warning' }, someCallback);\n\nOr for cross-browser support:\n\n    site('utils')('quibblelogger').post({ message: \"All your base.\", level: 'stern warning' }, someCallback);\n\nVoilà!\n\n\n## Plugins ##\n\nEvery time you initialize a new Fermata URL, you do so through a plugin. Fermata provides two built-in (high level) plugins:\n\n1. `json` — initialized with a base URL string, then simply sends a JavaScript object to the server as a JSON string and expects that the server will reply with JSON too. Passes headers (and `X-Status-Code`) as the second callback argument.\n2. `raw` - gives more direct access, whatever text/byte data you pass gets sent verbatim and your callback gets the full response info. This is a handy way to start when adding new plugins (see below).\n\nMany useful REST servers might talk in XML, or require that every request be specially signed with a secret key. Or maybe you just want to build the base URL string from higher-level settings.\n\nEnter custom plugins.\n\nFor example, many of the ideas in Fermata originated in a [node.js Chargify library](https://github.com/natevw/node-chargify) we wrote for their [payment management API](http://docs.chargify.com/api-introduction).\n\nWithout plugins, setting up Fermata to connect to Chargify is totally possible...but kinda ugly:\n\n    var acct = fermata.json({url:\"http://\" + api_key + \":x@\" + site_name + \".chargify.com\"});\n\nWith the old custom Chargify-specific library this was a lot cleaner:\n\n    var acct = chargify.wrapSite(site_name, api_key);\n\n...but of course if we stick with the old custom library, we then have to learn how to use its old custom interface (which was a bit confusing, and didn't support Fermata's dot syntax).\n\nPlugins give us the best of both worlds. Fermata's one magical native API, with useful service-specific smoke and mirrors hiding backstage:\n\n    var acct = fermata.chargify(site_name, api_key);\n    // WHOOHOO NOW WE ARE MONEY MAKING!!!\n\n\nThere's a tiny bit of setup to use plugins from node.js, since Fermata can't *actually* read your mind:\n\n    var f = require('fermata'),\n        _fc = require('fermata-chargify');\n    f.registerPlugin('billing', _fc);        // installs Chargify plugin into our Fermata module, in this case with a less-typical name\n    \n    f.billing(site_name, api_key);\n\nIn the browser, just include any plugins after the script tag for Fermata, and each plugin will be accessible through its default name.\n\nIf such a \"fermata-chargify\" plugin hadn't been published, we could just register an implementation directly:\n\n    fermata.registerPlugin('myChargify', function (transport, name, key) {\n        this.base = \"https://\" + api_key + \":x@\" + site_name + \".chargify.com\";\n        transport = transport.using('statusCheck').using('autoConvert', \"application/json\");\n        return function (req, callback) {\n            req.path[req.path.length - 1] += \".json\";\n            transport(req, callback);\n        };\n    });\n\nNote how `transport` can be extended using other built-in (and/or known-to-be-registered) plugins like `'statusCheck'` and `'autoConvert'`.\nTake a look at the detailed documentation below for tips on publishing plugins that can be easily used from both node.js and the browser.\n\n\n## Complete documentation ##\n\n*NOTE*: this API may continue to [undergo refinement](https://github.com/natevw/fermata/blob/master/ROADMAP.md) until a stable 1.0 release.\n\n\n### URL proxy ###\n\n* `fermata.json(base_url)` - create a URL proxy object for base_url using the built-in 'json' plugin\n* `()` - absolute URL as string\n* `.method([options, [headers, [data,]]] function)` - request targetting callback, returns native [XHR](http://www.w3.org/TR/XMLHttpRequest/#interface-xmlhttprequest)/[http.ClientRequest](http://nodejs.org/api/http.html#http_class_http_clientrequest) object including an `.abort()` method\n* `(string/array...[, object])` - general extension syntax, each type documented below\n* `(object)` - override query parameters (see $key:value details below)\n* `(array)` - extend URL with components (without encoding)\n* `(string[, string...])` - extend URL (with encoding)\n* `[string]` - extend URL (with encoding)\n\nOnce you create a URL wrapper, you can extend it in various ways:\n\n    var api = fermata.json({url:\"http://api.example.com:5984\"});\n    var eg1 = api.database._design.app._view.by_date;\n    var eg2 = api['database']['_design']['app']['_view']['by_date'];\n    var eg3 = api(\"database\")(\"_design\")(\"app\")(\"_view\", \"by_date\");\n    var eg4 = api.database([\"_design/app\", \"_view/by_date\"]);\n\nThese all result in the same API endpoint. We can dump the URL as a string using an empty `()`:\n\n    eg1() === eg2() === eg3() === eg4() === \"http://api.example.com:5984/database/_design/app/_view/by_date\";\n\nAt any point in the process, you can set query parameters (a leading '$' on a key forces JSON stringification of the value):\n\n    var api = fermata.api({url:\"http://api.example.com:5984\", user:\"myuser\", password:\"mypassword\");\n    var faster_queries = api({ stale: 'ok' });\n    var always_include_docs = faster_queries.database({ include_docs: true });\n    var some_app = always_include_docs({ reduce: false })._design.app;\n    var recent_items = some_app(['_view/by_date'], { $endkey: [2011, 4, 1] });\n    recent_items({ limit: 10 })() === \"http://api.example.com:5984/database/_design/app/_view/by_date?stale=ok\u0026include_docs=true\u0026reduce=false\u0026limit=10\u0026endkey=%5B2011%2C4%2C1%5D\";\n\nThen, presto! To make a request on any Fermata URL, simply provide your response callback to the JavaScript method corresponding to the HTTP verb:\n    \n    api.database.get(logging_callback);\n    \n    var data = {};\n    api.database.put(data, logging_callback);\n    \n    var headers = {};\n    api.database.post(headers, data, logging_callback).post(data);\n    \n    // any method name can be used, not just the usual HTTP suspects\n    api.database.copy({Destination: \"new_database\"}, null, logging_callback);\n\n\n### Default plugins ###\n\nFermata provides two high-level plugins:\n\n* `fermata.json(\"http://example.com\")` - send/receive data objects as JSON\n* `fermata.raw({base:\"http://example.com\"})` - expects string request data, returns raw response from the platform-level transport, e.g. `yourCallback(null, {status:418 headers:{}, data:\"...\"})`\n\nThere are also several builtin plugins that are primarily intended for chaining within your own plugins:\n\n* `fermata.statusCheck()` - simple plugin that sets the callback error parameter if the response status code is not 2xx.\n* `fermata.autoConvert([defaultType])` - converts request/response data between native JS data objects and common HTTP content types, using header fields (currently supports: \"text/plain\", \"application/json\", \"application/x-www-form-urlencoded\", \"application/form-data\")\n* `fermata.oauth({client, client_secret[, token, token_secret]})` - adds an OAuth authorization signature to the requests it transports\n\n#### File handling ####\n\nThere are two primary ways a server might allow binary file uploads, and Fermata supports them both:\n\n* RESTful PUT - `fermata.raw(\"http://example.com/some/destination\").put(  nodeBufferOrBrowserFile  , callback)`\n* form upload - `fermata.json(\"http://example.com/some/action\").post({'Content-Type':\"multipart/form-data\"}, {fileField:  form.input.file || {data:nodeBuffer, name:\"\", type:\"\"}  }, callback)`\n\nFor multipart/form-data, send a File/Blob in the browser (Fermata will use the XHR2 FormData to send it). Under node.js, set the field name's value to an object with a `.data` property (and optional `.name` and `.type`). \n\nKeep in mind that Fermata is intentionally designed to convert back and forth between *fully-formed* HTTP data and *in-memory* JavaScript objects, as this is exactly what you want for API communication, documents and photos. For larger files like video data or disk snapshots, you'll want to directly use the streaming features of node.js on the server-side, and provide an interruptible uploader app on the client-side.\n\n\n### Adding plugins ###\n\n* `fermata.registerPlugin(name, setupFunction)` - register a plugin initialization function that receives the platform transport function. This function should set this.base and must return a (typically wrapped/extended) transport function.\n\nWhen the user calls `fermata.yourplugin(...)` to create a new URL wrapper, Fermata sets up a base \"transport\" function for your plugin to use and calls `yourSetupFunction(transport, ...)` with a `this` object set to an empty \"internal URL\" dictionary.\nYou can *chain* plugins with the `.using(name, ...)` method on the transport object you receive. For example, if you want your requests to be processed by  \nThe \"internal URL\" contains a subset of the request parameters (`{base:\"\", path:[\"\"], query:{}}`) that the user will be extending through Fermata's API. You can add defaults in your setup function, setting `this.base` to a specific service's API root for example.\n\nFermata plugins intended for cross-platform use should generally try to follow the following template:\n\n    var fermata;\n    (function () {\n        var plugin = function (transport, baseURL) {                // A plugin is a setup function which receives a base \"raw\" HTTP transport, followed by each argument supplied to the fermata.plugin call.\n            this.base = baseURL;\t\t\t\t    // ...this setup function can set default base/path/query items, then must return the (typically wrapped) request transport function.\n            transport = transport.using('statusCheck').using('autoConvert', \"application/json\");\t\t// any registered plugin may be used\n            return function (request, callback) {                   // request = {base, method, path, query, options, headers, data}\n                request.path[request.path.length - 1] += \".json\";   // NOTE: automatically adding an extension like this breaks the URL string Fermata automatically returns on `()` calls, and so this pattern will likely need to be replaced somehow before v1.0\n                transport(request, function (err, response) {       // response = {status, headers, data}\n                    callback(err, response);\n                });\n            };\n        };\n        \n        // some boilerplate to deal with browser vs. CommonJS\n        if (fermata) {\n            fermata.registerPlugin(\"jsonExtension\", plugin);\n        } else {\n            module.exports = plugin;\n        }\n    })();\n\nAs of Fermata v0.9, this plugin API may still need some improvement (=change) but the basic idea is that Fermata can easily delegate the interesting high-level decisions to logic customized for a particular REST server interface.\n\n\n## Release Notes ##\n\n* 0.1 - initial release\n* 0.2 - Better docs, API tweaks\n* 0.3 - URL.method() redesign\n* 0.5 - Browser support\n* 0.6 - Plugin architecture\n* 0.7 - Plugin chaining\n* 0.8 - Form-based file uploads\n* 0.9 — Expose native request, better errors\n* 0.10 — Auto-convert improvements (headers, 204)\n* 0.11 — Streams/options support, split out plugins, compatibility improvements\n\n## Roadmap ##\n\n* 0.12 - Clean up some loose ends and lingering questions (composable plugins? promises?)\n* 1.0 - [Your feedback needed](https://github.com/natevw/fermata/issues) before the API is finalized!\n\n## MIT License ##\n\nFermata: a succinct REST client.\nWritten by [Nathan Vander Wilt](http://github.com/natevw).\nCopyright © 2011 \u0026yet, LLC.\nCopyright © 2012–2015 Nathan Vander Wilt.\n\nReleased under the terms of the MIT License:\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n","funding_links":[],"categories":["\u003ca name=\"JavaScript\"\u003e\u003c/a\u003eJavaScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnatevw%2Ffermata","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnatevw%2Ffermata","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnatevw%2Ffermata/lists"}