Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Build a Node.js Client for Your REST+JSON API

Build a Node.js Client for Your REST+JSON API

In this presentation, Les Hazlewood - Stormpath CTO and Apache Shiro PMC Chair - will share all of the golden nuggets learned while designing, implementing and supporting a Node.js Client purpose-built for a real-world REST+JSON API.

Sign up for Stormpath: https://api.stormpath.com/register
More from Stormpath: https://stormpath.com/blog

Stormpath is a user management and authentication service for developers. By offloading user management and authentication to Stormpath, developers can bring applications to market faster, reduce development costs, and protect their users. Easy and secure, the flexible cloud service can manage millions of users with a scalable pricing model.

Stormpath

April 22, 2014
Tweet

More Decks by Stormpath

Other Decks in Programming

Transcript

  1. Building a Node.js Client for Your REST+JSON API Les Hazlewood

    @lhazlewood CTO, Stormpath stormpath.com
  2. .com • User Management and Authentication API • Security for

    your applications • User security workflows • Security best practices • Developer tools, SDKs, libraries
  3. Overview • Resources • Public / Private API • Proxy

    Design • Active Record • Fluent API • Configuration • Caching • Authentication • Pluggability • Lessons Learned
  4. HATEOAS • Hypermedia • As • The • Engine •

    Of • Application • State Learn more at Stormpath.com
  5. Resources • Nouns, not verbs • Coarse-grained, not fine-grained •

    Support many use cases • Globally unique HREF Learn more at Stormpath.com
  6. Collection Resource • Example: /applications • First class resource w/

    own properties: • offset • limit • items • first, next, previous, last • etc • items contains instance resources Learn more at Stormpath.com
  7. Instance Resource • Example: /applications/8sZxUoExA30mP74 • Child of a collection

    • RUD (no Create - done via parent collection) Learn more at Stormpath.com
  8. Resource var util = require('util'); function Resource(...) { ... }

    util.inherits(Resource, Object); someResource.href Learn more at Stormpath.com
  9. Collection Resource function CollectionResource(...) {...} util.inherits(CollectionResource, Resource); aCollResource.each(function (item, callback)

    { ... }, function onCompletion(err) { ... }); aCollResource.eachSeries aCollResource.map aCollResource.filter ... other async.js methods ... Learn more at Stormpath.com
  10. Encapsulation • Public API • Internal/Private Implementations • Extensions •

    Allows for change w/ minimal impact http://semver.org Learn more at Stormpath.com
  11. Encapsulation in practice • Use an underscore prefix: _ •

    Super clear code comments: mark @public or @private • Public API docs: don’t display private classes/methods/functions Learn more at Stormpath.com
  12. Public API • All non-@private functions/vars • Builder methods (method

    chaining) • Object literals (config) is public too! Learn more at Stormpath.com
  13. Public prototypical OO Classes • Client • ApiKey • Application

    • Directory • Account • Group • etc Learn more at Stormpath.com
  14. Classes with static helper methods Client client = Clients.builder() ...

    .build(); • Create multiple helper classes separation of concerns Learn more at Stormpath.com
  15. Builder methods (method chaining) client.getApplications() .where(name).startsWith(‘*foo’) .orderBy(name).asc() .limit(10) .execute(function (err,

    apps){ ... }); clients.getApplications() à ApplicationRequestBuilder Single Responsibility Principle! Learn more at Stormpath.com
  16. Private API • Implementations + SPI interfaces • Builder implementations

    • Implementation Plugins Learn more at Stormpath.com
  17. Resource Implementations • Create a base Resource class: • Property

    manipulation methods • Dirty checking • Reference to DataStore • Lazy Loading • Create base InstanceResource and CollectionResource implementations • Extend from InstanceResource or CollectionResource Learn more at Stormpath.com
  18. Resource var utils = require(’utils'); function Resource(data, dataStore) { var

    DataStore = require('../ds/DataStore'); if (!dataStore && data instanceof DataStore){ dataStore = data; data = null; } data = data || {}; for (var key in data) { if (data.hasOwnProperty(key)) { this[key] = data[key]; } } var ds = null; //private var, not enumerable Object.defineProperty(this, 'dataStore', { get: function getDataStore() { return ds; }, set: function setDataStore(dataStore) { ds = dataStore; } }); if (dataStore) { this.dataStore = dataStore; } } utils.inherits(Resource, Object); module.exports = Resource; Learn more at Stormpath.com
  19. InstanceResource var utils = require(‘utils'); var Resource = require('./Resource'); function

    InstanceResource() { InstanceResource.super_.apply(this, arguments); } utils.inherits(InstanceResource, Resource); InstanceResource.prototype.save = function saveResource(callback) { this.dataStore.saveResource(this, callback); }; InstanceResource.prototype.delete = function deleteResource(callback) { this.dataStore.deleteResource(this, callback); }; Learn more at Stormpath.com
  20. Application var utils = require(‘utils'); var InstanceResource = require('./InstanceResource'); function

    Application() { Application.super_.apply(this, arguments); } utils.inherits(Application, InstanceResource); Application.prototype.getAccounts = function getApplicationAccounts(/* [options,] callback */) { var self = this; var args = Array.prototype.slice.call(arguments); var callback = args.pop(); var options = (args.length > 0) ? args.shift() : null; return self.dataStore.getResource(self.accounts.href, options, require('./Account'), callback); }; Learn more at Stormpath.com
  21. Account JSON Resource { “href”: “https://api.stormpath.com/v1/accounts/x7y8z9”, “givenName”: “Tony”, “surname”: “Stark”,

    …, “directory”: { “href”: “https://api.stormpath.com/v1/directories/g4h5i6” } } Learn more at Stormpath.com
  22. Proxy Pattern String href = “https://api.stormpath.com/v1/....”; client.getAccount(href, function(err, acct) {

    if (err) throw err; account.getDirectory(function(err, dir) { if (err) throw err; console.log(dir); }); }); Learn more at Stormpath.com
  23. Component Architecture account .save() RequestExecutor Authentication Strategy Cache Manager DataStore

    Request Authenticator Cache Cache Cache Learn more at Stormpath.com
  24. Component Architecture account API Server .save() RequestExecutor Authentication Strategy Cache

    Manager DataStore Request Authenticator Cache Cache Cache Learn more at Stormpath.com
  25. Component Architecture account API Server .save() RequestExecutor ResourceFactory JSON à

    Resource Authentication Strategy Cache Manager DataStore Request Authenticator Cache Cache Cache Learn more at Stormpath.com
  26. Caching var cache = cacheManager.getCache(regionName); cache.ttl //time to live cache.tti

    //time to idle cache.get(href, function(err, obj) { ... }); Learn more at Stormpath.com
  27. Caching client.getAccount(href, function(err, acct) {...}); // in the DataStore: var

    cache = cacheManager.getCache(‘accounts’); cache.get(href, function(err, entry) { if (err) return callback(err); if (entry) { ... omitted for brevity ... return callback(entry.value); } //otherwise, cache miss – execute a request: requestExecutor.get(href, function(err, body) { //1. cache body //2. convert to Resource instance //3. invoke callback w/ instance } } Learn more at Stormpath.com
  28. Queries account.getGroups( { name: ‘foo*’, description: ‘*test*’, orderBy: ‘name desc’,

    limit: 100 }, function onResult(err, groups) { ... } ); //results in a request to: https://api.stormpath.com/v1/accounts/a1b2c3/groups? name=foo*&description=*test*&orderBy=name%20desc&limit=100 Learn more at Stormpath.com
  29. Queries account.getGroups().where() .name().startsWith(“foo”) .description().contains(“test”) .orderBy(“name”).desc() .limitTo(100) ); //results in a

    request to: https://api.stormpath.com/v1/accounts/a1b2c3/groups? name=foo*&description=*test*&orderBy=name%20desc&limit=100 Learn more at Stormpath.com
  30. Authentication • Favor a digest algorithm over HTTP Basic •

    Prevents Man-in-the-Middle attacks (SSL won’t guarantee this!) • Also support Basic for environments that require it (Dammit Google!) • ONLY use Basic over SSL • Represent this as an AuthenticationScheme to your Client / RequestExecutor Learn more at Stormpath.com
  31. Authentication • AuthenticationScheme.SAUTHC1 • AuthenticationScheme.BASIC • AuthenticationScheme.OAUTH10a • ... etc

    ... Client client = new stormpath.Client({ //defaults to sauthc1 authcScheme: ‘basic’ }); Client/RequestExecutor uses a Sauthc1RequestAuthenticator or BasicRequestAuthenticator or OAuth10aRequestAuthenticator, etc. Learn more at Stormpath.com
  32. Plugins • Plugins or Extensions module • One sub-module per

    plugin • Keep dependencies to a minimum plugins/ |- request/ |- foo/ Learn more at Stormpath.com
  33. Lessons Learned • Recursive caching if you support resource expansion

    • Dirty checking logic is not too hard, but it does add complexity. Start off without it. Learn more at Stormpath.com
  34. Lessons Learned: Promises var promise = account.getGroups(); promise.then(function() { //called

    on success }, function() { //called on error }, function() { //called during progress }); Learn more at Stormpath.com
  35. Lessons Learned: async.js async.waterfall([ function(callback){ callback(null, 'one', 'two'); }, function(arg1,

    arg2, callback){ // arg1 now equals 'one' and arg2 now equals 'two' callback(null, 'three'); }, function(arg1, callback){ // arg1 now equals 'three' callback(null, 'done'); } ], function (err, result) { // result now equals 'done' }); Learn more at Stormpath.com