This week of Hoodie development is sponsored by The Hoodie FirmSponsor us!

Very fast app development

We want to enable you to build complete web apps in days, without having to worry about backends, databases or servers, all with an open source library that's as simple to use as jQuery.

That's why we're making Hoodie.

Hoodie is an Offline First and noBackend architecture for frontend-only web apps on the web and on iOS.

People really seem to like it:

See what others are tweeting about Hoodie or browse through our personal favourite user quotes.

Features

  • Complete!
  • We're on it
  • Planned

Who is it for?

  • For you!
  • You're next
  • Planned

Quick intro

If you've got the time: there's also a slightly older long intro, with more technical background info.

So how simple is Hoodie? Here are some simple code fragments from a hypothetical task list app:

1. Instantiating your Hoodie

hoodie = new Hoodie();

Just one line of JS to get started with Hoodie.

2. User signup

hoodie.account.signUp(username, password);

Yes, that's all. signOut, signIn and the other account management functions are similarly short and sweet.

3. Storing data

This will store a task in this new user's store ()

var type = 'task';
var attributes = {title: 'Try out hoodie today'};
hoodie.store.add(type, attributes)
  .done(function (newObject) {
    // Data was saved!
  });

As you can see, the documents you store need a type and some JSON data. Both are arbitrary and don't need to be set up anywhere previously. Just pass Hoodie an object for your data, it will eat anything and save it as JSON.

The promises returned by Hoodie are a good place to deal with the UI immediately related to the action, such as disabling a submit button while data is sent, showing and hiding a loading spinner, displaying success and error messages and all that. However, the actual data that you've sent or retrieved is best dealt with in a more decoupled manner, with database events:

4. Event listeners

The view should update whenever a task is added, so let's listen to the data store directly for when that happens:

hoodie.store.on('add:task', function (newObject) {
  // Update the view with the changedObject
});

This way, you have nicely reactive views. Users can now add tasks on one device, and the new task will automatically appear on the running page on any other device they may be using simultaneously.

5. Loading data

Let's load all of the user's 'task' documents:

var type = 'task';
hoodie.store.findAll(type)
  .done(function (tasks) {
    // Do something with the tasks
  });

Looks good?

Find out more about sharing, making data public, listening to remote events and sending emails in the Hoodie Documentation

Or, if you want to dive in directly:

Getting started

Installing Hoodie and its dependencies

Hoodie has support for Mac OS X, Windows and Linux.

Installing Hoodie on Mac OS X requires homebrew. First, you need to install Node.js, git and CouchDB, since the core of Hoodie is built with these:

# make sure your Homebrew is up to date first
    $ brew update
    $ brew install git
    $ brew install node
    $ brew install couchdb

Now, install Hoodie using Node's package manager:

$ npm install -g hoodie-cli

Mac users can also optionally install local-tld to automatically get pretty *.dev domains every time you start a Hoodie app.

$ npm install -g local-tld

Installation done! Time to build apps.

First, install CouchDB (1.2.x or newer). On Ubuntu/Debian:

$ sudo apt-get update
$ sudo apt-get install couchdb git

Then, download the Node.js (stable) source code. Extract, compile and install:

$ tar -xvf node-v0.10.10.tar.gz
$ cd node-v0.10.10
$ ./configure
$ make && sudo make install

Now you can install Hoodie using Node's package manager:

$ sudo npm install -g hoodie-cli

Installation done! Time to build apps.

This is an Ubuntu-specific guide courtesy of Stuart Langridge. Start by installing CouchDB:

$ sudo apt-get update
$ sudo apt-get install couchdb-bin git

On Ubuntu, you don't have to build Node.js from source, you can install it as a package instead. Add Chris Lea's Node.js PPA and install from it:

$ sudo add-apt-repository ppa:chris-lea/node.js
$ sudo apt-get update
$ sudo apt-get install nodejs

Now you can install Hoodie using Node's package manager:

$ sudo npm install -g hoodie-cli

Installation done! Time to build apps.

This is an Fedora-specific guide. First, install CouchDB (1.2.x or newer), git and node.js. On Fedora 19+:

$ sudo yum install couchdb git nodejs npm

Now you can install Hoodie using Node's package manager:

$ sudo npm install -g hoodie-cli

