{"id":15515905,"url":"https://github.com/bfirsh/otter","last_synced_at":"2025-04-23T03:26:57.422Z","repository":{"id":4805086,"uuid":"5958427","full_name":"bfirsh/otter","owner":"bfirsh","description":"A server that runs your client-side apps.","archived":false,"fork":false,"pushed_at":"2013-05-21T16:36:03.000Z","size":894,"stargazers_count":82,"open_issues_count":3,"forks_count":2,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-04-21T13:04:58.124Z","etag":null,"topics":["javascript","server-side-rendering","single-page-app"],"latest_commit_sha":null,"homepage":"","language":"CoffeeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bfirsh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2012-09-25T23:56:59.000Z","updated_at":"2023-07-05T16:44:57.000Z","dependencies_parsed_at":"2022-08-19T01:41:51.156Z","dependency_job_id":null,"html_url":"https://github.com/bfirsh/otter","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bfirsh%2Fotter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bfirsh%2Fotter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bfirsh%2Fotter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bfirsh%2Fotter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bfirsh","download_url":"https://codeload.github.com/bfirsh/otter/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250362952,"owners_count":21418171,"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":["javascript","server-side-rendering","single-page-app"],"created_at":"2024-10-02T10:04:50.349Z","updated_at":"2025-04-23T03:26:57.404Z","avatar_url":"https://github.com/bfirsh.png","language":"CoffeeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"Otter\n=====\n\nA server that runs your client-side apps.\n\nWait, what?\n-----------\n\nWeb apps are shifting client-side. More and more logic is moving from server to client, but that often ends up with your server just serving up a JSON API and a blank index.html which gets filled with content client-side. This sucks for two reasons:\n\n - It's slow. You must make at least two round trips to the server before any content is displayed.\n - Your content is invisible to search engines, curl, browsers with JavaScript disabled, etc.\n\nA typical solution to this problem is to write server-side code that renders some of what the client would render. This works, but now you're writing everything twice.\n\nBut what if we could use client-side APIs on the server? What if we could generate HTML with the DOM and jQuery? Make HTTP requests with XMLHTTPRequest? This would mean you could run your client-side code on the server without modification.\n\nOtter does just that. When a client makes a request to Otter, it loads up your app inside Zombie.js, a Node implementation of the browser APIs. After it has finished loading a page, it renders the DOM to a string and sends it back to the client.\n\nIf the client supports JavaScript, they can start up the client-side router, and it's business as usual. If the client can't run JavaScript, they just see the content as a normal web page.\n\nYou've now got an application which behaves like it was written in a server-side language, but actually shares its code with the client.\n\n\n### So I'll have to write my server in JavaScript?\n\nNope! You won't have to modify your server.\n\nOtter is a stand-alone server which runs client-side JavaScript. The app running inside Otter talks to the same server that your browser does. If you've got a Backbone app, it talks to the same server which serves JSON for your models.\n\n### So I'll have to write my client-side code with Backbone?\n\nNope! Unlike other techniques that allow running the same code on the server and in the browser, Otter is framework agnostic. It's an implementation of the browser APIs on the server, so almost any code which runs inside the browser will run inside Otter.\n\n### Is it secure?\n\nOtter is far more paranoid than a browser, so you won't trip up on common client-side vulnerabilities. \n\nAll code runs inside a sandbox. Node's sandboxes aren't perfect though – you must still ensure that you always run trusted code – but to help with that, Otter only allows HTTP requests to the local server by default. If you wish to load data from other domains, you must allow them explicitly.\n\nInstall\n-------\n\n    $ sudo npm install -g otter\n\n(Or use your preferred way of installing npm packages.)\n\nGetting started\n---------------\n\nOtter is, at a basic level, an HTTP server. Pointed at a directory, it will serve the files inside it. It only starts doing clever things when it is asked to serve a file that doesn't exist.\n\nInstead of showing a 404, it will open the file `index.html` in Zombie.js. When Zombie.js finishes loading the page (all Ajax requests have finished, etc) it sends `document.outerHTML` as the HTTP response back to the browser.\n\nTo demonstrate how Otter works, an example app is included in `example/`. It's a simple Twitter client written in Backbone. The first page load runs server-side, then the client instantiates Backbone's router to handle subsequent requests. It uses [backbone-otter](https://github.com/bfirsh/backbone-otter) to handle caching between server and client.\n\nRun Otter on that directory, allowing requests to `api.twitter.com`:\n\n    $ otter -a api.twitter.com example/\n    Server started on port 8000.\n\nPoint your browser at [http://localhost:8000](http://localhost:8000).\n\nUsage\n-----\n\n    $ otter [options] \u003cpath\u003e\n\nOtter is passed a path to a directory which is expected to contain an `index.html` file. It takes these options:\n\n#### -a `host1,host2`\n\nA comma-separated list of hosts to allow connections to (e.g. `api.example.com,api.twitter.com`). By default, Otter will not allow connections to any host except itself. If you want to allow Ajax connections to your API, for example, you will need to add it to this list.\n\n#### -p `port`\n\nThe port to listen on. Default: 8000\n\n#### -w `num`\n\nThe number of worker processes to spawn, defaulting to the number of CPUs.\n\nAPI\n---\n\nOtter provides an API to use inside your apps, exposed as `window.otter` on both the server and the client.\n\n### `window.otter.isServer`\n\n`true` or `false`, whether or not the page is running on the server or client.\n\n### `window.otter.cache`\n\nAn object that can be used to pass data from the server to the client.\n\nWhen your app is running inside Otter, it can set keys on this object. The object is serialised to JSON and injected into the top of the page sent to the browser. When the browser opens the page, the value of `window.otter.cache` is restored from the serialised JSON.\n\nSee the section *Resuming your app on the client* for an example of how this object can be used.\n\n\nWriting an app for Otter\n------------------------\n\nWriting an app for Otter is almost the same as writing a single-page app just for the browser, but there are a few things you need to take into account.\n\n### Running code only on the server or the client\n\nSome code only makes sense to run on the client; for example, handling user interactions, drawing to canvases etc. You can use the `window.otter.isServer` variable to check if you are running on the server:\n\n```javascript\nif (window.otter \u0026\u0026 window.otter.isServer) {\n    // Running on the server\n}\nelse {\n    // Running in the browser\n}\n```\n\n### Resuming your app on the client\n\nReinstantiating the app on the client after the app has been run on the server, in order that it can handle user interaction and route future pages, is a tricky problem. Otter is framework agnostic, so it doesn't prescribe a solution. It does, however, provide tools, such as `window.otter.cache` (see [API](#api)) and [backbone-otter](http://github.com/bfirsh/backbone-otter) if you're using Backbone.\n\nThe brute-force approach is to reroute the URL, completely rebuilding the page client-side. This isn't as scary as it sounds if you cache the data that was fetched on the server, but the downsides of this are obvious inefficiency, and possibly odd side-effects of loading in a new copy of the DOM, if the user has already interacted with the initial DOM.\n\nIf you want a more efficient solution, we can work smarter. We can cache data that Otter fetches from your API, and pass it on to the client. If we then rebuild a set of models and views attached to the correct DOM elements that the server has generated, we can \"boot up\" the application again without having to regenerate the HTML. In Backbone, this is a matter of only rendering a view if it hasn't already been rendered by the server. See the included example app for a simple demonstration of how this can be done.\n\nI am working on some [Backbone tools for Otter](https://github.com/bfirsh/backbone-otter) to make this process easier.\n\n### Redirects\n\nOtter will intercept changes in location (e.g. setting `window.location`) and immediately respond with a 302 redirect. This causes the browser to change location as you would expect if the code was running client-side.\n\n### Cookies\n\nCookies in an HTTP request to Otter will be passed through to `document.cookie` so that they are available inside Otter. Similarly, any cookies set in `document.cookie` will be sent back with the HTTP response to the browser.\n\nDeployment\n----------\n\nOtter apps can be deployed to Heroku. Within your app, you'll want to create a `package.json` file that specifies Otter as a dependency:\n\n```json\n    {\n      \"name\": \"otter-example\",\n      \"version\": \"0.1.0\",\n      \"dependencies\" : {\n        \"otter\": \"*\"\n      }\n    }\n```\n\nYou'll also need a `Procfile` to tell Heroku how to run Otter, assuming `index.html` and the rest of your app is in a directory called `app/`:\n\n```\n    web: ./node_modules/.bin/otter -p $PORT app/\n```\n\nThen [deploy to Heroku as usual](https://devcenter.heroku.com/articles/nodejs).\n\nTake a look at [Hao-kang Den's kawauso](https://github.com/hden/kawauso) for a more complete example.\n\n\nExtending Otter\n---------------\n\nInstead of running Otter standalone, it can also be extended by using it as part of a Node.js app. See `lib/otter/server.coffee` for the Express app that is used internally. The renderer used in this file is available externally as `require('otter').renderer`.\n\n\nRunning the test suite\n----------------------\n\n    $ npm test\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbfirsh%2Fotter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbfirsh%2Fotter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbfirsh%2Fotter/lists"}