{"id":19151437,"url":"https://github.com/sc5/cerebellum","last_synced_at":"2025-07-24T15:34:06.553Z","repository":{"id":23877603,"uuid":"27256578","full_name":"SC5/cerebellum","owner":"SC5","description":"Cerebellum.js is a powerful set of tools that help you structure your isomorphic apps, just add your preferred view engine.","archived":false,"fork":false,"pushed_at":"2016-10-16T12:18:49.000Z","size":243,"stargazers_count":106,"open_issues_count":5,"forks_count":1,"subscribers_count":29,"default_branch":"master","last_synced_at":"2025-04-14T16:09:23.858Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SC5.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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":"2014-11-28T07:39:16.000Z","updated_at":"2019-07-23T21:50:40.000Z","dependencies_parsed_at":"2022-07-10T10:17:03.198Z","dependency_job_id":null,"html_url":"https://github.com/SC5/cerebellum","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SC5%2Fcerebellum","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SC5%2Fcerebellum/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SC5%2Fcerebellum/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SC5%2Fcerebellum/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SC5","download_url":"https://codeload.github.com/SC5/cerebellum/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252819636,"owners_count":21809057,"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-11-09T08:14:43.586Z","updated_at":"2025-05-07T05:24:51.719Z","avatar_url":"https://github.com/SC5.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Cerebellum.js\n\n[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/SC5/cerebellum?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge)\n\nCerebellum.js is a powerful set of tools that help you structure your isomorphic apps, just add your preferred view engine. Cerebellum works great in conjunction with [React](http://facebook.github.io/react/).\n\nCerebellum is designed for single-page apps that need search engine visibility. Same code works on server and client.\n\n[Introductory blog post, published on February 5, 2015.](http://sc5.io/posts/cerebellum-js-uncomplicated-isomorphic-javascript-apps)\n\n[Cerebellum React helpers](https://github.com/hoppula/cerebellum-react)\n\n### What does it do?\n\n* Fully shared GET routes between server and client\n* Fully shared data stores between server and client, uses [Vertebrae's](https://github.com/hoppula/vertebrae/) Collection \u0026 Model with [Axios](https://github.com/mzabriskie/axios) adapter, so you can use the same REST APIs everywhere.\n* Stores the server state snapshot to JSON. Browser client will automatically bootstrap from snapshot, you don't need to do any extra requests on client side.\n* Uses [express.js](http://expressjs.com/) router on server and [page.js](https://github.com/visionmedia/page.js) router on browser. Both use same [route format](https://github.com/pillarjs/path-to-regexp), so you can use named parameters, optional parameters and regular expressions in your routes.\n* Data flows from models/collections to views and views can dispatch changes with change events. All rendering happens through router.\n* Automatic SEO, no hacks needed for server side rendering.\n* You can easily make apps that work even when JavaScript is disabled in browser\n* Fast initial load for mobile clients, browser bootstraps from server state and continues as a single-page app without any extra configuration.\n* Store state is maintained in [Immutable.js](http://facebook.github.io/immutable-js/)\n* Can be used with any framework that provides rendering for both server and client. [React.js](http://facebook.github.io/react/) recommended, see [examples/static-pages](https://github.com/SC5/cerebellum/tree/master/examples/static-pages).\n\n## Data flow\n\nCerebellum's data flow is similar to [Flux architecture](https://facebook.github.io/flux/) in many ways, but it has some key differences.\n\nDiagram below shows the data flow for client side. Server side is identical, except that there are naturally no interaction triggered updates (green arrows).\n\n\u0026nbsp;\n\n![Cerebellum data flow](http://i.imgur.com/fuxe9Sw.png \"Cerebellum data flow\")\n\n\u0026nbsp;\n\nIn a nutshell, route handler asks stores for data and renders a view with the response.\n\nWhen you want to change things, you send a change event to central Store instance. Store will perform the API call and trigger a success event when it's ready. You can then act on that event by invoking a route handler again.\n\n**All rendering happens through route handlers in Cerebellum.**\n\n### Server and client data flow example\n\n1) User requests a page at **/posts/1**\n\n2) Server or client will ask from router to check for a route that matches \"/posts/1\".\n\n2) Router finds a matching route handler at \"/posts/:id\" and queries Store's post store (which is a model) with parameter `{id: id}`.\n\n3) Store will either invoke `GET /api/posts/1` call or use cached post data (if available).\n\n4) Store returns post data to route handler\n\n5) Route handler passes data to Post view component\n\n6) Server or client renders the returned view component\n\n### Triggering changes with client side change events (green arrows)\n\nViews can trigger change events (`create`, `update`, `delete` or `expire`) with Store's `dispatch` method. Store delegates change event to corresponding store and invokes API request. When request is completed, Store triggers completion event (**create:storeId**, **update:storeName**, **delete:storeName** or **expire:storeName**).\n\nClient can listen for these events. In store event callbacks you can clear caches and re-render current route (or invoke another route handler). There's also an option to automatically clear caches for stores.\n\n### Change events example\n\nLet's say that reader wants to add a comment to a blog post. We want to persist that comment to server and re-render the blog post.\n\n1) When our avid reader clicks \"Send comment\" button, view triggers change event with\n```javascript\nstore.dispatch(\"create\", \"comments\", {id: this.props.id}, {name: \"Hater\", comment: \"This example sucks!\"})\n```\n\n2) Store makes a API call `POST /api/posts/1/comments` to create a new comment with our data\n\n3) Store automatically clears the client side cache for this particular post's comments and triggers `create:comments` event\n\n4) We have a event handler in `client.js` which invokes the **/posts/1** route handler\n\n```javascript\nclient.store.on(\"create:comments\", function(err, data) {\n  // document.location.pathname is the current url,\n  // you could also use \"/posts/\" + data.options.id;\n  client.router.replace(document.location.pathname);\n});\n```\n5) Route handler re-fetches comments collection for this post as its cache was cleared\n\n6) When comments collection has been fetched, the blog post gets re-rendered with the new comment\n\n## Store\n\nStore is responsible for handling all data operations in Cerebellum. It receives change events for individual stores, performs changes and notifies client by triggering success events.\n\nYou register your collections and models (stores) to Store by passing them to server and client constructors in **options.stores** (see **\"Stores (stores.js)\"** section below for more details).\n\nStore will automatically snapshot its state on server and client will bootstrap Store from that state. Client will also cache all additional API requests, but you can easily clear caches when fresh data is needed.\n\n### Models and Collections are immutable\n\nAll your stores are read only, state is stored in [Immutable.js](http://facebook.github.io/immutable-js/). All mutations are handled by Store with **create**, **update**, **delete** and **expire** events.\n\n### Fetching data inside routes\n\nYou can retrieve data from a store using **fetch(storeId, options)**. Route's **this** context includes Store instance (**this.store**) that is used for all data retrieval.\n\nWhen fetching collections, you don't usually need any parameters, so you can do:\n\n```javascript\nthis.store.fetch(\"posts\").then(...);\n```\n\nIf your store needs to fetch dynamic data (models usually do), pass options to **fetch** as second parameter. For example, if you need to fetch model by id, your options would be `{id: id}`.\n\n```javascript\nthis.store.fetch(\"post\", {id: id}).then(...);\n```\n\nYou can also fetch multiple stores at once with **fetchAll**:\n\n```javascript\nthis.store.fetchAll({\"post\": {id: id}, \"comments\": {id: id}}).then(...);\n```\n\n**fetch** returns [Immutable.js Cursors](https://github.com/facebook/immutable-js/tree/master/contrib/cursor). If you're using React, [Omniscient's shouldUpdate mixin](https://github.com/omniscientjs/omniscient/blob/master/shouldupdate.js) works great with these cursors.\n\n### Caches and cacheKeys\n\nStore will populate its internal cache when calling **fetch()**. So when you request same data in different route on client, Store will return the cached data.\n\nYour models and collections can have `cacheKey` method, it defines the path where data will be cached in Store's cache. Store will automatically generate the `cacheKey` for collections and models if you don't provide one. Note that the model needs to be fetched with `id` parameter for automatic `cacheKey` generation.\n\nIf you want to use **fetch** options as part of `cacheKey`, you can access them using `this.storeOptions`.\n\n```javascript\nvar Model = require('cerebellum').Model;\n\nvar Post = Model.extend({\n  cacheKey: function() {\n    return \"posts/\" + this.storeOptions.id;\n  },\n  url: function() {\n    return \"/posts/\" + this.storeOptions.id + \".json\";\n   }\n});\n```\n\n### Triggering changes\n\nPass router's store instance to your view components and\ncall `store.dispatch` with **create**, **update**, **delete** or **expire**.\n\nFor example, you would create a new post to \"posts\" collection by calling:\n\n```javascript\nstore.dispatch(\"create\", \"posts\", {title: \"New post\", body: \"Body text\"});\n```\n\nYou can update a model with:\n\n```javascript\nstore.dispatch(\"update\", \"post\", {id: id}, {\n  title: \"New post\",\n  body: \"New body text\"\n});\n```\n\nStore will then execute the API call to url defined in given store and fire an appropriate callback when it finishes.\n\n### Expiring caches and re-rendering routes\n\nYou can listen for store events in your client.js. Make sure to wait for client's initialize callback to finish before placing event handlers.\n\n```javascript\noptions.initialize = function(client) {\n  var store = client.store;\n  var router = client.router;\n  store.on(\"create:posts\", function(err, data) {\n    console.log(data.store) // =\u003e posts\n    console.log(data.result); // =\u003e {id: 3423, title: \"New post\", body: \"Body text\"}\n    router(\"/posts\"); // navigate to posts index, will re-fetch posts from API as cache was automatically cleared\n  });\n\n  store.on(\"update:post\", function(err, data) {\n    // explicitly clear posts collection in addition to automatically cleared post model\n    // you could also handle this in post model with relatedCaches method\n    store.clearCache(\"posts\");\n    // re-render route, posts collection \u0026 post model will be re-fetched\n    router.replace(\"/posts/\" + data.options.id);\n  });\n\n};\n\ncerebellum.client(options);\n```\n\n## Options\n\nOptions below are processed by both server.js \u0026 client.js, it usually makes sense to create a shared `options.js` for these shared options.\nServer and Client specific options are documented in their respective sections in documentation.\n\n### Options (options.js)\n\nexample `options.js` with default values, these options are shared with client \u0026 server constructors.\n\n```javascript\nvar stores = require('./stores');\nvar routes = require('./routes');\n\nmodule.exports = {\n  routes: routes,\n  storeId: \"store_state_from_server\",\n  stores: stores,\n  initStore: true\n};\n```\n\n#### routes\n\nObject of route paths and route handlers. Best practice is to put these to their own file instead of bloating options.js, see **\"Routes (routes.js)\"** documentation below.\n\n#### storeId\n\nDOM ID in index.html where server stores the JSON snapshot that client will use for bootstrapping Store.\n\n#### stores\n\nObject containining store ids and stores. Best practice is to put these to their own file as well, see **\"Stores (stores.js)\"** documentation below.\n\n#### initStore\n\nInitialize store for route handlers (**this.store**). Defaults to true. Disable if you want to perform the data retrieval elsewhere. For example, when using framework like [Omniscient](https://github.com/omniscientjs/omniscient) you would perform the data fetching in server.js \u0026 client.js and pass cursor to immutable data structure in routeContext.\n\n#### routeHandler\n\nMethod that decides how route handlers are being called. Default behaviour is that route handler gets applied with route params. With React, you can use `cerebellum-react/route-handler`.\n\n\n### Routes (routes.js)\n\nExample `routes.js`\n\n```javascript\nvar Index = React.createFactory(require('./components/index'));\nvar Post = React.createFactory(require('./components/post'));\n\nmodule.exports = {\n  '/': function() {\n    return this.store.fetch(\"posts\").then(function(posts) {\n      return {title: \"Front page\", component: Index({posts: posts})};\n    });\n  },\n  '/posts/:id': function(id) {\n    return this.store.fetch(\"post\", {id: id}).then(function(post) {\n      return {title: post.get(\"title\"), component: Post({post: post})};\n    });\n  }\n};\n```\n\nYour routes will get picked by **client.js** and **server.js** and generate exactly same response in both environments (provided you implement your **render** functions in that manner).\n\nYour route handlers can return either promises or strings, cerebellum will handle both use cases.\n\nIn route handler's **this** scope you have **this.store** which is the reference to Store instance. It contains all your stores and **fetch** for getting the data.\n\nOn the server Store is initialized for every request and on the client it's created only once, in the application's initialization phase.\n\nServer serializes all Store content to a JSON snapshot at the end of a request and client then deserializes that JSON and bootstraps itself.\n\n### Stores (stores.js)\n\nExample `stores.js`\n\n```javascript\nvar PostsCollection = require('./stores/posts');\nvar AuthorModel = require('stores/author');\n\nmodule.exports = {\n  posts: PostsCollection,\n  author: AuthorModel\n};\n```\n\nReturn an object of store ids and stores. These will be registered to be used with Store.\n\n### Server (server.js)\n\nServer is responsible for rendering the first page for the user. Under the hood server creates an express.js app and server constructor returns reference to that express app instance.\n\nServer is initialized by calling:\n\n```javascript\nvar app = cerebellum.server(options, routeContext);\n```\n\nIf you want to customize route handler's **this** context, pass your own context as **routeContext**. routeContext must be an object or a promise that resolves with an object. Cerebellum will automatically add **store** to the context. If you don't want this, use the **initStore: false** option.\n\nSee **\"Options (options.js)\"** section for shared options **(routes, storeId, stores ...)**, options below are server only.\n\n#### options.render(document, options={}, request={params: {}, query: {}})\n\nRoute handler will call server's render with document, its options and request object. document is a cheerio instance containing the `index.html` content.\n\nRender method is invoked with route handler's this context.\n\nRender method can return either a string or a promise resolving with string.\n\nExample server render function:\n\n```javascript\noptions.render = function(document, options) {\n  document(\"title\").html(options.title);\n  document(\"#app\").html(React.renderToString(options.component));\n  return document.html();\n}\n```\n\n#### options.staticFiles\n\nPath to static files, `index.html` will be served from there.\n\n```javascript\noptions.staticFiles = __dirname + \"/public\"\n```\n\n#### options.middleware\n\nArray of middleware functions, each of them will be passed to express.use().\nYou can also include array with route \u0026 function.\n\n```javascript\nvar compress = require('compression');\nvar auth = require('./lib/auth');\noptions.middleware = [\n  compress(),\n  [\"/admin\", auth()]\n];\n```\n\n#### options.entries\n\nYou can define entry files per route pattern, e.g. you want to include different .js bundle \u0026 .css in admin section.\nIf entries option is not defined, server will default to `path.join(options.staticFiles, \"index.html\")`.\nIf routes object is empty or any route pattern does not match, server will default to `path.join(options.entries.path, \"index.html\")`.\n\n```javascript\noptions.entries = {\n  path: \"assets/entries\",\n  routes: {\n    \"/admin\": \"admin.html\"\n  }\n};\n```\n\n#### useStatic\n\nInstance method for cerebellum.server instance. Registers express.js static file handling, you usually want to call this after executing cerebellum.server constructor, so Cerebellum routes take precedence over static files.\n\n```javascript\nvar app = cerebellum.server(options);\napp.useStatic();\n```\n\n### Client (client.js)\n\nClient is responsible for managing the application after getting the initial state from server.\n\nClient is initialized by calling:\n\n```javascript\ncerebellum.client(options, routeContext);\n```\n\nIf you want to customize route handler's **this** context, pass your own context as **routeContext**. routeContext must be an object or a promise that resolves with an object. Cerebellum will automatically add **store** to the context. If you don't want this, use the **initStore: false** option.\n\nSee **\"Options (options.js)\"** section for shared options **(routes, storeId, stores ...)**, options below are client only.\n\n#### options.render(options={}, request={params:{}, query:{}})\n\nRoute handler will call client's render with its options and request object when it gets resolved.\n\nRender method is invoked with route handler's this context.\n\n```javascript\noptions.render = function(options) {\n  document.getElementsByTagName(\"title\")[0].innerHTML = options.title;\n  return React.render(options.component, document.getElementById(\"app\"));\n};\n```\n\n#### options.initialize(client)\n\nThis callback will be executed after client bootstrap is done.\nReturns client object with **router** and **store** instances.\n\nYou can listen for store events, expire store caches and render routes here.\n\n```javascript\noptions.initialize = function(client) {\n  React.initializeTouchEvents(true);\n};\n```\n\n#### options.autoClearCaches\n\nWith this option Store will automatically clear cache for matching cacheKey after **create**, **update** or **delete**. Defaults to true.\n\n```javascript\noptions.autoClearCaches = true;\n```\n\n#### options.instantResolve\n\nWith instantResolve you can make the **fetch** promises to resolve immediately with empty data. When **fetch** calls actually finish, they will fire **fetch:storeId** events that you can use to re-render the routes. This is really useful when you want to render the view skeleton immediately and show some loading spinners while the data retrieval is ongoing. instantResolve will only affect client side **fetch** calls, it has no effect on server side.\n\n```javascript\noptions.instantResolve = true;\n```\n\n## Models \u0026 Collections\n\nCerebellum comes with models \u0026 collections from CommonJS version of Backbone, [Vertebrae](https://www.npmjs.com/package/vertebrae).\nHowever, you could roll your own implementations as Cerebellum's Store has no dependencies to any model or collection libraries.\n\n### Model options\n\n[Model documentation for Backbone](http://backbonejs.org/#Model) applies to Cerebellum models, but there are some extra options that can be utilized.\n\n#### cacheKey\n\nOptional property or method, should return the cache key for model. This will be generated automatically if not provided (you must provide storeOptions.id for automatic generation).\n\n```javascript\ncacheKey: function() {\n\treturn \"myCustomPrefix:\"+this.storeOptions.id; // defaults to this.storeOptions.id\n}\n```\n\n#### relatedCaches\n\nWith **relatedCaches** method you can define additional cache sweeps that happen when model's cache gets cleared.\n\n```javascript\nrelatedCaches: function() {\n\treturn {\"comments\": this.storeOptions.id};\n}\n```\n\n### Collection options\n\n[Collection documentation for Backbone](http://backbonejs.org/#Collection) applies to Cerebellum collections, but the are some extra options that can be utilized.\n\n#### cacheKey\n\nOptional property or method, should return the cache key for collection. This will be generated automatically if not provided.\n\n```javascript\ncacheKey: function() {\n\treturn \"myCustomPrefix:\"+this.storeOptions.id; // defaults to this.storeOptions.id\n}\n```\n\n#### relatedCaches\n\nWith **relatedCaches** method you can define additional cache sweeps that happen when collection's cache gets cleared.\n\n```javascript\nrelatedCaches: function() {\n\treturn {\"posts\": \"/\"};\n}\n```\n\n## Usage with React\n\nCerebellum works best with [React](http://facebook.github.io/react/).\n\nReact makes server side rendering easy with **React.renderToString** and it can easily initialize client from server state. All code examples in this documentation use React for view generation.\n\n## Running tests\n\nStart test server for client tests\n\n    npm start\n\nRunning client tests (requires test server to be up and running)\n\n    npm run test_client\n\nRunning server tests\n\n    npm run test_server\n\nRunning all tests (server \u0026 client)\n\n    npm run test\n\n## Browser support\n\nInternet Explorer 9 and newer, uses ES5 and needs pushState.\n\n## Apps using Cerebellum\n\n### [urls](https://github.com/SC5/cerebellum-urls)\nSample app for saving \u0026 tagging urls, demonstrates CRUD \u0026 authorization\n\n### [Cereboard](https://github.com/hoppula/cereboard)\nSample app on how to declare needed data directly in view components and keep the router clear of store.fetch calls.\n\n### [LiigaOpas](http://liiga.pw)\nStats site for Finnish hockey league (Liiga)\n\nSource available at: [https://github.com/hoppula/liiga](https://github.com/hoppula/liiga)\n\n## License\nMIT, see LICENSE.\n\nCopyright (c) 2014-2015 Lari Hoppula, [SC5 Online](http://sc5.io)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsc5%2Fcerebellum","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsc5%2Fcerebellum","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsc5%2Fcerebellum/lists"}