A Painkiller for Isomorphic Data Fetching.

Marco Romero
6 min readDec 7, 2015

--

Most you reading this should be by now very familiar with the concept of isomorphic JavaScript apps. You know, those that can be rendered in the server and in the browser.

I have been following and prototyping with that topic since 2013, and so far, most articles and documentation focus on things like routing and rendering.

But rendering is just one chapter of the story. What about the data that needs to be shown on screen? How do you simplify the consumption of an API in the browser and in the server? What about isomorphic models?

Surfing on the interwebz I’ve found very few reasonable simple examples like this one. And of course we have Relay and React-Transmit that offer an approach to solve this issue for React apps. Still, it is not the answer I am looking for. Why? Well, because React.

Don’t think this the wrong way. I love React, I use it in my personal projects. But I also like freedom, and the idea of tying my application to a framework just doesn’t click with my philosophy of development. I wanted something agnostic to any framework.

A Morphine Injection

A couple of months ago I was giving a talk about isomorphic javascript with React and Rx in the local JavaScript community meeting. And during the pizza-networking time, one of the guys(David Oliveros) that listened to my talk approached and told me that he wrote a small library to do isomorphic models… And that they were using it in production at the startup he is currently working.

My reaction. Django

That’s how I was introduced to isomorphine, a webpack loader and NodeJS library that allows you to use your data model methods the same way in the browser and in the server.

Let me show you…

Set it up

npm install express webpack isomorphine

The minimum required dependencies to start working with isomorphine are a connect-like server(like Express), webpack, and of course the isomorphine loader.

For organising our code for this particular scenario I recommend the following directory structure:

APP_NAME
|--app <--- isomorphic code goes here.
|--models
|----Entity1
|--------create.js
|--------update.js
|--------read.js
|--------OtherFunctionality.js
|----index.js
|--server
|----index.js
|package.json
|webpack.config.js
|index.js

Now set up the webpack.config.js

var webpack = require('webpack');

module.exports = {
entry: {
...
},
output: {
...
},
module: {
preLoaders: [
{
loaders: ['isomorphine']
}
]
}/** UPDATE: This is no longer needed in latest version,
plugins: [
new webpack.DefinePlugin({
'process.env': {
ISOMORPHINE_HOST: '"http://myhost.com"',
ISOMORPHINE_PORT: 8000
}
})
]**/
};

Please do note the double quotes(“”) on the ISOMORPHINE_HOST environment variable. I spent an hour or so trying to find why the set up was breaking due this.

UPDATE: This previous specification of port and host is no longer needed in the latest version. Please refer to the repos README for more details.

Now that we have everything configured. Lets write some code to actually take advantage of the library.

Use it now

Most of the apps I have built using express, or any other server framework, include the data models in the server folder. It made a lot of sense, after all, it is the only place where you actually get access to your data models by making request to a database or to a third party web service, or just by having access to the file system.

But now, since we will have to access them from 2 different places(Client & Server) it is a good idea to move the data layer outside of the server because that would helps us to separate responsibilities.

Now here is the interesting part. Lets create an entity and add some functionality:

As you see, there is nothing different from a regular JavaScript function. It takes some parameter, performs some logic, and returns a result.

The real magic resides in the /models/index.js file:

// /models/index.jsimport isomorphine from 'isomorphine';export default isomorphine.proxy();

At this point your models are ready to be used by any server side code. Just import the models like so:

And in your app/index.js (this is what will be called in the client side)

As I am sure you noticed, we are calling the methods the same way. This is really isomorphic data fetch.

Finally just bundle and run your server:

// package.json{
...
"scripts":{
"bundle" : "webpack",
"start": "node server/index"
}
}
$ npm run bundle && npm start

What kind of sorcery is this?

Well, it’s not that complicated. Isomorphine has a very simple API. Just the proxy() method. This method reads the directory where it seats and builds an object map with every file inside imported as a function method. So this:

|----Entity1
|--------create.js
|--------update.js
|--------read.js
|--------OtherFunctionality.js

becomes this:

{ 
Entity1: {
create: [Function],
update: [Function],
read: [Function]
},
router:[Function]
}

Additionally, you will notice it adds a router method. This one is used to map and listen to the calls from the client side.

The last part of the trick it’s pretty neat.

If you inspect your bundle.js file, you will notice that the model’s code is different that what we wrote. That is because isomorphine webpack loader prevents the model’s source code from being added to the bundle, and instead, it replaces the implementation code with ajax calls to the endpoint routes provided by the proxy() router (the one I mentioned above) leaving the signature intact, so you can use the API the same way you would in the server. Awesome right?!

The main advantage of not bundling your API code is security. Where and How you obtain your data stays a secret to the client, thus allowing you to make any validation on sensitive areas of your models without exposing your logic.

Speaking of validation. Isomorphine also provides the express request object as part of the context of the function methods through this.

const read = (param, callback)=>{    let clientCall = this.xhr && this.req; //    console.log(clientCall);
}

Anything normally attached to the request(cookies, sessions, headers,params) will be in that context property. You can use it to validate the API call before continuing your process.

Final Thoughts

Playing around with isomorphine was a great learning experience. I believe this library is a good step in the right direction to help developers to create true isomorphic/universal JavaScript applications.

In short, this is what I conclude about this experiment:

  • The API is dead simple and that is great. One method to rule them all.
  • The conventions of how you should organise your Data Models was something I was not used to. Nevertheless, it has its good parts. I found out that by organising your code this way, you enforce abstraction of your data fetching strategy that also helps testability. Separation of concerns FTW!.
  • The fact that your models are not bundled is a great plus. This keeps your bundle lighter and also helps to protect you app from being reverse engineered.
  • It would be awesome if instead of callbacks, the API methods were able to support ES6 promises. I’m willing to work on a PR to make this happen. UPDATE: Latest version now supports ES6 Promises and Async!
  • Some example apps for you to take as reference: TodoApp, Github Profile Fetch

If you found this article useful please recommend. Also I’d love your feedback on the matter. If you happen to play with isomorphine too, please share your experience and/or issue a bug or feature request to the author’s library if you think convenient.

Thanks for reading this far.

--

--