/ Development

Cerebellum.js – uncomplicated isomorphic JavaScript apps

Isomorphic applications are a hot topic at the moment. The rise of React.js has brought a lot of interest on the topic and there’s even a dedicated website for Isomorphic JavaScript.

While some people disagree with the terminology, isomorphic is already an established term for applications that share most of their functionality between server and client.

Cerebellum.js (npm) is a library of tools that work together seamlessly to provide a good base for your isomorphic JavaScript application.

What is Cerebellum.js?

With Cerebellum you can achieve fast initial load for mobile clients. Browser bootstraps from server state and continues as a single-page app without any extra configuration. Being able to render the same content in server and client is also a huge boost for search engine visibility as all your content is instantly crawlable.

Cerebellum was born as a result of my experimentations on how to share the same routes between server and client. Piece by piece it has advanced to a state where it’s fully usable for many different usage scenarios.

I’ve developed multiple apps over the years with Backbone.js and it was my weapon of choice framework until I discovered React. However, React handles only the view layer and just replacing Backbone.View with React will not fully utilize React’s strengths and leads to a complex app structure. After considering different options, I didn’t want to give up using Backbone’s excellent collections and models, so I included them as default data structures / API interfaces for Cerebellum.

Under the hood

Cerebellum’s three main pillars are Server, Client and Store.

Server

Server is an express.js app that renders given route and snapshots Store’s state to JSON. You pass your shared routes to Server constructor and you can then add your API routes to the server instance returned from constructor.

Client

Client bootstraps Store’s state from initial JSON and renders given route. When using React, it will do a diff between DOM & virtual DOM, client’s render does not have to touch DOM at all as nothing has changed.

Store

Store is a central dispatcher for all data related operations. Server and client initialize a single instance of Store, it’s created for every request on server side and only once on client side.

You register all your available models and collections with the Store instance, giving them unique names (required for caching).

All data retrieval happens through collections and models with included Axios adapter, which enables you to call the same APIs both on server and in browser with the same interface.

Data flow

Cerebellum has unidirectional data flow, everything gets rendered through the router with route handlers. What this means in practice is that your route handlers define what data they need and render the view component when all required data has been fetched.

Cerebellum caches all fetch requests automatically. All data you fetch on server side gets transferred to client as a JSON snapshot, so you won’t need to fetch the same data again on client side.

When you need to change data, you send a change event (create, update or delete) to Store instance that processes the corresponding API request for given collection or model. When the API request finishes, Store triggers a success event that you can react to on your client. Usually you want to clear the cache for modified collection/model and re-render your view with new data by triggering a route handler.

 

Cerebellum data flow

 

Cerebellum’s data flow is heavily inspired by Flux architecture. The biggest difference to Flux is that you pass the data down the tree as props instead of each component having their local state updated by events. For situations where you need the extra level of decoupling that Flux provides, you can replace the built-in Store with any Flux implementation. Cerebellum is designed to be as non-restrictive and decoupled as possible.

Cerebellum comes with Vertebrae’s collections and models. Vertebrae is a CommonJS version of Backbone. However, you are free to use any collection & model implementation you like, as Cerebellum’s Store has no direct dependencies on any data library.

Shared routes

GET routes are 100% shared between server and client and you’ll only need POST, PUT and DELETE in your APIs. Cerebellum uses express.js‘ router on the server and page.js‘ router on the client. They both use path-to-regexp module for parsing the routes, so you can use named parameters, optional parameters and regular expressions.

Check out this routes configuration for a real example.

Use any (isomorphic) view layer

Cerebellum is view layer neutral, so feel free to implement your client’s and server’s render methods with the framework of your choice. Obviously it wouldn’t make sense to re-render everything with traditional view layer like Backbone’s View, but when using virtual DOM based solution like React, you can safely invoke your route handlers to re-render whenever you like.

Client render example

Server render example

Future plans

Recently I’ve experimented with integrating Omniscient.js with Cerebellum. That integration still needs some helpers to reduce the amount of code needed for immutable data updates and better way to share the initial data fetch code between server and client. Using cursors and sub-cursors to provide data for a tree of components, with a listener at the top level, is certainly a great pattern and it will be greatly helpful in certain types of applications.

I’ve also been thinking about adding an option to automatically convert the collection & model data to Immutable.js structures after fetch. That way view components could perform fast equality checks for passed in data and they would have all the Immutable’s excellent Map and List methods available.

In the wild

Cerebellum has already been used in internal projects, client projects and it also powers LiigaOpas, which is an open sourced stats site for Finnish Hockey League.

Check out the extensive documentation at Cerebellum’s GitHub repository. We’re always interested in more contributors, so if you have any questions or ideas, don’t hesitate to open an issue.

Learn our ways of working.