Maki

hand-rolled goodness

From Zero to MVP In Five Minutes

maki.ericmartindale.com

Resource-based architecture-as-a-library

@martindale • Atlanta NodeJS Meetup • April 9th, 2015

Why Maki?

  • LocalSense, 2011 – 2013
  • Startup Weekend, March 2013
  • ↳ Coursefork, funded company
  •       ↳ Trinket: trinket.io
  • soundtrack.io, May 2013 – now
  • BitPay, December 2013 – now

In the beginning...

Internet !== World Wide Web

The Internet didn't really take off

...until the World Wide Web was created.

(specifically,    hypertext  )

<a href="location">document</a>

The Semantic Web

...is all about knowledge representation.

<abbr title="2015-04-09T21:16:37Z">
  3 minutes ago
</abbr>
<a href="http://twitter.com/martindale" rel="me">
  Eric on Twitter
</a>

...makes information very easy to consume.*

microformats.org

While the World Wide Web is a publishing platform,
the Internet should be a secure network of privacy-
aware applications that protect individual liberties.

HTTP is Really Cool!

EventsWebSockets (http Upgrade header)
Pipeliningspdy, http/2.0
Content NegotiationAccept, Accept-Language headers
Methodsverbs: OPTIONS, PATCH, PUT...
AuthenticationBasic Auth (username, password)
Cryptographic (HTTP Signatures)

The Same URI can serve many users,

but allow referencing of the same document.

and we don't need any stinkin' cookies, either.

Servers should serve resources, not applications.

what about all those client-side JS frameworks?

They're perfect for progressively enhancing a document,
but only after it has been served.



Benefits include: performance, resilience (high-latency connections), bandwidth...
...and developer ease-of-use.

Methods

GETto retrieve a document or collection
POSTto create a document in a collection
PUTto create a document at a specific location
PATCHto update a document at a specific location
DELETEto remove a document at a specific location
HEADto retrieve a document or collection metadata
OPTIONSto retrieve a list of methods a document supports
in the current HTTP context (i.e., what credentials
are provided in the Authorization header?)
$ curl -H "Accept: text/html" irs.gov/returns
<!DOCTYPE html>
<html>
  <head>
...
            

Full document including a list of returns,
and maybe some Javascript to live-update the page.

$ curl -H "Accept: application/json" irs.gov/returns
[
  {
    "id": 8675309,
    "date": "Thu Apr 09 2015 21:48:57 GMT-0400 (EDT)",
    "amount": 2753.22,
    "status": "pending"
  },
  ...
]
            
var ws = new WebSocket('irs.gov/returns/8675309');
ws.onmessage = function(msg) {
  console.log('Resource updated.', msg );
};
            
$ curl -X PATCH -d "status=issued" irs.gov/returns/8675309
HTTP/2.0 200 OK
{
  "id": 8675309,
  "date": "Thu Apr 09 2015 21:48:57 GMT-0400 (EDT)",
  "amount": 2753.22,
  "status": "issued"
}
            

Okay, so what?

Building this for real sounds really hard.

hint: it is.

Maki

Code is craft.

Be a craftsman.


var Maki = require('maki');
var twitter = new Maki();

twitter.define('Tweet', {
  attributes: {
    content: { type: 'String', max: 140 }
  }
});

twitter.start();
            

var Maki = require('maki');
var twitter = new Maki();

twitter.define('User', {
  attributes: {
    username: { type: String, max: 35 }
  }
});

twitter.define('Tweet', {
  attributes: {
    content: { type: String, max: 140 },
    _user: { type: twitter.mongoose.SchemaTypes.ObjectId, ref: 'User' }
  }
});

twitter.start();
            

var Maki = require('maki');
var twitter = new Maki();

var Passport = require('maki-passport-local');
var passport = new Passport({
  resource: 'User'
});

twitter.use( passport );

twitter.define('User', {
  attributes: {
    username: { type: String , max: 35 , slug: true },
    password: { type: String , max: 200 , masked: true }
  }
});

twitter.define('Tweet', {
  attributes: {
    content: { type: String, max: 140 },
    _user: { type: twitter.mongoose.SchemaTypes.ObjectId, ref: 'User' }
  }
});

twitter.start();
            

var Maki = require('maki');
var twitter = new Maki();

var Passport = require('maki-passport-local');
var passport = new Passport({
  resource: 'User'
});

twitter.use( passport );

var User = twitter.define('User', {
  attributes: {
    username: { type: String , max: 35 , slug: true },
    password: { type: String , max: 200 , masked: true }
  }
});

var Tweet = twitter.define('Tweet', {
  attributes: {
    content: { type: String, max: 140 },
    _user: { type: twitter.mongoose.SchemaTypes.ObjectId, ref: 'User' }
  }
});

User.on('create', function(user) {
  Tweet.create({
    content: 'My first tweet!',
    _user: user._id
  }, function(err, tweet) { console.log( err || tweet ); });
});

twitter.start();
            

RFC 6902: JSON PATCH protocol

Atomic transformations for objects,
encoded as a standard JSON string.


var ws = new WebSocket('ws://localhost:9200/tweets');    // generic WebSocket
ws.onmessage = function( msg ) {                         // message handler
  console.log( JSON.parse( msg.data ) );                 // JSON-RPC string + Data
}                                                        //
                                                         //
{                                                        // WebSocket Message Data
  "jsonrpc": "2.0",                                      // JSON-RPC wrapper
  "method": "patch",                                     // JSON-RPC wrapper
  "params": {                                            // JSON-RPC wrapper
    "channel": "/tweets",                                // Maki protocol
    "ops": [                                             // JSON PATCH
      {                                                  // JSON PATCH
        "op": "add",  // JSON PATCH method               // JSON PATCH
        "path": "/0", // DOM/XPath for JSON              // JSON PATCH
        "value": {                                       // JSON PATCH
          "_id": "5526d60b20aed53950ab5112",             // JSON PATCH
          "content": "Another fantastic example tweet."  // JSON PATCH
        }                                                // JSON PATCH
      }                                                  // JSON PATCH
    ]                                                    // JSON PATCH
  }                                                      // JSON-RPC wrapper
  "id": "7f7244f0-def0-11e4-af97-75fb2146fd5f"           // JSON-RPC wrapper
}                                                        // WebSocket Message Data
            

Multiplexing


var ws = new WebSocket('ws://localhost:9200/tweets');    // generic WebSocket
ws.onmessage = function( msg ) {                         // message handler
  console.log( JSON.parse( msg.data ) );                 // JSON-RPC messages
}

ws.send({
  "jsonrpc": "2.0",
  "method": "subscribe",
  "params": {
    "channel": "/users"
  }
});
            

Maki

maki.ericmartindale.com

github.com/martindale/maki

eric@ericmartindale.com    •    @martindale