couchapps - o'reilly mediaassets.en.oreilly.com/1/event/61/couchapps with... · can run on...

112
CouchApps + = Blog: http://bradley-holt.com @BradleyHolt (http://twitter.com/BradleyHolt ) [email protected]

Upload: dominh

Post on 04-Mar-2018

215 views

Category:

Documents


2 download

TRANSCRIPT

CouchApps

+

=Blog: http://bradley-holt.com

@BradleyHolt (http://twitter.com/BradleyHolt)[email protected]

About Me (Bradley Holt)

Co-Founder andTechnical Director

from Vermont

Battery Park - Burlington, Vermont by Marty Desilets, on Flickr

OrganizerBTV

(Minor) Contributor

Author

http://oreilly.com/catalog/9781449303129/ http://oreilly.com/catalog/9781449303433/

Why CouchDB?

CouchDB is a database, web server & application server

Schema-less “self-contained” JSON documents—no tables, just documents

RESTful HTTP API is simple to integrate with

Custom-written MapReduce “views” index your data for fast retrieval

Multi-master replication can happen continuously, in any direction, and/or whenever nodes can connect

Can run on anything from a server to a mobile device (CouchDB can be embedded in Android and iOS apps)

Benefits

Trade-OffsNo ad-hoc queries (you can use temporary views, but they’re slow on large data sets)

Thinking in terms of MapReduce views can take some time to get used to

No transactions across document boundaries

Your data will be “eventually consistent” and conflicts can occur (but can be resolved)

MapReduce Views

Mapping Document Titlesfunction(doc) { // JSON object representing a doc to be mapped if (doc.title) { // make sure this doc has a title emit(doc.title); // emit the doc’s title as the key }}

key id value

"Building iPhone Apps with HTML, CSS, and JavaScript" "978-0-596-80579-1" null

"CouchDB: The Definitive Guide"

"978-0-596-15589-6" null

"DocBook: The Definitive Guide"

"978-1-565-92580-9" null

"RESTful Web Services" "978-0-596-52926-0" null

Mapped Document Titles

Mapping Document Authorsfunction(doc) { // JSON object representing a doc to be mapped if (doc.authors) { // make sure this doc has an authors field for (var i in doc.authors) { emit(doc.authors[i]); // emit each author as the key } }}

Mapped Authorskey id value

"J. Chris Anderson" "978-0-596-15589-6" null

"Jan Lehnardt" "978-0-596-15589-6" null

"Jonathan Stark" "978-0-596-80579-1" null

"Leonard Muellner" "978-1-565-92580-9" null

"Leonard Richardson" "978-0-596-52926-0" null

"Noah Slater" "978-0-596-15589-6" null

"Norman Walsh" "978-1-565-92580-9" null

"Sam Ruby" "978-0-596-52926-0" null

HTTP API via the CouchDB jQuery Pluginhttp://localhost:5984/_utils/script/jquery.couch.js

The same-origin policy dictates that your HTML and AJAX data (JSON) must be retrieved from the same origin.

Create Database$.couch.db("mydb").create({ success: function(data) { console.log(data); }});

PUT http://localhost:5984/mydb 201 Created

{"ok":true}

Save New Document$.couch.db("mydb").saveDoc({}, { success: function(data) { console.log(data); }});

POST http://localhost:5984/mydb 201 Created

{ "ok":true, "id":"e15d848571c8a0352e94738ba6018790", "rev":"1-967a00dff5e02add41819138abb3284d"}

Save Updated Documentvar doc = { _id: "0a72c9c36bd169818dc97ed18b000aa4", _rev: "1-967a00dff5e02add41819138abb3284d", title: "CouchApps"};$.couch.db("mydb").saveDoc(doc, { success: function(data) { console.log(data); }});

Save Updated Document (cont’d)PUT http://localhost:5984/mydb/0a72c9c36bd169818dc97ed18b000aa4 201 Created

{ "ok":true, "id":"0a72c9c36bd169818dc97ed18b000aa4", "rev":"2-516027e3179a22a22e06874c374e8ef0"}

Remove Documentvar doc = { _id: "0a72c9c36bd169818dc97ed18b000aa4", _rev: "2-516027e3179a22a22e06874c374e8ef0"};$.couch.db("mydb").removeDoc(doc, { success: function(data) { console.log(data); }});

Remove Document (cont’d)DELETE http://localhost:5984/mydb/0a72c9c36bd169818dc97ed18b000aa4?rev=2-516027e3179a22a22e06874c374e8ef0 200 OK

{ "id":0a72c9c36bd169818dc97ed18b000aa4 "ok":true, "rev":"3-e9a5aa1c486eee23c84fa028bc904991"}

Changes (Long Polling)$.couch.db("mydb").changes().onChange(function(data) { console.log(data);});

GET http://localhost:5984/mydb/_changes?heartbeat=10000&feed=longpoll&since=34 200 OK

{ "results":[ { "seq":35, "id":"d12ee5ea1df6baa2b06451f44a01b7b5", "changes":[ { "rev":"1-967a00dff5e02add41819138abb3284d" } ] } ], "last_seq":35}

Why CouchApps?

BenefitsStreamlining of your codebase (no middle tier)

Same language on both the client and server (JavaScript)

Show and list functions let you generate HTML from within CouchDB, if you don’t want to rely on JavaScript

Replication of both data and code together

Deploy/replicate an application along with its data

Goes “with the grain” of the web

“Ground Computing”Replication filters allow you to replicate relevant data to a user

Local data means faster access to the user’s data

Offline access

Data portability

Decentralization—no need for a canonical database

Potentially gives more control to the user over his or her own data

What about the HTML5 Web Storage API?Allows persistent storage of key/value pairs

Enjoys significant cross-browser support

Lacks indexed queries

No replication features

IndexedDB might help, but is not part of the HTML5 specification and is only implemented in a limited number of browsers

Need to look outside the HTML5 specification if you need more than just a key/value storage

Trade-OffsTooling could use some improvement

Terminology still unclear—CouchApp can mean many different things

Must use JavaScript for the presentation tier

Pure JavaScript applications may have SEO issues

Show and list functions must be side-effect free, which can be limiting

UsesMobile applications that require offline access

Multi-device applications (e.g. address book, tasks)

Peer-to-peer collaboration applications

Distributed social networking

Any application that stores “documents”—CMS, wiki, etc.

Geospatial applications (via GeoCouch)

MustacheLogic-Less Templates

}

Variables

VariablesTemplate:<p>Hello, {{name}}.</p>

Hash:{ "name": "Bradley"}

Output:<p>Hello, Bradley.</p>

Unescaped VariablesTemplate:<p>Hello, {{{name}}}.</p>

Hash:{ "name": "<em>Bradley</em>"}

Output:<p>Hello, <em>Bradley</em>.</p>

“Missed” VariablesTemplate:<p>Hello, {{name}}.</p>

Hash:{ "role": "admin"}

Output:<p>Hello, .</p>

Sections

False ValuesTemplate:<p>Shown.</p>{{#show}}<p>Not shown.</p>{{/show}}

Hash:{ "show": false}

Output:<p>Shown.</p>

Empty ListsTemplate:<p>Shown.</p>{{#show}}<p>Not shown.</p>{{/show}}

Hash:{ "role": "admin"}

Output:<p>Shown.</p>

Non-Empty ListsTemplate:{{#users}} <p>{{name}}</p>{{/users}}

Hash:{ "users": [ { "name": "Bradley" }, { "name": "Jason" }, ]}

Output: <p>Bradley</p> <p>Jason</p>

Non-False (and Not a List) ValuesTemplate:{{#user}} <p>Logged in as {{name}}</p>{{/user}}

Hash:{ "user": { "name": "Bradley" }}

Output: <p>Logged in as Bradley</p>

Descends into the “user” context for the section

Inverted SectionsTemplate:{{^user}} <p>Not logged in</p>{{/user}}

Hash:{ "user": false}

Output: <p>Not logged in</p>

Evently

Evently is a jQuery plugin for writing event-based applications

<p id="foo"></p><script type="text/javascript">$("#foo").evently({ click: function() { $(this).text("Clicked"); }})</script>

DOM Events

Custom Events<p id="user"></p><script type="text/javascript">$("#user").evently({ login: function(e, user) { $(this).text(user); }})$("#user").trigger("login", "Bradley");</script>

$("#user").evently({ _init: { async: function(callback) { $.couch.session({ success: function(data) { callback(data); } }); }, mustache: "<span>{{#name}}Logged in as {{name}}{{/name}}{{^name}}Not logged in{{/name}}</span>", data: function(data) { return data.userCtx; } }});

Widgets

jQuery.data Syntactic Sugar// $$ inspired by @wycats: http://yehudakatz.com/2009/04/20/evented-programming-with-jquery/function $$(node) { var data = $(node).data("$$"); if (data) { return data; } else { data = {}; $(node).data("$$", data); return data; }};

$$ ExamplesThis:$("#my-node").data("key", "value");

Becomes:$$("#my-node").key = "value";

This:var value = $("#my-node").data("key");

Becomes:var value = $$("#my-node").key;

Design Documents

Each CouchApp is entirely self-contained in one design document

Design documents live alongside other documents, but have a special naming convention of _design/<name>

A database can have multiple CouchApps (and/or design documents) and CouchDB server can host multiple databases

CouchApps & Design Documents

Can contain:•View definitions

•Map functions•Reduce functions

•Show functions•List functions•Document update handlers•Document update validation functions•Rewrite definitions•Any other fields you want, just like with any other document•Attachments, just like with any other document

ID follows the form of: _design/<design_name>

What’s in a design document?

Exploring Pages, a CouchApp Wiki

Page Comments, JavaScript

function(doc) { if (doc.type == "comment") { emit([doc.topic, doc.at], doc); }};

views/recent-comments/map.js

The recent comments view’s map function

Wiring the Evently Widget <div id="comments"></div> … <script type="text/javascript" charset="utf-8"> var opts = {}; … $.couch.app(function(app) { … $$("#wiki").docid = {{docid}}; $$("#wiki").title = {{title_json}}; $("#comments").evently("comments", app); … }, opts); </script>

Note that this template gets rendered server-side

evently/comments/_init/query.jsfunction() { var docid = $$("#wiki").docid; return { view : "recent-comments", endkey : [docid, {}], startkey : [docid] };};

Builds a query to a CouchDB view

evently/comments/_init/data.jsfunction(view) { var docid = $$("#wiki").docid, linkup = $$("#wiki").app.require("vendor/couchapp/lib/linkup"); return { topic : docid, title : $$("#wiki").title, comments : view.rows.map(function(r) { var by = r.value.by || {}; return { gravatar_url : by.gravatar_url, by : by.nickname, at : r.key[1], comment : linkup.encode(r.value.comment) // move to view } }) }};

Handles the resultant data from the CouchDB view and prepares it for use by the Mustache template

evently/comments/_init/mustache.html<h3>Comments about {{title}}</h3><ul> {{#comments}} <li> <div class="avatar"> {{#gravatar_url}}<img src="{{gravatar_url}}"/>{{/gravatar_url}} <div class="name"> {{by}} </div> </div> <p>{{{comment}}}</p> <em><span class="date">{{at}}</span></em> <div class="clear"></div> </li> {{/comments}}</ul>

The widget’s Mustache template

evently/comments/_init/mustache.html (cont’d)<form> <label>Comment (w/ <a href="http://github.com/couchapp/couchapp" target="_new">linkup</a>*)</label> <br/> <!-- <input type="text" size="60" name="comment"> --> <textarea name="comment" rows="4" cols="60"></textarea> <input type="hidden" name="topic" value="{{topic}}"> <p><input type="submit" value="Save Comment"></p></form>

evently/comments/_init/after.jsfunction() { $(".date").prettyDate();};

This is executed after everything else is done

evently/comments/_init/selectors/form/submit.jsfunction() { var form = $(this), app = $$(form).app, f = form.serializeObject(); f.type = "comment"; f.at = new Date(); f.by = $$("#profile").profile; app.db.saveDoc(f, { success : function() { $("#comments").trigger("_init"); } }) return false;};

Creates a selector based on the file path, and calls the function when its namesake event is fired

All Comments, HTML

[ … { "from" : "/pages/comments", "to" : "_list/comments/all-comments", "query" : { "descending" : true, "limit" : 20 } }, …]

rewrites.json

Rewrites to the comments list, using the all-comments view

CouchDB supports virtual hosts via the “vhosts” server configuration section. This lets you route to a rewrite handler based on the Host HTTP header.{ "example.com": "/db/_design/tutorial/_rewrite", "www.example.com": "/db/_design/tutorial/_rewrite"}

function(doc) { if (doc.type == "comment" && doc.comment) { emit(doc.at, doc); }};

views/all-comments/map.js

lists/comments.jsfunction() { var row, ddoc = this, mustache = require("vendor/couchapp/lib/mustache"), markdown = require("vendor/couchapp/lib/markdown"), data = { title : "All Comments", site_title : this.couchapp.name, path : "/pages/comments", comments : [] }; provides("html", function() { while (row = getRow()) { log(row); data.comments.push(row.value) } send(mustache.to_html(ddoc.templates.comments, data, ddoc.templates.partials)); });};

templates/comments.html{{>header}}<ul> {{#comments}} <li> <span class="date">{{at}}</span>: {{#by}}{{name}} {{/by}} {{^by}}Unknown {{/by}} (<a href="../page/{{topic}}">{{topic}}</a>) <span class="idontknow">{{comment}}</span> </li> {{/comments}} </ul> </body> …</html>

The {{>header}} syntax is for partials

templates/partials/header.html<!DOCTYPE html><html> <head> <title>{{title}} - {{site_title}}</title> <link rel="stylesheet" href="../style/base-min.css" type="text/css"> <link rel="stylesheet" href="../style/main.css" type="text/css"> </head> <body> <div id="header"> <div id="account"></div> <h1><a href="../page/index">{{site_title}}</a>: <a href="{{path}}">{{title}}</a></h1> </div> <div id="profile"></div>

Securityvalidate_doc_update.js

function (newDoc, oldDoc, userCtx, secObj) { var v = require("vendor/couchapp/lib/validate").init(newDoc, oldDoc, userCtx, secObj); …}

Function Definition

Let Admins Do Anything if (v.isAdmin()) { return true; // admin can do anything }

Require Login to Make Changes if (!userCtx.name) { // this could be configurable based on secObj v.unauthorized("please login to make changes"); }

Only Let Admins Delete // only admin may delete if (newDoc._deleted) { v.unauthorized("only admin may delete docs"); }

A special _security database property lets you defined database admins and database readers

CouchApp Tool (Python)

CouchApps can be built without CouchApp tooling—but the tooling makes things much easier.

$ sudo easy_install pip$ sudo pip install couchapp

Installing CouchApp

CouchApp Tooling Basics

$ couchapp generate tutorial2011-06-15 16:25:18 [INFO] /tutorial generated.

$ cd tutorial

Generate a CouchApp

Generated Files$ tree -FL 1.├── README.md├── _attachments/├── _id├── couchapp.json├── evently/├── language├── lists/├── shows/├── updates/├── vendor/└── views/

7 directories, 4 files

README.mdGenerated by CouchAppCouchApps are web applications which can be served directly from CouchDB. This gives them the nice property of replicating just like any other data stored in CouchDB. They are also simple to write as they can use the built-in jQuery libraries and plugins that ship with CouchDB.More info about CouchApps here.Deploying this appAssuming you just cloned this app from git, and you have changed into the app directory in your terminal, you want to push it to your CouchDB with the CouchApp command line tool…

Update this file to be a README for your CouchApp

_attachments$ tree -F _attachments_attachments├── index.html└── style/ └── main.css

1 directory, 2 files

_attachments/index.html will be the starting point for your CouchApp_attachments/style/main.css should be self-explanatory

_id$ cat _id_design/tutorial

This is the name of the design document in which your CouchApp will live

couchapp.json$ cat couchapp.json{ "name": "Name of your CouchApp", "description": "CouchApp"}

Metadata about the CouchApp

evently$ tree -F eventlyevently├── items/│   └── _changes/│   ├── data.js│   ├── mustache.html│   └── query.json└── profile/ └── profileReady/ ├── mustache.html └── selectors/ └── form/ └── submit.js

6 directories, 5 files

User made widgets, which an override vendor widgets

language$ cat languagejavascript

The language used for functions defined within the design document

lists$ tree -F listslists

0 directories, 0 files

Contains CouchDB list functions

shows$ tree -F showsshows

0 directories, 0 files

Contains CouchDB show functions

updates$ tree -F updatesupdates

0 directories, 0 files

Contains CouchDB update functions, which can transform arbitrary HTTP requests into document updates

vendor$ tree -FL 2 vendorvendor└── couchapp/ ├── _attachments/ ├── evently/ ├── lib/ └── metadata.json

4 directories, 1 file

Third-party “vendor” code that ships with CouchApp as a convenience, as well as some useful Evently widgets

views$ tree -F viewsviews└── recent-items/ └── map.js

1 directory, 1 file

Function definitions for CouchDB MapReduce views

views/recent-items/map.js$ cat views/recent-items/map.jsfunction(doc) { if (doc.created_at) { emit(doc.created_at, doc); }};

Deploying a CouchApp

Deploy to Localhost$ couchapp push tutorial2011-06-16 11:16:05 [INFO] Visit your CouchApp here:http://127.0.0.1:5984/tutorial/_design/tutorial/index.html

My New CouchApp

Note the account, profile, and items widgets

Pathbinder

Pathbinder is a jQuery plugin for triggering events based on the path components of a URL hash.

<div id="profile"></div><script type="text/javascript">$("#profile").html('<p><a href="#/profile">View Profile</a></p>');$("#profile").bind("profile", function() { $(this).html("<p>Profile goes here…</p>");});$("#profile").pathbinder("profile", "/profile");</script>

Basic Usage

Path Parameters<div id="profile"></div><script type="text/javascript">$("#profile").html('<p><a href="#/profile/Bradley">View Bradley\'s Profile</a></p>');$("#profile").bind("profile", function(e, params) { $(this).html("<p>Viewing " + params.id + "'s profile</p>");});$("#profile").pathbinder("profile", "/profile/:id");</script>

Pathbinder, Evently & Mustache<div id="profile"></div><script type="text/javascript">$("#profile").evently({ _init: { path: "/", mustache: '<p><a href="#/profile/Bradley">View Profile</a></p>' }, viewProfile: { path: "/profile/:id", mustache: '<p>Viewing {{id}}\'s profile; <a href="#/">Back</a></p>', data: function(e, params) { return params; } }});</script>

The path name can be stored in a path.txt file within an Evently widget in a CouchApp.

Alternative CouchApp Tools

Command-line tool, written for Node.js

Simpler folder structure

Not compatible with the Python version

Links:•https://github.com/mikeal/node.couchapp.js•http://japhr.blogspot.com/2010/04/quick-intro-to-

nodecouchappjs.html•http://vimeo.com/26147136

node.couchapp.js

Kanso FrameworkAlso built using Node.js

http://kansojs.org/

soca“Sammy On Couch App” or “Sittin’ on a Couch App”

Command-line tool written in Ruby

Uses Sammy.js

https://github.com/quirkey/soca

ReupholsterA simple way to get started developing CouchApps

http://reupholster.iriscouch.com/reupholster/_design/app/index.html

See the CouchApp wiki for a list of CouchApps

Hosting is available through Iris Couch or Cloudant.

CouchApp Wikihttp://couchapp.org/

CouchDB Wiki http://wiki.apache.org/couchdb/

CouchDB: The Definitive Guide by J. Chris Anderson, Jan Lehnardt, and Noah Slater (O’Reilly) 978-0-596-15589-6

Writing and Querying MapReduce Views in CouchDB by Bradley Holt (O’Reilly)978-1-449-30312-9

Scaling CouchDB by Bradley Holt (O’Reilly)063-6-920-01840-7

Beginning CouchDB by Joe Lennon (Apress) 978-1-430-27237-3

CouchDB & CouchApp Resources

Questions?

Thank YouBlog: http://bradley-holt.com

@BradleyHolt (http://twitter.com/BradleyHolt)[email protected]

Copyright © 2011 Bradley Holt. All rights reserved.