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 a noBackend architecture for frontend-only web apps.
It took less than 15 minutes for a person with no experience in any part of the stack to take an existing single user app and make it a multi-user application with robust security and data storage. […] Bravo, hood.ie, brav-fucking-o.— Robert Horvick, in a blog post about Hoodie and angular.js
❤ Hood.ie - a fast offline-first architecture for webapps. Super-simple user management & storage. Great for mobile.— Addy Osmani, via Twitter
Hood.ie is looking absolutely KILLER. It's in the early stages; great time to experiment and give feedback.— Paolo Fragomeni, via Twitter
I had some pull requests merged into @hoodiehq this weekend. Web apps without the back-end. Go try it!— Adam Yeats, via Twitter
See what others are tweeting about Hoodie or browse through our personal favourite user quotes.
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:
hoodie = new Hoodie();
Just one line of JS to get started with Hoodie.
hoodie.account.signUp(username, password);
Yes, that's all. signOut, signIn and the other account management functions are similarly short and sweet.
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 retreived is best dealt with in a more decoupled manner, with database events:
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 (event, changedObject) {
// 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.
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
});
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:
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.
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 everytime 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.
$ 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 http://127.0.0.1:6007 and show you Hoodie's default app.
Once your app is running, you can usually access your app's CouchDB at http://127.0.0.1:6009 (Futon, CouchDB's web-based administration at http://127.0.0.1:6009/_utils) and Pocket, your app's admin backend at http://127.0.0.1:6008. If these change, Hoodie will tell you in the terminal.
Local-TLD is an optional add-on for OS X that gives you nicer local development domains, like appname.dev
. Again, the Hoodie CLI will tell you what the respective domains are for CouchDB, Futon and Pocket.
Important: local-tld currently blocks any vhosts you may have set up (localhost works fine though). There is a simple workaround for the time being:
$ sudo ipfw flush
$ npm install -g local-tld
We're working on making this a nicer experience, but it seems as if you won't be able to have both at once for the forseeable future. We hope that's ok with you. If you have any trouble with local-tld, please let us know in the local-tld issues.
That's all. Go nuts!
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.
<script src="hoodie.js"></script>
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');
// sign up
hoodie.account.signUp('[email protected]', 'secret');
// sign in
hoodie.account.signIn('[email protected]', '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('[email protected]');
// 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;
// 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) {});
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. Exceptions 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) {});
// 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 (event, 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 (event, changedObject) {});
// ... and by type and id
hoodie.store.on('add:note:uuid123', function (newObject) {});
hoodie.store.on('update:note:uuid123', function (updatedObject) {});
hoodie.store.on('remove:note:uuid123', function (removedObject) {});
hoodie.store.on('change:note:uuid123', function (event, changedObject) {});
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 (event, 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 (event, changedObject) {});
// ... and by type and id
hoodie.remote.on('add:note:uuid123', function (newObject) {});
hoodie.remote.on('update:note:uuid123', function (updatedObject) {});
hoodie.remote.on('remove:note:uuid123', function (removedObject) {});
hoodie.remote.on('change:note:uuid123', function (event, changedObject) {});
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 do 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) { ... });
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) { ... });
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 differenciated 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('[email protected]');
share.revokeWriteAccess(['[email protected]', '[email protected]']);
// 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();
Plugins provide and extend Hoodie's basic functionality. Currently, there are only two, users and shares. We're going to provide more plugins such as payments and oAuth in the future.
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
Follow us on Twitter if you want to stay informed about our progress: @hoodiehq, or check our Hoodie Weeklies.
Hoodie is a project by Jan Lehnardt (@janl), Gregor Martynus (@gr2m), Alex Feyerke (@espylaub), Caolan McMahon (@caolan), Lena Reinhard (@ffffux) and Sven Lito (@svenlito). We are based in Berlin, Zurich, Sheffield and London 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.