13 minutes read
The moral of the story is: Take it easy, there’s a plenty of time to learn everything. Even Category Theory, Lemmas, Metaprogramming, Point-Free Style and other more advanced stuff.
The Hoodie Bindings for PureScript are here.
Hoodie: going offline with no backend!
Well, the title isn’t something you’d like to use as an advertisement for you latest Web-App, isn’t it? OK, I was a little bit unfair because we’ll not go offline with Hoodie but actually keep the app alive no matter if there’s a network connection available or not. And we do have a fully-fledged backend but simply don’t want to poison our frontend with it’s specifics. We just code the frontend as if there were no backend at all. In Hoodie’s case the backend is powered by the NoSQL database CouchDB, so you have to install it as a prerequisite. Hoodie itself can be installed as an npm package via:
npm install -g hoodie-cli
However, I’d like to point to the excellent docs of the project for more detailed info. Every Hoodie app comprises of certain settings in package.json, like plugins, dependencies etc. To complete these tasks without headache you’ll use the hoodie-cli we mentioned previously. Give your app a name and wait for Hoodie to create the whole structure for you.
hoodie new myOfflineFirstApp
Then go to the newly created directory and push the start button by typing in:
After a few moments you’ll see a message like this:
Please note that the output here is from Windows and on this OS you’ll run into a problem if your CouchDB installation is not on the default path (C:\Programs…\CouchDB… etc). As you can see in the screenshot my CouchDB is located under C:\bin and the app still runs. This is because I’m using a patched version of the npm package called multicouch that takes care of localizing CouchDB installations. There’s an open issue regarding this problem and my pull request is here. I have no clue if it works on other Windows machines so there’s no guarantee that it’ll work on yours. However, the more people test it the better for us all. If you’re under Linux/OSX you don’t have to take care of this problem as it only affects Windows.
After a successful start from the console Hoodie automatically opens up your default browser with the ubiquitous “ToDo” WebApp. The original app looks like this but in this article we’ll be using a modified version based on PureScript showing us some of the currently implemented Hoodie-API calls.
Using Hoodie with PureScript
Before we can use any of the available APIs we have to instantiate Hoodie first.
Here we use hoodie API and provide Nothing as the only parameter because we want to use Hoodie at our current server. If we want to provide it on some other location we’d have to give a URL instead of Nothing. The second line is for defining an EventHandler to listen for certain changes in our Store. The Binding-API calls expect you to provide a valid Hoodie instance as the last parameter. In our case this is the myHoodie variable (or binding in PureScript lingo).
Now we want to define a document and put it on the Store.
To add the document to our local database we use the add function.
Again, we have to provide a proper document type, its properties and options. The two callbacks are not mandatory but will be used here to complete the Promise returned from Hoodie. Had we decided to send a Nothing instead of callbacks purescript-hoodie would’ve automatically provided two ’empty’ callbacks to handle the Promise.
Now, open your console and refresh the page.
And before you throw tomatoes at me: refreshing the page isn’t mandatory! We do this here just because we’re using PureScript without any additional UI-Layers or frameworks. So, there’s simply no two-way data-binding, virtual-dom or anything similar. We’re just testing the Hoodie API though PureScript’s lenses. No, dear Haskellers, not the lenses! I’m not a native speaker so my vocabulary isn’t filled with poetic metaphors. It’s actually a wonder that I’m able to write in (broken?) English as I’m not using it as a spoken language. The last time I used it (at work) is more than three years ago. No wonder I still have problems with Present Perfect Tense. Sorry.
Of course, we can search for documents in our database. There are several APIs and here we’re searching for all documents of type “todo”.
findAll needs a type and the two callbacks for done and fail parts of the JS-Promise. The last parameter, as usual, is our Hoodie instance. You’ve surely noticed that we’re using the same callback for both possible outcomes (done/fail). It doesn’t have to be like this but in our case we just want to write the result to the console without any special treatment of return values. Here’s how our callback’s definition looks like:
It accepts any type of data and forwards it to logRaw from API.Hoodie.purs. This function writes everything to the console without any special formatting. A poor man’s debugger. 🙄
The overall strategy is very simple, I think. You use a certain function to do something with a document or a collection of them. The needed parameters are either document properties or the general type of the document you want to work with. The Hoodie instance is mandatory and always the last of the parameters.
Now the question is, what happens to the documents if there’s no connection at all? Well, this where the ‘real’ Hoodie kicks in. You simply don’t have to care about the connectivity as Hoodie will always first save your changes to the local storage in your browser and frequently try to find out if there’s a connection available. And when it finally connects to the backend it’ll immediately update it to reflect the latest changes from your local store. You can also open several browser sessions with the same user logon. When you update a document on one of the browsers the change will immediately reflect on others and vice-versa.
To be honest, the above examples are not very good PureScript idioms. Also, I haven’t touched other aspects of Hoodie like accounts, events, plugins etc. Handling document types by littering the code with countless “todo”-parameters isn’t the best way of maintaining type-safety, in my opinion. It would’ve been better to use constructs like ADTs.
Anyway, I hope I could convince you that there’s no need to stick with only a single language. We’re constantly searching for best possible solutions of such important problems like keeping the App alive without network connection, changing data in a type-safe manner, simultaneously reflecting the storage state to all connected clients and other unavoidable side-effects.
Opening an app for public consumption is actually putting it into a world full of side-effects that could possibly hinder your customers from consuming it at all.