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

node.js Module Development

1318668b99b2d5a3900f3f7758763a69?s=47 Jay Harris
September 14, 2013

node.js Module Development

Node.js has given JavaScript a new resurgence as a server-side language. No longer just for image rollovers and AJAX, JS is now available as a platform for creating lightning-fast, lightweight, networked applications. In this session, we will move beyond Node’s base web servers and Twitter applications, and into module development: those small, reusable components that are the foundation for every business application on every platform. Learn how to create a module within Node.js, how to test your module and validate functionality, and how to get your creation distributed into the wild. With this knowledge, you can make the next great Node package and become famous.

Author:
Jay Harris
Problem Solver | Arana Software
jay@aranasoft.com | www.aranasoft.com
www.twitter.com/jayharris

1318668b99b2d5a3900f3f7758763a69?s=128

Jay Harris

September 14, 2013
Tweet

Transcript

  1. M O D U L E D E V E

    L O P M E N T
  2. Node.js is a platform built on Chrome's JavaScript runtime for

    easily building fast, scalable network applications. “ ” What is node.js?
  3. None
  4. ./

  5. sourceControl git  init

  6. javascriptFiles mkdir  ./lib

  7. executableFiles mkdir  ./bin

  8. testFiles mkdir  ./test

  9. documentationFiles mkdir  ./doc mkdir  ./example mkdir  ./man

  10. informationalFiles touch  ./README touch  ./LICENSE touch  ./AUTHOR

  11. ./bin ./doc ./example ./lib ./man ./test singularNouns

  12. packageManagement

  13. Package Manager

  14. github: install: isaacs/npm comes with node

  15. npm install <package> localInstallation

  16. npm install <package> --global globalInstallation -g or --global

  17. npm install <package> --link dualInstallation

  18. npm install <package> --save[-dev] dependencyReferences --save or --save-dev

  19. npm install updateDependencies

  20. npm init packageInitialization

  21. $  npm  init name: (sample-node) Sample version: (0.0.0) 0.1.0 description:

    This is a sample module entry point: (index.js) _
  22. author:  Jay  Harris license:  (BSD)  BSD About  to  write  to

     /Projects/sample-­‐node/package.json: {    "name":  "Sample",    "version":  "0.1.0",    "description":  "This  is  a  sample  module",    "main":  "index.js",    "scripts":  {        "test":  "echo  \"Error:  no  test  specified\"  &&  exit  1"    },    "author":  "Jay  Harris",    "license":  "BSD" } Is  this  ok?  (yes)  _
  23. ./package.json { "name": "Sample", "version": "0.1.0", "description": "This is a

    sample module", "main": "index.js", "scripts": { "test": "echo \"Error: no tests avail.\" && exit 1" }, "author": "Jay Harris", "license": "BSD" } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
  24. npm init is additive not destructive

  25. moduleCreation

  26. module

  27. module.exports

  28. ./lib/sample.js module.exports.sayHello = function() { return "Hello World!"; } 1

    2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
  29. var  sample  =  require("./lib/sample.js"); //  Returns  'Hello  World!' sample.sayHello();

  30. ./lib/person.js function Person(first, last) { if (!(this instanceof Person)) {

    return new Person(first, last); } this.firstName = first; this.lastName = last; return this; } module.exports = Person; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
  31. var person = require("./lib/person.js"); // This will return undefined person.firstName;

    // This will return 'Jay' var jayHarris = new Person('Jay','Harris'); jayHarris.firstName;
  32. function Person(first, last) { // ... } module.exports = Person;

    // This is a public method; Person.prototype.sayHello = function() { return _join.call(this, "Hello", this.firstName); } // This is a private method var _join(first, second) { return first + ' ' + second; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  33. var  person  =  require("./lib/person.js"); //  This  will  throw  'Has  No

     Method'  error person.sayHello(); //  This  will  return  'Hello  Jay' var  jayHarris  =  new  Person('Jay','Harris'); jayHarris.sayHello();
  34. function Person(first, last) { // ... } module.exports = Person;

    // This is a static method module.exports.sayHello = function() { return "Hello World!"; } // This is a public instance method; Person.prototype.sayHello = function() { return _join.call(this, "Hello", this.firstName); } // This is a private method var _join(first, second) { return first + ' ' + second; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  35. var  person  =  require("./lib/person.js"); //  This  will  return  'Hello  World!'

    person.sayHello(); //  This  will  return  'Hello  Jay' var  jayHarris  =  new  Person('Jay','Harris'); jayHarris.sayHello();
  36. eventEmitter

  37. var  EventEmitter  =  require('events').EventEmitter;

  38. EventEmitter.call(this);

  39. var  util  =  require('util'); //  ... util.inherits(MyClass,  EventEmitter);

  40. var EventEmitter = require('events').EventEmitter , util = require('util'); function Person(first,

    last) { // ... EventEmitter.call(this); // ... } util.inherits(Person, EventEmitter); Person.prototype.goToBed = function() { this.emit("sleep"); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  41. var  person  =  require("./person.js"); //  This  will  return  'Hello  Jay'

    var  jayHarris  =  new  Person('Jay','Harris'); jayHarris.on("sleep",  function()  {        console.log("Goodnight,  Jay"); } jayHarris.goToBed(); //  Output  'Goodnight,  Jay'
  42. testingNode.js

  43. TDD is to coding style as yoga is to posture.

    Even when you're not actively practicing, having done so colors your whole life healthier.” j. kerr “
  44. assertingCorrectness

  45. var  assert  =  require('assert');

  46. assert(value)            .ok(value)      

         .equal(actual,  expected)            .notEqual(actual,  expected)            .deepEqual(actual,  expected)            .notDeepEqual(actual,  expected)            .strictEqual(actual,  expected)            .notStrictEqual(actual,  expected)            .throws(block,  [error])            .doesNotThrow(block,  [error])            .ifError(value)
  47. var assert = require('assert'); // Will pass assert.ok(true); // Will

    throw an exception assert.ok(false); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  48. var assert = require('assert'); // Will throw 'false == true'

    error assert.ok(typeof 'hello' === 'number'); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  49. $  node  test.js assert.js:104    throw  new  assert.AssertionError({    

               ^ AssertionError:  false  ==  true        at  Object.<anonymous>  (my-­‐test.js:7:8)        at  Module._compile  (module.js:449:26)        at  Object.Module._extensions..js  (module.js:467:10)        at  Module.load  (module.js:356:32)        at  Function.Module._load  (module.js:312:12)        at  Module.runMain  (module.js:487:10)        at  process.startup.processNextTick.process._tick... $  _
  50. Chai Assertion Library

  51. github: install: chaijs/chai npm install chai

  52. isTrue,                  

       isFalse, isNull,                      isNotNull, isUndefined,            isDefined, isFunction,              isNotFunction, isArray,                    isNotArray, isBoolean,                isNotBoolean, isNumber,                  isNotNumber, isString,                  isNotString,                                    include,                                    lengthOf,                                    operator,                                    closeTo   isObject,                  isNotObject, typeOf,                      notTypeOf, instanceOf,              notInstanceOf, match,                        notMatch, property,                  notProperty, deepProperty,          notDeepProperty, propertyVal,            propertyNotVal, deepPropertyVal,    deepPropertyNotVal, additional assertions
  53. var  assert  =  require('chai').assert;

  54. var assert = require('chai').assert; // Will throw 'expected 'hello' to

    be a number' assert.isNumber('hello'); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  55. $  node  test.js expected  'hello'  to  be  a  number $

     _
  56. var chai = require('chai') , assert = chai.assert; chai.Assertion.includeStack =

    true; // Will throw and display stack assert.isNumber('hello'); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  57. expectAssertions

  58. expectSyntax expect(person).to.be.an('object');        .with.property('age')        .that.is.a('number')

           .that.equals(34); assert.isObject(person); assert.property(person,  "age"); assert.isNumber(person.age); assert.equals(person.age,  34); assertSyntax
  59. var  expect  =  require('chai').expect;

  60. assertionChains for readability

  61. expect(person).to.exist        .and.be.an('object')        .with.property('age')  

         .that.is.to.exist        .and.is.a('number')        .and.equals(34);
  62. .to .be .been .is .that .and .have .with syntaxSugar for

    readability
  63. expect(person).to.exist        .and.be.an('object')        .with.property('age')  

         .that.is.to.exist        .and.is.a('number')        .and.equals(34);
  64. expect(person).to.exist        .and.be.an('object')        .with.property('age')  

         .that.is.to.exist        .and.is.a('number')        .and.equals(34);
  65. .property subjectChange from original object

  66. expect(person)    .that.is.an('object')    .with.property('address')        .that.is.an('object')  

         .with.property('city')            .that.is.a('string')            .and.equals('Detroit')
  67. testingFramework

  68. mocha simple, flexible, fun

  69. mocha github: install: visionmedia/mocha npm install -g mocha

  70. $  npm  install  -­‐g  mocha $  mkdir  test $  mocha

           ✔  0  tests  complete  (1ms) $  
  71. var  mocha  =  require('mocha');

  72. describe('Testing  out  this  thing',  function()  {        it('should

     do  stuff',  function()  {                //  Assertion  tests        }); }); bddSyntax
  73. ./test/my-test.js var assert = require('assert'); describe('Assertions', function() { it('should pass

    on truthiness', function() { assert.ok(true); }); it('should fail on falsiness', function() { assert.ok(false); }); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
  74. $  mocha  -­‐-­‐reporter  spec    Assertions        ✓

     should  pass  on  truthiness          1)  should  fail  on  falsiness    ✖  1  of  2  tests  failed:    1)  Assertions  should  fail  on  falsiness:              AssertionError:  false  ==  true            at  (stack  trace  omitted  for  brevity) $  _
  75. groupedTests

  76. describe('Testing  out  this  thing',  function()  {        describe('with

     a  subset  of  this  other  thing',  function()  {                it('should  do  stuff',  function()  {                        //  Assertion  tests                });        }); }); groupSyntax
  77. var expect = require('chai').expect; describe('Assertions', function() { describe('on equality', function()

    { it('should pass on truthiness', function() { expect(true).is.true; }); it('should pass on falsiness', function() { expect(false).is.false; }); }); describe('on type', function() { it('should pass on number', function() { expect(5).is.a('number'); }); }); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  78. $  mocha  -­‐-­‐reporter  spec    Assertions        of

     equality            ✓  should  pass  on  truthiness              ✓  should  pass  on  falsiness          on  type            ✓  should  pass  on  number      ✔  3  tests  complete  (6ms) $  _
  79. pendingTests

  80. describe('Testing  out  this  thing',  function()  {        describe('with

     a  subset  of  this  other  thing',  function()  {                it('should  do  stuff  someday');        }); }); pendingSyntax
  81. var assert = require('chai').assert; describe('Assertions', function() { describe('of truthiness', function()

    { it('should pass on truthiness', function() { expect(true).is.true; }); it('should pass on falsiness', function() { expect(false).is.false; }); }); describe('of type', function() { it('should pass on number', function() { expect(5).is.a('number'); }); it('should pass on object'); }); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  82. $  mocha  -­‐-­‐reporter  spec    Assertions        of

     truthiness            ✓  should  pass  on  truthiness            ✓  should  pass  on  falsiness        of  type            ✓  should  pass  on  number              -­‐  should  pass  on  object    ✔  4  tests  complete  (6ms)    •  1  test  pending $  _
  83. skippedTests

  84. describe('Testing  out  this  thing',  function()  {        it.skip('should

     be  skipped',  function()  {                //  Assertion  tests        }); }); describe.skip('This  entire  suite  will  be  skipped',  function()  {        it('should  do  stuff',  function()  {                //  Assertion  tests        }); }); skipSyntax
  85. describe('Assertions', function() { describe('of truthiness', function() { it('should pass on

    truthiness', function() { assert.isTrue(true); }); it('should pass on falsiness', function() { assert.isFalse(false); }); }); describe('of type', function() { it.skip('should pass on number', function() { assert.isNumber(5); }); it('should pass on object'); }); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  86. $  make  test    Assertions        of  truthiness

               ✓  should  pass  on  truthiness              ✓  should  pass  on  falsiness          of  type            -­‐  should  pass  on  number              -­‐  should  pass  on  object    ✔  4  tests  complete  (6ms)    •  2  test  pending $  _
  87. setupTeardown

  88. before(); beforeEach(); after(); afterEach();

  89. describe('Testing  out  this  thing',  function()  {        before(function(){

                   //  ...        };        describe('with  a  subset  of  that  thing',  function()  {                it('should  do  stuff',  function()  {                        //  Assertion  tests                });                afterEach(function(){                        //  ...                };        }); }); setupTeardown
  90. asynchronousTests

  91. it('should  not  error',  function(done)  {        search.find("Apples",  done);

    }); asynchronousSyntax
  92. it('should  not  error',  function(done)  {        search.find("Apples",  done);

    }); it('should  return  2  items',  function(done)  {        search.find("Apples",  function(err,  res)  {                if  (err)  return  done(err);                res.should.have.length(2);                done();        }); }); asynchronousSyntax
  93. All Mocha functions accept this callback

  94. describe('When searching for Apples', function(done) { before(function(done){ items.save(['fiji apples', 'empire

    apples'], done); }); it('should not error', function(done) { search.find("Apples", done); }); it('should return 2 items', function(done) { search.find("Apples", function(err, res) { if (err) return done(err); res.should.have.length(2); done(); }); }); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  95. simplifyExecution

  96. $  mocha  <args> should be simplified to $  make  test

    and $  npm  test
  97. ./makefile # Makefile for sample module test: mocha --reporter spec

    --ui bdd .PHONY: test 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
  98. ./makefile # Makefile for sample module test: mocha \ --reporter

    spec \ --ui bdd .PHONY: test 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
  99. $  make  test    Assertions        ✓  should

     pass  on  truthiness          1)  should  fail  on  falsiness    ✖  1  of  2  tests  failed:    1)  Assertions  should  fail  on  falsiness:              AssertionError:  false  ==  true            at  (stack  trace  omitted  for  brevity) $  _
  100. ./package.json { "name": "sample", "version": "0.1.0", "scripts": { "test": "make

    test" } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
  101. $  npm  test    Assertions        ✓  should

     pass  on  truthiness          1)  should  fail  on  falsiness    ✖  1  of  2  tests  failed:    1)  Assertions  should  fail  on  falsiness:              AssertionError:  false  ==  true            at  (stack  trace  omitted  for  brevity) $  _
  102. eliminate global dependency $  npm  install  mocha  -­‐-­‐link

  103. test: @./node_modules/.bin/mocha \ --reporter spec \ --ui bdd .PHONY: test

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  104. update dev dependencies $  npm  install  mocha  -­‐-­‐save-­‐dev $  npm

     install  chai    -­‐-­‐save-­‐dev
  105. $  npm  install  mocha  -­‐-­‐save-­‐dev $  npm  install  chai  

     -­‐-­‐save-­‐dev $  git  diff  package.json   diff  -­‐-­‐git  a/package.json  b/package.json index  439cf44..3609bb9  100644 -­‐-­‐-­‐  a/package.json +++  b/package.json @@  -­‐1,5  +1,7  @@  {      "name":  "sample", -­‐    "version":  "0.1.0" +    "version":  "0.1.0", +    "devDependencies":  { +        "mocha":  "~1.9.0", +        "chai":  "~1.6.0" +    }  }
  106. notificationSystems

  107. mocha --watch continuousTesting -w or --watch

  108. $  mocha  -­‐-­‐watch  ✔  5  tests  complete  (22ms)  ^  watching

  109. Growl

  110. mac: win: also: apple app store growlForWindows.com growlNotify

  111. mocha --growl growlNotifications -G or --growl

  112. ⌘S

  113. test: @./node_modules/.bin/mocha \ --reporter spec \ --ui bdd watch: @./node_modules/.bin/mocha

    \ --reporter min \ --ui bdd \ --growl \ --watch .PHONY: test watch 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  114. mockingObjects

  115. JavaScript ships with a mocking framework ...it’s called JavaScript

  116. var me = {firstName: 'Jay' , lastName: 'Harris' , getFullName:

    function() { return this.firstName + ' ' + this.lastName; }}; // Returns 'Jay Harris' me.getFullName(); me.getFullName = function() { return 'John Doe'; }; // Returns 'John Doe' me.getFullName(); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  117. nock HTTP Mocking Library

  118. nock github: install: flatiron/nock npm install nock

  119. var http = require('http'); var reqOptions = { host: 'api.twitter.com',

    path: '/1/statuses/user_timeline.json?' + 'screen_name=jayharris' }; // ... http.request(reqOptions, resCallback).end(); // Returns live Twitter data 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  120. var nock = require('nock'); var twitter = nock('http://api.twitter.com') .get('/1/statuses/user_timeline.json?'+ 'screen_name=jayharris')

    .reply(200, "This worked"); // Returns "This worked" http.request(reqOptions, resCallback).end(); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  121. // Returns live Twitter data http.request(reqOptions, resCallback).end(); var nock =

    require('nock'); var twitter = nock('http://api.twitter.com') .get('/1/statuses/user_timeline.json?'+ 'screen_name=jayharris') .reply(200, "This worked"); // Returns "This worked" http.request(reqOptions, resCallback).end(); // Returns live Twitter data http.request(reqOptions, resCallback).end(); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  122. nock nock.recorder.rec(  ); nock.recorder.play(  );

  123. Spies, Stubs, & Mocks Sinon.js

  124. github: install: cjohansen/Sinon.JS npm install sinon Sinon.js

  125. versionTesting

  126. nvm Node Version Manager

  127. nvm github: github: creationix/nvm hakobera/nvmw

  128. $  nvm  list        v0.4.0      

     v0.6.18        v0.8.21        v0.4.7          v0.8.1        v0.10.0        v0.6.0          v0.8.8 current:     v0.8.1 10  -­‐>  0.10.0  (-­‐>  v0.10.0) 4  -­‐>  0.4.7  (-­‐>  v0.4.7) 6  -­‐>  0.6.18  (-­‐>  v0.6.18) 8  -­‐>  0.8.21  (-­‐>  v0.8.21) default  -­‐>  0.10.0  (-­‐>  v0.10.0) newest  -­‐>  0.10.0  (-­‐>  v0.10.0) $  _
  129. $  node  -­‐-­‐version v0.10.0 $  nvm  install  v0.10.6 Now  using

     node  v0.10.6 /Users/jayharris/.nvm/v0.10.6/bin/npm $  node  -­‐-­‐version v0.10.6 $  _
  130. packageDistribution

  131. Package Manager

  132. ./.npmignore filePackaging

  133. npm adduser userSetup

  134. $  npm  adduser Username:  (jayharris)  jayharris Password: Email:  (jay@aranasoft.com)  _

  135. $  npm  whoami jayharris $  _

  136. npm publish packagePublish

  137. {  name:  'morale',    description:  'Async  API  wrapper  for  Morale',

       'dist-­‐tags':  {  latest:  '0.2.0'  },    versions:        [  '0.1.0',          '0.1.2',          '0.2.0'  ],    maintainers:  'jayharris  <jay@aranasoft.com>',    time:        {  '0.1.0':  '2012-­‐01-­‐23T03:24:59.824Z',          '0.1.2':  '2012-­‐01-­‐25T23:20:52.927Z',          '0.2.0':  '2012-­‐08-­‐13T16:23:28.488Z'  },    author:  'Arana  Software  <info@aranasoft.com>',    repository:        {  type:  'git',          url:  'git://github.com/aranasoft/morale-­‐node.git'  },    users:  {  fgribreau:  true  },    version:  '0.2.0',
  138. $  npm  publish npm  http  PUT  https://registry.npmjs.org/morale npm  http  409

     https://registry.npmjs.org/morale npm  http  GET  https://registry.npmjs.org/morale npm  http  200  https://registry.npmjs.org/morale npm  http  PUT  https://registry.npmjs.org/morale/0.2.1/-­‐tag/latest npm  http  201  https://registry.npmjs.org/morale/0.2.1/-­‐tag/latest npm  http  GET  https://registry.npmjs.org/morale npm  http  200  https://registry.npmjs.org/morale npm  http  PUT  https://registry.npmjs.org/morale/-­‐/ morale-­‐0.2.1.tgz/-­‐rev/25-­‐c9bbf49ea0bd2a750e257153fab5794b npm  http  201  https://registry.npmjs.org/morale/-­‐/ morale-­‐0.2.1.tgz/-­‐rev/25-­‐c9bbf49ea0bd2a750e257153fab5794b +  morale@0.2.1 $  _
  139. {  name:  'morale',    description:  'Async  API  wrapper  for  Morale',

       'dist-­‐tags':  {  latest:  '0.2.1'  },    versions:        [  '0.1.0',          '0.1.2',          '0.2.0',          '0.2.1'  ],    maintainers:  'jayharris  <jay@aranasoft.com>',    time:        {  '0.1.0':  '2012-­‐01-­‐23T03:24:59.824Z',          '0.1.2':  '2012-­‐01-­‐25T23:20:52.927Z',          '0.2.0':  '2012-­‐08-­‐13T16:23:28.488Z',          '0.2.1':  '2012-­‐08-­‐30T19:10:20.133Z'  },    author:  'Arana  Software  <info@aranasoft.com>',    repository:        {  type:  'git',          url:  'git://github.com/aranasoft/morale-­‐node.git'  },
  140. { "private": "true" } privatePackages

  141. ./package.json { "name": "Sample", "version": "0.1.0", "description": "This is a

    sample module", "main": "index.js", "scripts": { "test": "echo \"Error: no tests avail.\" && exit 1" }, "author": "Jay Harris", "license": "BSD", "private": "true" } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
  142. $  npm  publish npm  ERR!  Error:  This  package  has  been

     marked  as  private npm  ERR!  Remove  the  'private'  field  from  the  package.json  to   publish  it. $  _
  143. npm shrinkwrap versionLockdown

  144. $  npm  shrinkwrap wrote  npm-­‐shrinkwrap.json $  _

  145. { "name": "morale", "version": "0.2.1", "dependencies": { "underscore": { "version":

    "1.3.3" }, "mocha": { "version": "1.3.0", "dependencies": { "commander": { "version": "0.6.1" }, "growl": { "version": "1.5.1" }, "jade": { "version": "0.26.3", 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  146. $  rm  npm-­‐shrinkwrap.json $  npm  update $  npm  test $

     npm  shrinkwrap
  147. tellEveryone

  148. Go make some Awesome

  149. jay harris P R E S I D E N

    T jay@aranasoft.com #nodemoduledev @jayharris