Installation done! Time to build apps.

On Windows, install Node.js, git and CouchDB using the installers on each website. Then install Hoodie using the command prompt:

> npm install -g hoodie-cli

Installation done! Time to build apps.

Creating a Hoodie app

$ hoodie new myappname

This created the folder 'myappname'. Go in there and start the default app:

$ cd myappname
$ hoodie start

Your browser should now automatically open and show you Hoodie's default app. If you don't want that, start the app with

$ hoodie start -n

Hoodie will let you know about all local URLs you might need, including the app itself, Admin Dashboard and the app's CouchDB. If you need to access Futon, CouchDB's web-based administration tool, just append "/_utils" to the CouchDB URL. And if you need to change the port numbers in any URL, they are defined in "~/.ports.json".

That's all. Go nuts!

Documentation

Hoodie is currently a developer preview. Some features are missing, some things might change, there's a lot of optimization to be done. Don't use this for production.

Include the Hoodie library

<script src="hoodie.js"></script>

Initialize your Hoodie App

hoodie = new Hoodie();

You also have the option of defining the Hoodie API endpoint yourself, too. This is useful if you're using a dev environment that runs its own server, like yeoman.io. You'd have Grunt running your frontend at http://localhost:9000, and your Hoodie instance at http://localhost:6007:

hoodie = new Hoodie('http://localhost:6007/_api');

or, with local-tld:

hoodie = new Hoodie('http://hoodieappname.dev/_api');

Account

// sign up
hoodie.account.signUp('joe@example.com', 'secret');

// sign in
hoodie.account.signIn('joe@example.com', 'secret');

// sign out
hoodie.account.signOut();

// change password
hoodie.account.changePassword('currentpassword', 'newpassword');

// change username
hoodie.account.changeUsername('currentpassword', 'newusername');

// reset password
hoodie.account.resetPassword('joe@example.com');

// destroy account and all its data
hoodie.account.destroy();

// find out who the currently logged in user is (returns undefined if none)
hoodie.account.username;

Account events

// listen for account events
// user has signed up (this also triggers the authenticated event, see below)
hoodie.account.on('signup', function (user) {});

// user has signed in (this also triggers the authenticated event, see below)
hoodie.account.on('signin', function (user) {});

// user has signed out
hoodie.account.on('signout', function (user) {});

// user has re-authenticated after their session timed out (this does _not_ trigger the signin event)
hoodie.account.on('authenticated', function (user) {});

// user's session has timed out. This means the user is still signed in locally, but Hoodie cannot sync remotely, so the user must sign in again
hoodie.account.on('unauthenticated', function (user) {});

Store

Important: Hoodie will only sync data to the database if it belongs to a user! So you need to sign up/sign in a user using the methods above before the hoodie.store methods below will actually sync anything. If you don't have a user, hoodie.store will read/write using local browser storage only.

Since Hoodie treats all data as user data, and all data is private by default, the following functions can only ever access the data of the currently signed in user. An exception to this is data shared via global public shares or private user sharing.

// add a new object
var type = 'task';
var attributes = {status: 'open'};
hoodie.store.add(type, attributes)
  .done(function (newObject) {});

// update an existing object
var type = 'task';
var id = 'abc4567';
var update = {starred: true};
hoodie.store.update(type, id, update)
  .done(function (updatedObject) {});

// find one object
var type = 'task';
var id = 'abc4567';
hoodie.store.find(type, id)
  .done(function (object) {});

// Load all objects that belong to the current user
hoodie.store.findAll()
  .done(function (objects) {});

// Load all objects of one type, also belonging to the current user
var type = 'task';
hoodie.store.findAll(type)
  .done(function (objects) {});

// findAll also accepts a function as an argument. If that function returns true for an object in the store, it will be returned. This effectively lets you write complex queries for the store. In this simple example, assume all of our todo tasks have a key "status", and we want to find all unfinished tasks:
hoodie.store.findAll(function(object){
  if(object.type === "task" && object.status === "open"){
    return true;
  }
}).done(function (objects) {});

// remove an existing object belonging to the current user
var type = 'task';
var id = 'abc4567';
hoodie.store.remove(type, id)
  .done(function (removedObject) {});

// Remove all objects of one type, also belonging to the current user
var type = 'task';
hoodie.store.removeAll(type)
  .done(function (objects) {});

// removeAll, like findAll, also accepts a function as an argument. If that function returns true for an object in the store, it will be removed. Assuming all of our todo tasks have a key "status", and we want to remove all completed tasks:
hoodie.store.removeAll(function(object){
  if(object.type === "task" && object.status === "completed"){
    return true;
  }
}).done(function (objects) {});

Store events

// listen for store events
// new doc created
hoodie.store.on('add', function (newObject) {});

// existing doc updated
hoodie.store.on('update', function (updatedObject) {});

// doc removed
hoodie.store.on('remove', function (removedObject) {});

// any of the events above
hoodie.store.on('change', function (eventName, changedObject) {});

// all listeners can be filtered by type
hoodie.store.on('add:note',    function (newObject) {});
hoodie.store.on('update:note', function (updatedObject) {});
hoodie.store.on('remove:note', function (removedObject) {});
hoodie.store.on('change:note', function (eventName, changedObject) {});

// ... and by type and id
hoodie.store.on('add:uuid123:note',    function (newObject) {});
hoodie.store.on('update:uuid123:note', function (updatedObject) {});
hoodie.store.on('remove:uuid123:note', function (removedObject) {});
hoodie.store.on('change:uuid123:note', function (eventName, changedObject) {});

Synchronization

When signed in, local changes do get synced automatically. You can explicitly subscribe to remote updates.

// new doc created
hoodie.remote.on('add', function (newObject) {});

// existing doc updated
hoodie.remote.on('update', function (updatedObject) {});

// doc removed
hoodie.remote.on('remove', function (removedObject) {});

// any of the events above
hoodie.remote.on('change', function (eventName, changedObject) {});

// all listeners can be filtered by type
hoodie.remote.on('add:note',    function (newObject) {});
hoodie.remote.on('update:note', function (updatedObject)  {});
hoodie.remote.on('remove:note', function (removedObject) {});
hoodie.remote.on('change:note', function (eventName, changedObject) {});

// ... and by type and id
hoodie.remote.on('add:uuid123:note',    function (newObject) {});
hoodie.remote.on('update:uuid123:note', function (updatedObject)  {});
hoodie.remote.on('remove:uuid123:note', function (removedObject) {});
hoodie.remote.on('change:uuid123:note', function (eventName, changedObject) {});

Public Shares (Public User Stores)

Public shares are currently non-functional, sorry. We're on it!

Select data you want to share with others and control exactly what will be shared

// make note object with id 'abc4567' public
hoodie.store.find('note', 'abc4567').publish()

// make note with id 'abc4567' public, but only show the color, hide
// all other attributes
hoodie.store.find('note', 'abc4567').publish(['color']);

// make note with id 'abc4567' private again
hoodie.store.find('note', 'abc4567').unpublish();

// find all note objects from user 'joe'
hoodie.user('joe').findAll('note').done(function (notes) { ... });

Global Public Store

When enabled, all publicly shared objects by all users will be available through the hoodie.global API.

// find all public songs from all users
hoodie.global.findAll('song').done(function (songs) { ... });

Sharing

The hoodie.share plugin allows to share objects with other users. A share can be public, which means everybody knowing its id can access it. Or the access can be limited to specific users. Optionally, a password can be set for additional security. Access can be differentiated between read and write.

Important: The shares plugin still a work in progress. This is how things are likely going to look soon.

// add a new share
hoodie.share.add().done(function (share) {});

// grant / revoke access
share.grantReadAccess();
share.grantWriteAccess();
share.revokeReadAccess();
share.revokeWriteAccess();
share.grantReadAccess('joe@example.com');
share.revokeWriteAccess(['joe@example.com', 'lisa@example.com']);

// add all todo objects to the share
hoodie.store.findAll('todo').shareAt(share.id);

// remove a specific todo from the share
hoodie.store.find('todo', '123').unshareAt(share.id);

// add a new share and add some of my objects to it in one step
hoodie.store.findAll('todo').share()
  .done(function (todos, share) { alert('shared at ' + share.id); } );

// remove objects from all shares
hoodie.store.findAll('todo').unshare();

// remove share
hoodie.share.remove(share.id);

// open a share and load all its objects
hoodie.share('shareIdHere').findAll()
  .done(function (objects) {});

// subscribe / unsubscribe
hoodie.share('shareId').subscribe();
hoodie.share('shareId').unsubscribe();

Tasks

Tasks get picked up by backend workers in the background. You can think of them as special objects that describe specific tasks, for which you want backend logic.

If a task has been completed successfully, it gets removed. If there is an error, it stays in the task store to be handled or removed.

// start a new task
hoodie.task.start('message', {to: 'joe', text: 'Do you want to party?'})
  .then( showMessageSent, showMessageError )

// abort a pending task
hoodie.task.abort('message', '123')

// restart a pending or aborted task
hoodie.task.restart('message', '123', { extraProperty: 'value' })

// aborted all pending tasks
hoodie.task.restartAll()

// restart all pending or aborted tasks
hoodie.task.restartAll()

Task Subscription

Important: If change event is "error", the error message gets passed as options.error

// listen to new tasks
hoodie.task.on('start', function (newTask) {});

// task aborted
hoodie.task.on('abort', function (abortedTask) {});

// task could not be completed
hoodie.task.on('error', function (errorMessage, task) {});

// task completed successfully
hoodie.task.on('success', function (completedTask) {});

// all listeners can be filtered by type
hoodie.task.on('message:start',   function (newMessageTask, options) {});
hoodie.task.on('message:abort',  function (abortedMessageTask, options) {});
hoodie.task.on('message:error',   function (errorMessage, messageTask, options) {});
hoodie.task.on('message:success', function (completedMessageTask, options) {});
hoodie.task.on('message:change',  function (eventName, messageTask, options) {});

// ... and by type and id
hoodie.task.on('message:123:start',   function (newMessageTask, options) {});
hoodie.task.on('message:123:abort',  function (abortedMessageTask, options) {});
hoodie.task.on('message:123:error',   function (errorMessage, messageTask, options) {});
hoodie.task.on('message:123:success', function (completedMessageTask, options) {});
hoodie.task.on('message:123:change',  function (eventName, messageTask, options) {});

Email

The hoodie.email plugin allows the user to send emails from their app.

Important: The email plugin still a work in progress. This is how things are likely going to look soon.

// define an email object
var magic = hoodie.email.send({
  to      : ['susan@example.com'],
  cc      : ['bill@example.com'],
  subject : 'rule the world',
  body    : 'we can do it!\nSigned, Joe'
});

magic.done(function(mail) {
  alert('Mail has been sent to ' + mail.to);
});

magic.fail(function(error) {
  alert('Sorry, but something went wrong: ' + error.reason);
});

Plugins

Plugins provide and extend Hoodie's basic functionality.

A Plugin can extend all three parts of Hoodie: the frontend JavaScript library, the admin interface and backend workers, or just one or two of them, if that makes sense for the Plugin.

Plugins are just Node modules and can be published to NPM. Once published, users can install them with, e.g. hoodie install email.

This guide explains how to build Plugins for Hoodie

Hoodie already ships with two core modules: Users and Email. And we are working on a few more, like Shares, Payments and the like. We are encouraging anyone though, to build Plugins and publish them to NPM so everyone can benefit from them.

Follow us on Twitter if you want to stay informed about our progress: @hoodiehq, or check our Hoodie Weeklies.

About

Hoodie is a project by Jan Lehnardt (@janl), Gregor Martynus (@gr2m), Alex Feyerke (@espylaub), Caolan McMahon (@caolan), Lena Reinhard (@ffffux), Sven Lito (@svenlito) and Ola Gasidlo (@misprintedtype) and a growing community. We are based in Berlin, Zurich, Sheffield and Dortmund and run mostly on Coffee and Cheesecake.

All Hoodie code is on GitHub, collected under github.com/hoodiehq. Follow the Hoodie team on twitter: @hoodiehq. If you have any questions, find us on IRC: irc.freenode.net/#hoodie. Or send us an email: hi@thehoodiefirm.com.

Merchandise

Hoodie stickers

We've got some sweet Hoodie merch in the works, starting with high-quality die-cut transparent stickers that turn all sorts of things into Hoodie Support Objects™, from water bottles and notebooks to macs. Oh, and there will be hats for humans:

Hoodie hats

Be sure to sign up to our mailing list to be the first to hear about it.


Impressum