node.js essentials essentials.pdf · chapter 7, socket.io, explores the real-time communication...

Post on 29-May-2020

16 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Node.jsEssentials

TableofContents

Node.jsEssentials

Credits

AbouttheAuthor

AbouttheReviewer

www.PacktPub.com

Supportfiles,eBooks,discountoffers,andmore

Whysubscribe?

FreeaccessforPacktaccountholders

Preface

Whatthisbookcovers

Whatyouneedforthisbook

Whothisbookisfor

Conventions

Readerfeedback

Customersupport

Downloadingtheexamplecode

Errata

Piracy

Questions

1.GettingStarted

Settingup

Hellorequire

Hellonpm

Summary

2.SimpleHTTP

Introducingrouting

Summary

3.Authentication

Basicauthentication

Bearertokens

OAuth

Summary

4.Debugging

Logging

Errorhandling

Summary

5.Configuration

JSONfiles

Environmentalvariables

Arguments

Summary

6.LevelDBandNoSQL

LevelDB

MongoDB

Summary

7.Socket.IO

Rooms

Authentication

Summary

8.CreatingandDeployingPackages

Creatingnpmpackages

Summary

9.UnitTesting

Installingmocha

Chai

Stubbingmethods

Summary

10.UsingMoreThanJavaScript

CoffeeScript

Codeblocksandfunctions

Theexistentialoperator

Objectsandarrays

Classes

Summary

Index

Node.jsEssentials

Node.jsEssentialsCopyright©2015PacktPublishing

Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.

Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthor,norPacktPublishing,anditsdealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecauseddirectlyorindirectlybythisbook.

PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.

Firstpublished:November2015

Productionreference:1301015

PublishedbyPacktPublishingLtd.

LiveryPlace

35LiveryStreet

BirminghamB32PB,UK.

ISBN978-1-78528-492-2

www.packtpub.com

CreditsAuthor

FabianCook

Reviewers

ShoubhikBose

GlennGeenen

CommissioningEditor

EdwardGordan

AcquisitionEditor

DivyaPoojari

ContentDevelopmentEditor

AthiraLaji

TechnicalEditor

NaveenkumarJain

CopyEditor

SnehaSingh

ProjectCoordinator

HarshalVed

Proofreader

SafisEditing

Indexer

HemanginiBari

ProductionCoordinator

ShantanuN.Zagade

CoverWork

ShantanuN.Zagade

AbouttheAuthorFabianCookisanexperiencedJavaScriptdeveloperwholivesinHawkesBay,NewZealand.HebeganworkingwithJavaandC#veryearlyinhislife,whichleadtousingNode.jsinanopensourcecontext.HeisnowcurrentlyworkingforaNewZealandISP,knownasNOWNZwheretheyareutilizingthefullpowerofNode.js,DockerandCoreOS.

AbouttheReviewerGlennGeenenisaNode.jsdeveloperwithabackgroundingameandmobiledevelopment.HehasmostlyworkedasaniOSconsultantbeforebecomingaNode.jsconsultantforhiscompany,GeenenTijd.

www.PacktPub.com

Supportfiles,eBooks,discountoffers,andmoreForsupportfilesanddownloadsrelatedtoyourbook,pleasevisitwww.PacktPub.com.

DidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusat<service@packtpub.com>formoredetails.

Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewslettersandreceiveexclusivediscountsandoffersonPacktbooksandeBooks.

https://www2.packtpub.com/books/subscription/packtlib

DoyouneedinstantsolutionstoyourITquestions?PacktLibisPackt’sonlinedigitalbooklibrary.Here,youcansearch,access,andreadPackt’sentirelibraryofbooks.

Whysubscribe?FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,print,andbookmarkcontentOndemandandaccessibleviaawebbrowser

FreeaccessforPacktaccountholdersIfyouhaveanaccountwithPacktatwww.PacktPub.com,youcanusethistoaccessPacktLibtodayandview9entirelyfreebooks.Simplyuseyourlogincredentialsforimmediateaccess.

PrefaceNode.jsissimplyatoolthatletsyouuseJavaScriptontheserverside.However,itactuallydoesmuchmorethanthat–byextendingJavaScript,itallowsforamuchmoreintegratedandefficientapproachtodevelopment.Itcomesasnosurprisethatit’safundamentaltoolforfull-stackJavaScriptdevelopers.Whetheryouworkonthebackendorfrontend,youadoptamuchmorecollaborativeandagilewayofworkingusingNode.js,sothatyouandyourteamcanfocusondeliveringaqualityendproduct.Thiswillensurethatyou’rereadytotakeonanynewchallengethatgetsthrownatyou.

Thisbookwillbefastpacedandcoverdependencymanagement,runningyourownHTTPserver,realtimecommunication,andeverythinginbetweenthatisneededtogetupandrunningwithNode.js.

WhatthisbookcoversChapter1,GettingStarted,coversthesetupofNode.js.Youwillalsocoverhowtoutilizeandmanagedependencies.

Chapter2,SimpleHTTP,covershowtorunasimpleHTTPserverandhelpsyouunderstandroutingandutilizationofmiddleware.

Chapter3,Authentication,coverstheutilizationofmiddlewareandJSONWebTokentoauthenticateusers.

Chapter4,Debugging,coverstheintegrationofpost-mortemtechniquesinyourdevelopmenttasksandhowtodebugyourNode.jsprograms.

Chapter5,Configuration,coverstheconfigurationandmaintenanceofyoursoftwareusingcentralizedconfigurationoptions,arguments,andenvironmentalvariables.

Chapter6,LevelDBandNoSQL,coverstheintroductionofNoSQLdatabases,suchasLevelDBandMongoDB.Italsocoverstheuseofthesimplekey/valuestoreandamorecompletedocumentdatabase.

Chapter7,Socket.IO,exploresthereal-timecommunicationbetweenclients,servers,andbackagainandalsohowitauthenticatesandnotifiestheusers.

Chapter8,CreatingandDeployingPackages,focusesonsharingthemodulesandcontributingtotheeco-system

Chapter9,UnitTesting,testsyourcodeusingMocha,Sinon,andChanceandalsocovershowtousemockswithfunctionsandgeneraterandomvaluestotestyourcode

Chapter10,UsingMoreThanJavaScript,explainstheusageofCoffeeScriptwithNode.jstoexpandlanguagecapabilities.

WhatyouneedforthisbookYouwillneedacomputerthatrunsUnix(Macintosh),LinuxorWindows,alongwithyourpreferredIntegratedDevelopmentEnvironment.Ifyoudon’thaveanIDEthenyouhaveafewoptions,suchas:

Atom:https://atom.io/Sublime:http://www.sublimetext.com/Cloud9:https://c9.io/

WhothisbookisforThebookwillbehelpfultoanybodywhowantstohaveknowledgeofNode.js(whatNode.jsisabout,howtouseit,whereit’susefulandwhentouseit).Familiaritywithserver-sideandNode.jsisaprerequisite.

ConventionsInthisbook,youwillfindanumberofstylesoftextthatdistinguishbetweendifferentkindsofinformation.Herearesomeexamplesofthesestyles,andanexplanationoftheirmeaning.

Codewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandlesareshownasfollows:“Wecanincludeothercontextsthroughtheuseoftheincludedirective.”

Ablockofcodeissetasfollows:

<scripttype='application/javascript'src='script_a.js'></script>

<scripttype='application/javascript'src='script_b.js'></script>

Anycommand-lineinputoroutputiswrittenasfollows:

[~]$npminstall-gn

Newtermsandimportantwordsareshowninbold.Wordsthatyouseeonthescreen,inmenusordialogboxesforexample,appearinthetextlikethis:“Iftheuserhasn’tpassedboththeusernameandpasswordtheserverwillreturn500BadRequest“.

NoteWarningsorimportantnotesappearinaboxlikethis.

TipTipsandtricksappearlikethis.

ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthisbook—whatyoulikedormayhavedisliked.Readerfeedbackisimportantforustodeveloptitlesthatyoureallygetthemostoutof.

Tosendusgeneralfeedback,simplysendane-mailto<feedback@packtpub.com>,andmentionthebooktitleviathesubjectofyourmessage.

Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideonwww.packtpub.com/authors.

CustomersupportNowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.

DownloadingtheexamplecodeYoucandownloadtheexamplecodefilesforallPacktbooksyouhavepurchasedfromyouraccountathttp://www.packtpub.com.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.

ErrataAlthoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyoufindamistakeinoneofourbooks—maybeamistakeinthetextorthecode—wewouldbegratefulifyouwouldreportthistous.Bydoingso,youcansaveotherreadersfromfrustrationandhelpusimprovesubsequentversionsofthisbook.Ifyoufindanyerrata,pleasereportthembyvisitinghttp://www.packtpub.com/submit-errata,selectingyourbook,clickingontheerratasubmissionformlink,andenteringthedetailsofyourerrata.Onceyourerrataareverified,yoursubmissionwillbeacceptedandtheerratawillbeuploadedonourwebsite,oraddedtoanylistofexistingerrata,undertheErratasectionofthattitle.Anyexistingerratacanbeviewedbyselectingyourtitlefromhttp://www.packtpub.com/support.

PiracyPiracyofcopyrightmaterialontheInternetisanongoingproblemacrossallmedia.AtPackt,wetaketheprotectionofourcopyrightandlicensesveryseriously.Ifyoucomeacrossanyillegalcopiesofourworks,inanyform,ontheInternet,pleaseprovideuswiththelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.

Pleasecontactusat<copyright@packtpub.com>withalinktothesuspectedpiratedmaterial.

Weappreciateyourhelpinprotectingourauthors,andourabilitytobringyouvaluablecontent.

QuestionsYoucancontactusat<questions@packtpub.com>ifyouarehavingaproblemwithanyaspectofthebook,andwewilldoourbesttoaddressit.

Chapter1.GettingStartedEveryWebdevelopermusthavecomeacrossiteveryonceinawhile,eveniftheyjustdabbleinsimpleWebpages.WheneveryouwanttomakeyourWebpagealittlemoreinteractive,yougrabyourtrustworthyfriends,suchasJavaScriptandjQuery,andhacktogethersomethingnew.YoumighthavedevelopedsomeexcitingfrontendapplicationsusingAngularJSorBackboneandwanttolearnmoreaboutwhatelseyoucandowithJavaScript.

WhiletestingyourwebsiteonmultiplebrowsersyoumusthavecomeacrossGoogleChromeatsomepointandyoumighthavenoticedthatitisagreatplatformforJavaScriptapplications.

GoogleChromeandNode.jshavesomethingverybigincommon:theybothworkonGoogle’shigh-performanceV8JavaScriptengine,thisgivesusthesameengineinthebrowserthatwewillbeusinginthebackend,prettycool,right?

SettingupInordertogetstartedanduseNode.js,weneedtodownloadandinstallNode.js.Thebestwaytoinstallitwillbetoheadovertohttps://nodejs.org/anddownloadtheinstaller.

Atthetimeofwriting,thecurrentversionofNode.jsis4.2.1.

Toensureconsistency,wearegoingtouseanpmpackagetoinstallthecorrectversionofNode.JSand,forthis,wearegoingtousethenpackagedescribedathttps://www.npmjs.com/package/n.

Currently,thispackagehassupportonlyfor*nixmachines.ForWindows.seenvm-windowsordownloadthebinaryfor4.2.1fromhttps://nodejs.org/dist/v4.2.1/.

OnceyouhaveNode.jsinstalled,openaterminalandrun:

[~]$npminstall-gn

The–gargumentwillinstallthepackagegloballysowecanusethepackageanywhere.

Linuxusersmayneedtoruncommandsthatinstallglobalpackagesassudo.

Usingtherecentlyinstallpackage,run:

[~]$n

Thiswilldisplayascreenwiththefollowingpackages:

node/0.10.38

node/0.11.16

node/0.12.0

node/0.12.7

node/4.2.1

Ifnode/4.2.1isn’tmarkedwecansimplyrunthefollowingpackages;thiswillensurethatnode/4.2.1getsinstalled:

[~]$sudon4.2.1

Toensurethatthenodeisgood-to-go,letscreateandrunasimplehelloworldexample:

[~/src/examples/example-1]$touchexample.js

[~/src/examples/example-1]$echo"console.log(\"Helloworld\")">

example.js

[~/src/examples/example-1]$nodeexample.js

HelloWorld

Cool,itworks;nowlet’sgetdowntobusiness.

HellorequireIntheprecedingexample,wejustloggedasimplemessage,nothinginteresting,solet’sdiveabitdeeperinthissection.

Whenusingmultiplescriptsinthebrowser,weusuallyjustincludeanotherscripttagsuchas:

<scripttype='application/javascript'src='script_a.js'></script>

<scripttype='application/javascript'src='script_b.js'></script>

Boththesescriptssharethesameglobalscope,thisusuallyleadstosomeunusualconflictswhenpeoplewanttogivevariablesthesamename.

//script_a.js

functionrun(){

console.log("I'mrunningfromscript_a.js!");

}

$(run);

//script_b.js

functionrun(){

console.log("I'mrunningfromscript_b.js!");

}

$(run);

Thiscanleadtoconfusion,andwhenmanyfilesareminifiedandcrammedtogetheritcausesaproblem;script_adeclaresaglobalvariable,whichisthendeclaredagaininscript_band,onrunningthecode,weseethefollowingontheconsole:

>I'mrunningfromscript_b.js!

>I'mrunningfromscript_b.js!

Themostcommonmethodtogetaroundthisandtolimitthepollutionoftheglobalscopeistowrapourfileswithananonymousfunction,asshown:

//script_a.js

(function($,undefined){

functionrun(){

console.log("I'mrunningfromscript_a.js!");

}

$(run);

})(jQuery);

//script_b.js

(function($,undefined){

functionrun(){

console.log("I'mrunningfromscript_b.js!");

}

$(run);

})(jQuery);

Nowwhenwerunthis,itworksasexpected:

>I'mrunningfromscript_a.js!

>I'mrunningfromscript_b.js!

Thisisgoodforcodethatisn’tdependeduponexternally,butwhatdowedoforthecodethatis?Wejustexportit,right?

Somethingsimilartothefollowingcodewilldo:

(function(undefined){

functionLogger(){

}

Logger.prototype.log=function(message/*...*/){

console.log.apply(console,arguments);

}

this.Logger=Logger;

})()

Now,whenwerunthisscript,wecanaccessLoggerfromtheglobalscope:

varlogger=newLogger();

logger.log("This","is","pretty","cool")

>Thisisprettycool

Sonowwecanshareourlibrariesandeverythingisgood;ButwhatifsomeoneelsealreadyhasalibrarythatexposesthesameLoggerclass.

Whatdoesnodedotosolvethisissue?Hellorequire!

Node.jshasasimplewaytobringinscriptsandmodulesfromexternalsources,comparabletorequireinPHP.

Letscreateafewfilesinthisstructure:

/example-2

/util

index.js

logger.js

main.js

/*util/index.js*/

varlogger=newLogger()

varutil={

logger:logger

};

/*util/logger.js*/

functionLogger(){

}

Logger.prototype.log=function(message/*...*/){

console.log.apply(console,arguments);

};

/*main.js*/

util.logger.log("Thisisprettycool");

Wecanseethatmain.js.isdependentonutil/index.js,whichisinturndependentonutil/logger.js.

Thisshouldjustworkright?Maybenot.Let’srunthecommand:

[~/src/examples/example-2]$nodemain.js

ReferenceError:loggerisnotdefined

atObject.<anonymous>(/Users/fabian/examples/example-2/main.js:1:63)

/*Removedforsimplicity*/

atNode.js:814:3

Sowhyisthis?Shouldn’ttheybesharingthesameglobalscope?Well,inNode.jsthestoryisabitdifferent.Rememberthoseanonymousfunctionsthatwewerewrappingourfilesinearlier?Node.jswrapsourscriptsinthemautomaticallyandthisiswhererequirefitsin.

Letsfixourfiles,asshown:

/*util/index.js*/

Logger=require("./logger")

/*main.js*/

util=require("./util");

Ifyounotice,Ididn’tuseindex.jswhenrequiringutil/index.js;thereasonforthisisthatwhenyouarequireafolderratherthanafileyoucanspecifyanindexfilethatcanrepresentthatfolder’scode.Thiscanbehandyforsomethingsuchasamodelfolderwhereyouexposeallyourmodelsinonerequireratherthanhavingaseparaterequireforeachmodel.

Sonow,wehaverequiredourfiles.Butwhatdowegetback?

[~/src/examples/example-2]$node

>varutil=require("./util");

>console.log(util);

{}

Still,thereisnologger.Wehavemissedanimportantstep;wehaven’ttoldNode.jswhatwewanttoexposeinourfiles.

ToexposesomethinginNode.js,weuseanobjectcalledmodule.exports.Thereisashorthandreferencetoitthatisjustexports.Whenourfileiswrappedinananonymousfunction,bothmoduleandexportsarepassedasaparameter,asshowninthefollowingexample:

functionModule(){

this.exports={};

}

functionrequire(file){

//.....

returnsmodule.exports;

}

varmodule=newModule();

varexports=module.exports;

(function(exports,require,module){

exports="Valuea"

module.exports="Valueb"

})(exports,require,module);

console.log(module.exports);

//Valueb

TipDownloadingtheexamplecode

YoucandownloadtheexamplecodefilesforallPacktbooksyouhavepurchasedfromyouraccountathttp://www.packtpub.com.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.

Theexampleshowsthatexportsisinitiallyjustareferencetomodule.exports.Thismeansthat,ifyouuseexports={},thevalueyousetitaswon’tbeaccessibleoutsidethefunction’sscope.However,whenyouaddpropertiestoanexportsobject,youareactuallyaddingpropertiestothemodule.exportsobjectastheyareboththesamevalue.Assigningavaluetomodule.exportswillexportthatvaluebecauseitisaccessibleoutsidethefunction’sscopethroughthemodule.

Withthisknowledge,wecanfinallyrunourscriptinthefollowingmanner:

/*util/index.js*/

Logger=require("./logger.js");

exports.logger=newLogger();

/*util/logger.js*/

functionLogger(){

}

Logger.prototype.log=(message/*...*/){

console.log.apply(console,arguments);

};

module.exports=Logger;

/*main.js*/

util=require("./utils");

util.logger.log("Thisisprettycool");

Runningmain.js:

[~/src/examples/example-2]$nodemain.js

Thisisprettycool

Requirecanalsobeusedtoincludemodulesinourcode.Whenrequiringmodules,wedon’tneedtouseafilepath,wejustneedthenameofthenodemodulethatwewant.

Node.jsincludesmanyprebuiltcoremodules,oneofwhichistheutilmodule.Youcanfinddetailsontheutilmoduleathttps://nodejs.org/api/util.html.

Let’sseetheutilmodulecommand:

[~]$node

>varutil=require("util")

>util.log('Thisisprettycoolaswell')

01Jan00:00:00-Thisisprettycoolaswell

HellonpmAlongwithinternalmodulesthereisalsoanentireecosystemofpackages;themostcommonpackagemanagerforNode.jsisnpm.Atthetimeofwriting,thereareatotalof192,875packagesavailable.

Wecanusenpmtoaccesspackagesthatdomanythingsforus,fromroutingHTTPrequeststobuildingourprojects.Youcanalsobrowsethepackagesavailableathttps://www.npmjs.com/.

Usingapackagemanageryoucanbringinothermodules,whichisgreatasyoucanspendmoretimeworkingonyourbusinesslogicratherthanreinventingthewheel.

Let’sdownloadthefollowingpackagetomakeourlogmessagescolorful:

[~/src/examples/example-3]$npminstallchalk

Now,touseit,createafileandrequireit:

[~/src/examples/example-3]$touchindex.js

/*index.js*/

varchalk=require("chalk");

console.log("Iamjustnormaltext")

console.log(chalk.blue("Iambluetext!"))

Onrunningthiscode,youwillseethefirstmessageinadefaultcolorandthesecondmessageinblue.Let’slookatthecommand:.

[~/src/examples/example-3]$nodeindex.js

Iamjustnormaltext

Iambluetext!

Havingtheabilitytodownloadexistingpackagescomesinhandywhenyourequiresomethingthatsomeoneelsehasalreadyimplemented.Aswesaidearlier,therearemanypackagesouttheretochoosefrom.

Weneedtokeeptrackofthesedependenciesandthereisasimplesolutiontothat:package.json.

Usingpackage.jsonwecandefinethings,suchasthenameofourproject,whatthemainscriptis,howtoruntests,ourdependencies,andsoon.Youcanfindafulllistofpropertiesathttps://docs.npmjs.com/files/package.json.

npmprovidesahandycommandtocreatethesefilesanditwillaskyoutherelevantquestionsneededtocreateyourpackage.jsonfile:

[~/src/examples/example-3]$npminit

Theprecedingutilitywillwalkyouthroughthecreationofapackage.jsonfile.

Itonlycoversthemostcommonitemsandtriestoguessvaliddefaults.

Runthenpmhelpjsoncommandfordefinitivedocumentationonthesefieldsandtoknowwhattheydoexactly.

Afterwards,usenpmandinstall<pkg>--savetoinstallapackageandsaveitasadependencyinthepackage.jsonfile.

Press^Ctoquitatanytime:

name:(example-3)

version:(1.0.0)

description:

entrypoint:(main.js)

testcommand:

gitrepository:

keywords:

license:(ISC)

Abouttowriteto/examples/example-3/package.json:

{

"name":"example-3",

"version":"1.0.0",

"description":"",

"main":"main.js",

"scripts":{

"test":"echo\"Error:notestspecified\"&&exit1"

},

"author":"....",

"license":"ISC"

}

Isthisok?(yes)

Theutilitywillprovideyouwithdefaultvalues,soitiseasiertojustskipthroughthemusingtheEnterkey.

Nowwheninstallingourpackagewecanusethe--saveoptiontosavechalkasadependency,asshown:

[~/src/examples/example-3]$npminstall--savechalk

Wecanseechalkhasbeenadded:

[~/examples/example-3]$catpackage.json

{

"name":"example-3",

"version":"1.0.0",

"description":"",

"main":"main.js",

"scripts":{

"test":"echo\"Error:notestspecified\"&&exit1"

},

"author":"...",

"license":"ISC",

"dependencies":{

"chalk":"^1.0.0"

}

}

Wecanaddthesedependenciesmanuallybymodifyingpackage.json;thisisthemostcommonmethodtosavedependenciesoninstallation.

Youcanreadmoreaboutthepackagefileat:https://docs.npmjs.com/files/package.json.

Ifyouarecreatingaserveroranapplicationratherthanamodule,youmostlikelywanttofindawaytostartyourprocesswithouthavingtogiveapathtoyourmainfileallthetime;thisiswherethescriptobjectinyourpackage.jsonfilecomesintoplay.

Tosetyourstartupscript,youjustneedtosetthestartpropertyinthescriptsobject,asshown:

"scripts":{

"test":"echo\"Error:notestspecified\"&&exit1",

"start":"nodeserver.js"

}

Now,allweneedtodoisrunnpmstartandthennpmwillrunthestartscriptwehavealreadyspecified.

Wecandefinemorescripts,forexampleifwewantastartscriptforthedevelopmentenvironmentwecanalsodefineadevelopmentproperty;withnon-standardscriptnameshowever,insteadofjustusingnpm<script>,weneedtousenpmrun<script>.Forexample,ifwewanttorunournewdevelopmentscriptwewillhavetousenpmrundevelopment.

npmhasscriptsthataretriggeredatdifferenttimes.Wecandefineapostinstallscriptthatrunsafterwerunnpminstall;wecanusethisifwewanttotriggerapackagemanagertoinstallthemodules(forexample,bower)

Youcanreadmoreaboutthescriptsobjecthere:https://docs.npmjs.com/misc/scripts.

Youneedtodefineapackageifyouareworkinginateamofdeveloperswheretheprojectistobeinstalledondifferentmachines.Ifyouareusingasourcecontroltoolsuchasgit,itisrecommendedthatyouaddthenode_modulesdirectoryintoyourignorefile,asshown:

[~/examples/example-3]$echo"node_modules">.gitignore

[~/examples/example-3]$cat.gitignore

node_modules

SummaryThatwasquick,wasn’tit?WehavecoveredthefundamentalsofNode.js,whichweneedtocontinueonourjourney.

WehavecoveredhoweasyitistoexposeandprotectpublicandprivatecodecomparedtoregularJavaScriptcodeinthebrowser,wheretheglobalscopecangetverypolluted.

Wealsoknowhowtoincludepackagesandcodefromexternalsourcesandhowtoensurethatthepackagesincludedareconsistent.

Asyoucanseethereisahugeecosystemofpackagesinoneofthemanypackagemanagers,suchasnpm,justwaitingforustouseandconsume.

Inthenextchapter,wewillfocusoncreatingasimpleservertoroute,authenticate,andconsumerequests.

Chapter2.SimpleHTTPNowthatwehaveunderstoodthebasics,wecanmoveontosomethingabitmoreuseful.Inthischapter,wewilllookatcreatinganHTTPserverandroutingrequests.WhileworkingwithNode.jsyouwillcomeacrossHTTPveryoften,asserversidescriptingisoneofthecommonusesofNode.js.

Node.jscomeswithabuiltinHTTPserver;allyouneedtodoisrequiretheincludedhttppackageandcreateaserver.Youcanreadmoreaboutthepackageathttps://nodejs.org/api/http.html.

varHttp=require('http');

varserver=Http.createServer();

ThiswillcreateyourveryownHTTPserverthatisreadytoroll.Inthisstate,though,itwon’tbelisteningforanyrequests.Wecanstartlisteningonanyportorsocketwewish,aslongasitisavailable,asshown:

varHttp=require('http');

varserver=Http.createServer();

server.listen(8080,function(){

console.log('Listeningonport8080');

});

Let’ssavetheprecedingcodetoserver.jsandrunit:

[~/examples/example-4]$nodeserver.js

Listeningonport8080

Bynavigatingtohttp://localhost:8080/onyourbrowseryouwillseethattherequesthasbeenacceptedbuttheserverisn’tresponding;thisisbecausewehaven’thandledtherequestsyet,wearejustlisteningforthem.

Whenwecreatetheserverwecanpassacallbackthatwillbecalledeachtimethereisarequest.Theparameterspassedwillbe:request,response.

functionrequestHandler(request,response){

}

varserver=Http.createServer(requestHandler);

Noweachtimewegetarequestwecandosomething:

varcount=0;

functionrequestHandler(request,response){

varmessage;

count+=1;

response.writeHead(201,{

'Content-Type':'text/plain'

});

message='Visitorcount:'+count;

console.log(message);

response.end(message);

}

Let’srunthescriptandrequestthepagefromthebrowser;youshouldseeVisitorcount:1returnedtothebrowser:

[~/examples/example-4]$nodeserver.js

Listeningonport8080

Visitorcount:1

Visitorcount:2

Somethingweirdhashappenedthough:anextrarequestgetsgenerated.Whoisvisitor2?

Thehttp.IncomingMessage(theparameterrequest)exposesafewpropertiesthatcanbeusedtofigurethisout.Thepropertywearemostinterestedinrightnowisurl.Weareexpectingjust/toberequested,solet’saddthistoourmessage:

message='Visitorcount:'+count+',path:'+request.url;

Nowyoucanrunthecodeandseewhat’sgoingon.Youwillnoticethat/favicon.icohasbeenrequestedaswell.IfyouarenotabletoseethisthenyoumustbewonderingwhatIhavebeengoingonaboutorifyourbrowserhasbeentohttp://localhost:8080recentlyandhasacachediconalready.Ifthisisthecase,thenyoucanrequesttheiconmanually,forexamplefromhttp://localhost:8080/favicon.ico:

[~/examples/example-4]$nodeserver.js

Listeningonport8080

Visitorcount:1,path:/

Visitorcount:2,path:/favicon.ico

Wecanalsoseethatifwerequestanyotherpagewewillgetthecorrectpath,asshown:

[~/examples/example-4]$nodeserver.js

Listeningonport8080

Visitorcount:1,path:/

Visitorcount:2,path:/favicon.ico

Visitorcount:3,path:/test

Visitorcount:4,path:/favicon.ico

Visitorcount:5,path:/foo

Visitorcount:6,path:/favicon.ico

Visitorcount:7,path:/bar

Visitorcount:8,path:/favicon.ico

Visitorcount:9,path:/foo/bar/baz/qux/norf

Visitorcount:10,path:/favicon.ico

Thisisn’tthedesiredoutcomethough,foreverythingbutafewrouteswewanttoreturn404:NotFound.

IntroducingroutingRoutingisessentialforalmostallNode.jsservers.First,wewillimplementourownsimpleversionandthenmoveontothemorecomplexrounting.

Wecanimplementourownsimplerouterusingaswitchstatement,suchas:

functionrequestHandler(request,response){

varmessage,

status=200;

count+=1;

switch(request.url){

case'/count':

message=count.toString();

break;

case'/hello':

message='World';

break;

default:

status=404;

message='NotFound';

break;

}

response.writeHead(201,{

'Content-Type':'text/plain'

});

console.log(request.url,status,message);

response.end(message);

}

Let’srunthefollowingexample:

[~/examples/example-4]$nodeserver.js

Listeningonport8080

/foo404NotFound

/bar404NotFound

/world404NotFound

/count2004

/hello200World

/count2006

Youcanseethecountincreasingwitheachrequest;however,itisn’treturnedeachtime.Ifwehaven’tdefinedacasespecificallyforthatroute,wereturn404:NotFound.

ForservicesthatimplementaRESTfulinterface,wewanttobeabletorouterequestsbasedontheHTTPmethodaswell.Therequestobjectexposesthisusingthemethodproperty.

Addingthistothelogwecanseethis:

console.log(request.method,request.url,status,message);

Runtheexampleandexecuteyourrequests,youcanuseaRESTclienttoinvokeaPOSTrequest:

[~/examples/example-4]$nodeserver.js

Listeningonport8080

GET/count2001

POST/count2002

PUT/count2003

DELETE/count2004

Wecanimplementaroutertoroutebasedonamethod,buttherearepackagesthatdothisforusalreadyoutthere.Fornowwewilluseasimplepackagecalledrouter:

[~/examples/example-5]$npminstallrouter

Now,wecandosomemorecomplexroutingofourrequests:

Let’screateasimpleRESTfulinterface.

First,weneedtocreatetheserver,asshown:

/*server.js*/

varHttp=require('http'),

Router=require('router'),

server,

router;

router=newRouter();

server=Http.createServer(function(request,response){

router(request,response,function(error){

if(!error){

response.writeHead(404);

}else{

//Handleerrors

console.log(error.message,error.stack);

response.writeHead(400);

}

response.end('\n');

});

});

server.listen(8080,function(){

console.log('Listeningonport8080');

});

Runningtheservershouldshowthattheserverislistening.

[~/examples/example-5]$nodeserver.js

Listeningonport8080

Wewanttodefineasimpleinterfacetoread,save,anddeletemessages.Wemightwanttoreadindividualmessagesaswellasalistofmessages;thisessentiallydefinesasetofRESTfulendpoints.

RESTstandsforRepresentationalStateTransfer;itisaverysimpleandcommonstyle

usedbymanyHTTPprogramminginterfaces.

Theendpointswewanttodefineare:

HTTPMethod Endpoint Usedto

POST /message Createmessage

GET /message/:id Readmessage

DELETE /message/:id Deletemessage

GET /message Readmultiplemessages

ForeachHTTPmethod,therouterhasamethodtouseformappingaroute.Thisinterfaceisintheformof:

router.<HTTPmethod>(<path>,[...<handler>])

Wecandefinemultiplehandlersforeachroute,butwewillcomebacktothatinamoment.

Wewillgothrougheachroute,createanimplementation,andappendthecodetotheendofserver.js.

Wewanttostoreourmessagessomewhere,andintherealworldwewillstoretheminadatabase;however,forsimplicitywewilluseanarraywithasimplecounter,asshown:

varcounter=0,

messages={};

Ourfirstroutewillbeusedtocreatemessages:

functioncreateMessage(request,response){

varid=counter+=1;

console.log('Createmessage',id);

response.writeHead(201,{

'Content-Type':'text/plain'

});

response.end('Message'+id);

}

router.post('/message',createMessage);

WecanensurethatthisrouteworksbyrunningtheserveranddoingaPOSTrequesttohttp://localhost:8000/message.

[~/examples/example-5]$nodeserver.js

Listeningonport8080

Createmessage1

Createmessage2

Createmessage3

Wecanalsoconfirmthatthecounterisincrementing,astheidincreaseseachtimewemakearequest.Wewilldothistokeepatrackofthecountofmessagesandtogiveauniqueidtoeachmessage.

Nowthatthisisworking,weneedtobeabletoreadthemessagetextandtodothisweneedtobeabletoreadtherequestbodythatwassentbytheclient.Thisiswheremultiplehandlerscomeintoplay.Wecouldtacklethisintwodifferentways,ifwewerereadingthebodyinonlyonerouteorifweweredoingsomeotheractionspecifictoaroute,forinstanceauthorization,wewilladdanadditionalhandlertotheroute,suchas:

router.post('/message',parseBody,createMessage)

Theotherwaywecoulddoitisbyaddingahandlerforallmethodsandroutes;thiswillbeexecutedfirstbeforetheroutehandlers,thesearecommonlyreferredtoasmiddleware.Youcanthinkofhandlersasbeingachainoffunctionswhereeachoneiscallingthenext,onceitisfinishedwithitstasks.Withthisinmindyoushouldnotethattheorderinwhichyouaddahandler,bothmiddlewareandroute,willdictatetheorderofoperations.Thismeansthat,ifweareregisteringahandlerthatisexecutedforallmethods,wemustdothisfirst.

Therouterexposesafunctiontoaddthefollowinghandlers:

router.use(function(request,response,next){

console.log('middlewareexecuted');

//Nullastherewerenoerrors

//Iftherewasanerrorthenwecouldcall`next(error);`

next(null);

});

YoucanaddthiscodejustaboveyourimplementationofcreateMessage:

Onceyouhavedonethat,runtheserverandmakethefollowingrequest:

[~/examples/example-5]$nodeserver.js

Listeningonport8080

middlewareexecuted

Createmessage1

Youcanseethatthemiddlewaregetsexecutedbeforetheroutehandler.

Nowthatweknowhowmiddlewareworks,wecanusethemasfollows:

[~/examples/example-5]$npminstallbody-parser

Replaceourcustommiddlewarewith:

varBodyParser=require('body-parser');

router.use(BodyParser.text());

Atthisstage,wejustwanttoreadallrequestsasplaintext.

NowwecanretrievethemessageincreateMessage:

functioncreateMessage(request,response){

varid=counter+=1,

message=request.body;

console.log('Createmessage',id,message);

messages[id]=message;

response.writeHead(201,{

'Content-Type':'text/plain',

'Location':'/message/'+id

});

response.end(message);

}

Runserver.jsandPOSTacoupleofmessagestohttp://localhost:8080/message;youwillseesomethingsimilartothesemessages:

[~/examples/example-5]$nodeserver.js

Listeningonport8080

Createmessage1Hellofoo

Createmessage2Hellobar

Ifyounotice,youwillseethataheaderreturnswithanewlocationofthemessageanditsid,Ifwerequesthttp://localhost:8080/message/1,thecontentfromthefirstmessageshouldbereturned.

However,thereissomethingdifferentwiththisroute;ithasakeythatisgeneratedeachtimeamessageiscreated.Wedon’twanttosetupanewrouteforeachnewmessageasitwillbehighlyinefficient.Instead,wecreatearoutethatmatchesapattern,suchas/message/:id.ThisisacommonwaytodefineadynamicrouteinNode.js.

Theidpartoftherouteiscalledaparameter.Wecandefineasmanyoftheseaswewantinourrouteandreferthemusingtherequest;forexamplewecanhavearoutesimilarto/user/:id/profile/:attribute.

WiththisinmindwecancreateourreadMessagehandler,asshown:

functionreadMessage(request,response){

varid=request.params.id,

message=messages[id];

console.log('Readmessage',id,message);

response.writeHead(200,{

'Content-Type':'text/plain'

});

response.end(message);

}

router.get('/message/:id',readMessage);

Nowlet’ssavetheprecedingcodeintheserver.jsfileandruntheserver:

[~/examples/example-5]$nodeserver.js

Listeningonport8080

Createmessage1Hellofoo

Readmessage1Hellofoo

Createmessage2Hellobar

Readmessage2Hellobar

Readmessage1Hellofoo

Wecanseeit’sworkingbysendingafewrequeststotheserver.

Deletingmessagesisalmostthesameasreadingthem;butwedon’treturnanythingandnullouttheoriginalmessagevalue:

functiondeleteMessage(request,response){

varid=request.params.id;

console.log('Deletemessage',id);

messages[id]=undefined;

response.writeHead(204,{});

response.end('');

}

router.delete('/message/:id',deleteMessage)

First,runtheserver,thencreate,read,anddeleteamessage,asshown:

[~/examples/example-5]$nodeserver.js

Listeningonport8080

Deletemessage1

Createmessage1Hello

Readmessage1Hello

Deletemessage1

Readmessage1undefined

Thatlooksgood;however,wehaverunintoaproblem.Weshouldn’tbeabletoreadamessageagainafterdeletingit;wewillreturn404inboththereadanddeletehandlersifwecan’tfindamessage.Wecandothisbyaddingthefollowingcodetoourreadanddeletehandlers:

varid=request.params.id,

message=messages[id];

if(typeofmessage!=='string'){

console.log('Messagenotfound',id);

response.writeHead(404);

response.end('\n');

return;

}

Nowlet’ssavetheprecedingcodeintheserver.jsfileandruntheserver:

[~/examples/example-5]$nodeserver.js

Listeningonport8080

Messagenotfound1

Createmessage1Hello

Readmessage1Hello

Lastly,wewanttobeabletoreadallmessagesandreturnalistofallmessagevalues:

functionreadMessages(request,response){

varid,

message,

messageList=[],

messageString;

for(idinmessages){

if(!messages.hasOwnProperty(id)){

continue;

}

message=messages[id];

//Handledeletedmessages

if(typeofmessage!=='string'){

continue;

}

messageList.push(message);

}

console.log('Readmessages',JSON.stringify(

messageList,

null,

''

));

messageString=messageList.join('\n');

response.writeHead(200,{

'Content-Type':'text/plain'

});

response.end(messageString);

}

router.get('/message',readMessages);

Nowlet’ssavetheprecedingcodeintheserver.jsfileandruntheserver:

[~/examples/example-5]$nodeserver.js

Listeningonport8080

Createmessage1Hello1

Createmessage2Hello2

Createmessage3Hello3

Createmessage4Hello4

Createmessage5Hello5

Readmessages[

"Hello1",

"Hello2",

"Hello3",

"Hello4",

"Hello5"

]

Awesome;nowwehaveafullRESTfulinterfacetoreadandwritemessages.But,wedon’twanteveryonetobeabletoreadourmessages;theyshouldbesecureandwealsowanttoknowwhoiscreatingthemessages,wewillcoverthisinthenextchapter.

SummaryNowwehaveeverythingweneedtomakesomeprettycoolservices.WecannowcreateanHTTPfromscratch,routeourrequests,andcreateaRESTfulinterface.

ThiswillhelpyouwiththecreationofcompleteNode.JSservices.Inthenextchapter,wewillcoverauthentication.

Chapter3.AuthenticationWecannowcreateRESTfulAPIs,butwedon’twanteveryonetoaccesseverythingweexpose.Wewanttheroutestobesecureandtobeabletotrackwhoisdoingwhat.

Passportisagreatmoduleandanothermiddlewarethathelpsusauthenticaterequests.

PassportexposesasimpleAPIforproviderstoexpandonandcreatestrategiestoauthenticateusers.Atthetimeofwriting,thereare307officiallysupportedstrategies;however,thereisnoreasonwhyyoucan’twriteyourownstrategyandpublishitforotherstouse.

BasicauthenticationThesimpleststrategyforpassportisthelocalstrategythatacceptsausernameandpassword.

Wewillintroducetheexpressframeworkfortheseexamplesand,nowthatyouknowthebasicsofhowitallworksunderneath,wecanputitalltogether.

Youcaninstallexpress,body-parser,passport,andpassport-local.Expressisabatteries-includedWebframeworkforNode.js,andincludesroutingandtheabilitytousemiddleware:

[~/examples/example-19]$npminstallexpressbody-parserpassportpassport-

local

Fornow,wecanstoreourusersinasimpleobjecttoreferencelater,asshown:

varusers={

foo:{

username:'foo',

password:'bar',

id:1

},

bar:{

username:'bar',

password:'foo',

id:2

}

}

Oncewehaveafewusers,weneedtosetuppassport.Whenwecreateaninstanceofthelocalstrategy,weneedtoprovideaverifycallbackwherewechecktheusernameandpassword,whilereturningauser:

varPassport=require('passport'),

LocalStrategy=require('passport-local').Strategy;

varlocalStrategy=newLocalStrategy({

usernameField:'username',

passwordField:'password'

},

function(username,password,done){

user=users[username];

if(user==null){

returndone(null,false,{message:'Invaliduser'});

}

if(user.password!==password){

returndone(null,false,{message:'Invalidpassword'});

}

done(null,user);

}

)

Theverifycallbackinthiscaseisexpectingdonetobecalledwithauser.Italsoallowsustoprovideinformationiftheuserwasinvalidorthepasswordwaswrong.

Now,thatwehaveastrategywecanpassthistopassport,whichallowsustoreferenceitlateranduseittoauthenticateourrequests,asfollows:

Passport.use('local',localStrategy);

Youcanusemultiplestrategiesperapplicationandreferenceeachonebythenameyoupassed,inthiscase'local'.

Now,let’screateourserver,asshownhere:

varExpress=require('express');

varapp=Express();

Wewillhavetousethebody-parsermiddleware.Thiswillensurethat,whenweposttoourloginroute,wecanreadourbody;wealsoneedtoinitializepassport:

varBodyParser=require('body-parser');

app.use(BodyParser.urlencoded({extended:false}));

app.use(BodyParser.json());

app.use(Passport.initialize());

Tologintoourapplication,weneedtocreateapostroutethatusesauthenticationasoneofthehandlers.Thecodeforthisisasfollows:

app.post(

'/login',

Passport.authenticate('local',{session:false}),

function(request,response){

}

);

Now,whenwesendaPOSTrequestto/logintheserverwillauthenticateourrequests.

Onceauthenticated,theuserpropertywillbepopulatedontherequestobject,asfollows:

app.post(

'/login',

Passport.authenticate('local',{session:false}),

function(request,response){

response.send('UserId'+request.user.id);

}

);

Lastly,weneedtolistenforrequests,aswithalltheotherservers:

app.listen(8080,function(){

console.log('Listeningonport8080');

});

Letsruntheexample:

[~/examples/example-19]$nodeserver.js

Listeningonport8080

Now,wecanauthenticateuserswhenwesendaPOSTrequestatourserver.Iftheuserhasn’tpassedboththeusernameandpasswordtheserverwillreturn400BadRequest.

TipIfyouaren’tfamiliarwithcurlyoucoulduseatool,suchasAdvancedRESTClient:

https://chromerestclient.appspot.com/

InthefollowingexamplesIwillbeusingthecommandlineinterfacecurl.

WecanexecutealoginrequestbyexecutingaPOSTto/logincommand:

[~]$curl-XPOSThttp://localhost:8080/login-v

<HTTP/1.1400BadRequest

Iftheuserprovidesthewrongdetailsthen401Unauthorizedwillbereturned:

[~]$curl-XPOSThttp://localhost:8080/login\

-H'Content-Type:application/json'\

-d'{"username":"foo","password":"foo"}'\

-v

<HTTP/1.1401Unauthorized

Ifweprovidethecorrectdetailsthenwecanseeourhandlerwascalledandthecorrectdatawasreturned:

[~]$curl-XPOSThttp://localhost:8080/login\

-H'Content-Type:application/json'\

-d'{"username":"foo","password":"bar"}'

UserId1

[~]$curl-XPOSThttp://localhost:8080/login\

-H'Content-Type:application/json'\

-d'{"username":"bar","password":"foo"}'

UserId2

BearertokensNowthatwehaveanauthenticateduser,wecangenerateatokenthatcanbeusedwiththerestofourrequestsratherthanpassingourusernameandpasswordeverywhere.ThisiscommonlyknownasaBearertokenand,conveniently,thereisapassportstrategyforthis.

Forourtokens,wewillusesomethingcalledaJSONWebToken(JWT).JWTallowsustoencodetokensfromJSONobjectsandthendecodethemandverifythem.Thedatastoredinthemisopenandsimpletoread,sopasswordsshouldn’tbestoredinthem;however,itmakesverifyingauserverysimple.Wecanalsoprovidethesetokenswithexpirydates,whichhelpslimittheseverityoftokensbeingexposed.

YoucanreadmoreaboutJWTathttp://jwt.io/.

WecaninstallJWTusingthefollowingcommand:

[~/examples/example-19]$npminstalljsonwebtoken

Onceauserisauthenticated,wecansafelyprovidethemwithatokentouseinfuturerequests:

varJSONWebToken=require('jsonwebtoken'),

Crypto=require('crypto');

vargenerateToken=function(request,response){

//Thepayloadjustcontainstheidoftheuser

//andtheirusername,wecanverifywhethertheclaim

//iscorrectusingJSONWebToken.verify

varpayload={

id:user.id,

username:user.username

};

//Generatearandomstring

//Usuallythiswouldbeanappwideconstant

//Butcanbedonebothways

varsecret=Crypto.randomBytes(128)

.toString('base64');

//Createthetokenwithapayloadandsecret

vartoken=JSONWebToken.sign(payload,secret);

//Theuserisstillreferencingthesameobject

//inusers,sononeedtosetitagain

//Ifwewereusingadatabase,wewouldsave

//ithere

request.user.secret=secret

returntoken;

}

vargenerateTokenHandler=function(request,response){

varuser=request.user;

//Generateourtoken

vartoken=generateToken(user);

//Returntheuseratokentouse

response.send(token);

};

app.post(

'/login',

Passport.authenticate('local',{session:false}),

generateTokenHandler

);

Now,whentheuserlogsintheywillbepresentedwithatokentousethatwecanverify.

LetsrunourNode.jsserver:

[~/examples/example-19]$nodeserver.js

Listeningonport8080

Whenweloginnowwereceiveatoken:

[~]$curl-XPOSThttp://localhost:8080/login\

-H'Content-Type:application/json'\

-d'{"username":"foo","password":"bar"}'

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZC

I6MSwidXNlcm5hbWUiOiJmb28iLCJpYXQiOjE0MzcyO

TQ3OTV9.iOZO7oCIceZl6YvZqVP9WZLRx-XVvJFMF1p

pPCEsGGs

Wecanenterthisintothedebuggerathttp://jwt.io/andseethecontents,asshown:

{

"id":1,

"username":"foo",

"iat":1437294795

}

Ifwehadthesecretwecouldverifythatthetokeniscorrect.Thesignaturechangeseverytimewerequestatoken:

[~]$curl-XPOSThttp://localhost:8080/login\

-H'Content-Type:application/json'\

-d'{"username":"foo","password":"bar"}'

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZC

I6MSwidXNlcm5hbWUiOiJmb28iLCJpYXQiOjE0MzcyO

TQ5OTl9.n1eRQVOM9qORTIMUpslH-ycTNEYdLDKa9lU

pmhf44s0

Wecanauthenticateauserusingpassport-bearer;itissetupverysimilartopassport-local.However,ratherthanacceptingausernameandpasswordfromthebody,weacceptabearertoken;thiscanbepassedusingthequerystring,body,ortheAuthorizationheader:

Firstwemustinstallpassport-http-bearer:

[~/examples/example-19]$npminstallpassport-http-bearer

Thelet’screateourverifier.Therearetwosteps:thefirstisensuringthedecodedinformationmatchesouruser,thiswillbewhereweusuallyretrieveouruser;then’once

wehaveauserandit’svalid,wecancheckwhetherthetokenisvalidbasedontheuser’ssecret:

varBearerStrategy=require('passport-http-bearer').Strategy;

varverifyToken=function(token,done){

varpayload=JSONWebToken.decode(token);

varuser=users[payload.username];

//Ifwecan'tfindauser,ortheinformation

//doesn'tmatchthenreturnfalse

if(user==null||

user.id!==payload.id||

user.username!==payload.username){

returndone(null,false);

}

//Ensurethetokenisvalidnowwehaveauser

JSONWebToken.verify(token,user.secret,function(error,decoded){

if(error||decoded==null){

returndone(error,false);

}

returndone(null,user);

});

}

varbearerStrategy=newBearerStrategy(

verifyToken

)

Wecanregisterthisstrategyasthebearersowecanuseitlater:

Passport.use('bearer',bearerStrategy);

Wecancreateasimpleroutewhereweretrieveuserdetailsforanauthenticateduser:

app.get(

'/userinfo',

Passport.authenticate('bearer',{session:false}),

function(request,response){

varuser=request.user;

response.send({

id:user.id,

username:user.username

});

}

);

Let’sruntheNode.jsserver:

[~/examples/example-19]$nodeserver.js

Listeningonport8080

Oncewereceiveatoken:

[~]$curl-XPOSThttp://localhost:8080/login\

-H'Content-Type:application/json'\

-d'{"username":"foo","password":"bar"}'

Wecanusetheresultinourrequests:

[~]$curl-XGEThttp://localhost:8080/userinfo\

-H'Authorization:Bearer<token>'

{"id":1,"username":"foo"}

OAuthOAuthprovidesmanyadvantages;forinstance,itdoesnotneedtodealwiththeactualidentificationofusers.Wecanletusersloginusingservicestheytrust,suchasGoogle,Facebook,orAuth0.

Forthefollowingexamples,IwillbeusingAuth0.Theyprovideafreeaccountforyoutogetup-and-running:https://auth0.com/.

Youwillneedtosignupandcreateanapi(chooseAngularJS+Node.js),thengotoSettingsandtakedownthedomain,clientid,andclientsecret.YouwillneedthesetosetupOAuth.

WecanauthenticateusingOAuthusingpassport-oauth2:

[~/examples/example-19]$npminstall--savepassport-oauth2

Aswithourbearertokens,wewanttovalidatewhattheserverreturns,whichwillbeauserobjectthathasanid.Wewillmatchthiswithauserthatisinourdataorcreateanewuser:

varvalidateOAuth=function(accessToken,refreshToken,profile,done){

varkeys=Object.keys(users),user=null;

for(variKey=0;iKey<keys.length;i+=1){

user=users[key];

if(user.thirdPartyId!==profile.user_id){continue;}

returndone(null,user);

}

users[profile.name]=user={

username:profile.name,

id:keys.length,

thirdPartyId:profile.user_id

}

done(null,user);

};

OncewehaveafunctiontovalidateouruserswecanputtogethertheoptionsforourOAuthstrategy:

varoAuthOptions={

authorizationURL:'https://<domain>.auth0.com/authorize',

tokenURL:'https://<domain>.auth0.com/oauth/token',

clientID:'<clientid>',

clientSecret:'<clientsecret>',

callbackURL:"http://localhost:8080/oauth/callback"

}

Thenwecreateourstrategy,asfollows:

varOAuth2Strategy=require('passport-oauth2').Strategy;

oAuthStrategy=newOAuth2Strategy(oAuthOptions,validateOAuth);

BeforeweuseourstrategyweneedtoducktypethestrategiesuserProfilemethodwithourown,thisissowecanrequesttheuserobjecttouseinvalidateOAuth:

varparseUserProfile=function(done,error,body){

if(error){

returndone(newError('Failedtofetchuserprofile'))

}

varjson;

try{

json=JSON.parse(body);

}catch(error){

returndone(error);

}

done(null,json);

}

vargetUserProfile=function(accessToken,done){

oAuthStrategy._oauth2.get(

"https://<domain>.auth0.com/userinfo",

accessToken,

parseUserProfile.bind(null,done)

)

}

oAuthStrategy.userProfile=getUserProfile

Wecanregisterthisstrategyasoauthsowecanuseitlater:

Passport.use('oauth',oAuthStrategy);

WeneedtocreatetworoutestohandleourOAuthauthentication:oneroutetostarttheflowandtheotherfortheidentificationservertoreturnto:

app.get('/oauth',Passport.authenticate('oauth',{session:false}));

WecanuseourgenerateTokenHandlerhere,asourrequestwillhaveauseronit.

app.get('/oauth/callback',

Passport.authenticate('oauth',{session:false}),

generateTokenHandler

);

Wecannowstartourserverandrequesthttp://localhost:8080/oauth;theserverwillredirectyoutoAuth0.Onceloggedin,youwillreceiveatokenthatyoucanusewith/userinfo.

Ifyouwereusingsessions,youcouldsavetheusertothesessionandredirectthembacktoyourfrontpage(orthedefaultpagesetforaloggedinuser).Forasingle-pageapp,whenusingsomethinglikeAngular,youmaywanttoredirecttheuserwithatokenintheURLfortheclientframeworktograbontoandsave.

SummaryWecannowauthenticateusers;thisisgreataswecannowfigureoutwhothepeopleareandthenlimittheuserstocertainresources.

Inthenextchapterwewillcoverdebugging,wemayneedtouseitifourusersaren’tbeingauthenticated.

Chapter4.DebuggingAtsomepointinyourjourneywithNode.js,itisinevitablethatyouwillhavetodebugsomenastybugs.So,let’sexpectthembeforehandandplanforthatday.

LoggingThereareafewmethodsthatwecanusetodebugoursoftware;thefirstonewearegoingtolookatislogging.Thesimplestwaytologamessageistouseconsole.InmostofthepreviousexamplesconsolehasbeenusedtoportraywhatisgoingonwithoutneedingtoseetheentireHTTPrequestandresponse,thusmakingthingsalotmorereadableandsimple.

Anexampleofthisis:

varHttp=require('http');

Http.createServer(function(request,response){

console.log(

'Receivedrequest',

request.method,

request.url

)

console.log('Returning200');

response.writeHead(200,{'Content-Type':'text/plain'});

response.end('HelloWorld\n');

}).listen(8000);

console.log('Serverrunningonport8000');

Runningthisexamplewilllogrequestsandresponsesontheconsole:

[~/examples/example-6]$nodeserver.js

Serverrunningonport8000

ReceivedrequestGET/

Returning200

ReceivedrequestGET/favicon.ico

Returning200

ReceivedrequestGET/test

Returning200

ReceivedrequestGET/favicon.ico

Returning200

Ifweareusingaframeworkthatacceptsmiddleware,suchasexpress,wecoulduseasimplenpmpackagecalledmorgan;youcanfindthepackageathttps://www.npmjs.com/package/morgan:

[~/examples/example-7]$npminstallmorgan

[~/examples/example-7]$npminstallrouter

Wecanuseitbyusingrequiretobringitintoourcodeandaddingitasmiddleware:

varMorgan=require('morgan'),

Router=require('router'),

Http=require('http');

router=newRouter();

router.use(Morgan('tiny'));

/*Simpleserver*/

Http.createServer(function(request,response){

router(request,response,function(error){

if(!error){

response.writeHead(404);

}else{

//Handleerrors

console.log(error.message,error.stack);

response.writeHead(400);

}

response.end('\n');

});

}).listen(8000);

console.log('Serverrunningonport8000');

functiongetInfo(request,response){

varinfo=process.versions;

info=JSON.stringify(info);

response.writeHead(200,{'Content-Type':'application/json'});

response.end(info);

}

router.get('/info',getInfo);

Whentheserverisrunning,wecanseeeachrequestandresponsewithouthavingtoaddloggingtoeachhandler:

[~/examples/example-7]$nodeserver.js

Serverrunningonport8000

GET/test404--4.492ms

GET/favicon.ico404--2.281ms

GET/info200--1.120ms

GET/info200--1.120ms

GET/test404--0.199ms

GET/info200--0.494ms

GET/test404--0.162ms

Thiskindofloggingisasimplewaytoseewhatisbeingusedontheserverandhowlongeachrequestistaking.Here,youcanseethatthefirstrequeststookthelongestandthentheygotalotfaster.Thedifferenceisonlyof3ms;ifthetimewaslarger,itcouldhavebeenabigproblem.

Wecanincreasetheinformationthat’sloggedbychangingtheformatwepasstomorgan,asshown:

router.use(Morgan('combined'));

Byrunningtheserveryouwillseemoreinformation,suchastheremoteuser,dateandtimeoftherequest,amountofcontentthatwasreturned,andtheclienttheyareusing.

[~/examples/example-7]$nodeserver.js

Serverrunningonport8000

::1--[07/Jun/2015:11:09:03+0000]"GET/infoHTTP/1.1"200-"-""--

REMOVED---"

Timingisdefinitelyanimportantfactorasitcanbehelpfulwhensiftingthroughthemountainsoflogsthatyouwillobtain.Somebugscanbelikeatickingtime-bombwaitingtoexplodeat3AMonaSaturdaynight.Alltheselogsmeannothingtousiftheprocesshasdiedandthelogshavedisappeared.Thereisanotherpopularandusefulpackagecalledbunyan,whichwrapsmanyloggingmethodsintoone.

Bunyanbringstothetabletheadvantageofwriteablestreamstowritelogs,whetheritisafileondiskorstdout.Thisallowsustopersistourlogsforpostmortemdebugging.Youcanfindmoredetailsaboutbunyanathttps://www.npmjs.com/package/bunyan.

Now,let’sinstallthepackage.Wewantitinstalledbothlocallyandgloballysothatwecanalsouseitasacommandlinetool:

[~/examples/example-8]$npminstall–gbunyan

[~/examples/example-8]$npminstallbunyan

Now,letsdosomelogging:

varBunyan=require('bunyan'),

logger;

logger=Bunyan.createLogger({

name:'example-8'

});

logger.info('Hellologging');

Runningourexample:

[~/examples/example-8]$nodeindex.js

{"name":"example-

8","hostname":"macbook.local","pid":2483,"level":30,"msg":"Hello

logging","time":"2015-06-07T11:35:13.973Z","v":0}

Thisdoesn’tlookverypretty,doesit?BunyanusesasimplestructuredJSONstringtosavemessages;thismakesiteasytoparse,extend,andread.BunyancomeswithaCLIutilitytomakeeverythingniceandpretty.

Ifweruntheexamplewiththeutility,thenwewillseethattheoutputisnicelyformatted:

[~/examples/example-8]$nodeindex.js|bunyan

[2015-06-07T11:38:59.698Z]INFO:example-8/2494onmacbook.local:Hello

logging

Ifweaddafewmorelevels,youwillseeonyourconsolethateachiscoloreddifferentlytohelpusidentifythem:

varBunyan=require('bunyan'),

logger;

logger=Bunyan.createLogger({

name:'example-8'

});

logger.trace('Trace');

logger.debug('Debug');

logger.info('Info');

logger.warn('Warn');

logger.error('Error');

logger.fatal('Fatal');

logger.fatal('Wegotafatal,letsexit');

process.exit(1);

Let’sruntheexample:

[~/examples/example-8]$nodeindex.js|bunyan

[2015-06-07T11:39:55.801Z]INFO:example-8/2512onmacbook.local:Info

[2015-06-07T11:39:55.811Z]WARN:example-8/2512onmacbook.local:Warn

[2015-06-07T11:39:55.814Z]ERROR:example-8/2512onmacbook.local:Error

[2015-06-07T11:39:55.814Z]FATAL:example-8/2512onmacbook.local:Fatal

[2015-06-07T11:39:55.814Z]FATAL:example-8/2512onmacbook.local:Wegota

fatal,letsexit

Ifyounotice,traceanddebugweren’toutputtedontheconsole.Thisisbecausetheyareusedtofollowtheflowoftheprogramratherthanthekeyinformationandareusuallyverynoisy.

Wecanchangetheleveloflogswewanttoseebypassingthisasanoptionwhenwecreatethelogger:

logger=Bunyan.createLogger({

name:'example-8',

level:Bunyan.TRACE

});

Now,whenweruntheexample:

[~/examples/example-8]$nodeindex.js|bunyan

[2015-06-07T11:55:40.175Z]TRACE:example-8/2621onmacbook.local:Trace

[2015-06-07T11:55:40.177Z]DEBUG:example-8/2621onmacbook.local:Debug

[2015-06-07T11:55:40.178Z]INFO:example-8/2621onmacbook.local:Info

[2015-06-07T11:55:40.178Z]WARN:example-8/2621onmacbook.local:Warn

[2015-06-07T11:55:40.178Z]ERROR:example-8/2621onmacbook.local:Error

[2015-06-07T11:55:40.178Z]FATAL:example-8/2621onmacbook.local:Fatal

[2015-06-07T11:55:40.178Z]FATAL:example-8/2621onmacbook.local:Wegota

fatal,letsexit

Weusuallydon’twanttoseelogsthatarelowerthantheinfolevel,asanyinformationthatisusefulforpost-mortemdebuggingshouldhavebeenloggedusingtheinfoorhigher.

Bunyan’sapiisgoodforthefunctionofloggingerrorsandobjects.ItsavesthecorrectstructuresinitsJSONoutput,whichisreadyfordisplay:

try{

ref.go();

}catch(error){

logger.error(error);

}

Let’sruntheexample:

[~/examples/example-9]$nodeindex.js|bunyan

[2015-06-07T12:00:38.700Z]ERROR:example-9/2635onmacbook.local:refis

notdefined

ReferenceError:refisnotdefined

atObject.<anonymous>(~/examples/example-8/index.js:9:2)

atModule._compile(module.js:460:26)

atObject.Module._extensions..js(module.js:478:10)

atModule.load(module.js:355:32)

atFunction.Module._load(module.js:310:12)

atFunction.Module.runMain(module.js:501:10)

atstartup(node.js:129:16)

atnode.js:814:3

Ifwelookattheexampleandpretty-printit,wewillseethattheysaveitasanerror:

[~/examples/example-9]$npminstall-gprettyjson

[~/examples/example-9]$nodeindex.js|prettyjson

name:example-9

hostname:macbook.local

pid:2650

level:50

err:

message:refisnotdefined

name:ReferenceError

stack:

"""

ReferenceError:refisnotdefined

atObject.<anonymous>(~/examples/example-8/index.js:9:2)

atModule._compile(module.js:460:26)

atObject.Module._extensions..js(module.js:478:10)

atModule.load(module.js:355:32)

atFunction.Module._load(module.js:310:12)

atFunction.Module.runMain(module.js:501:10)

atstartup(node.js:129:16)

atnode.js:814:3

"""

msg:refisnotdefined

time:2015-06-07T12:02:33.875Z

v:0

Thisisusefulbecause,ifyoujustloganerror,youwilleithergetanemptyobjectifyouusedJSON.stringifyorjustthemessageifyouusedtoString:

try{

ref.go();

}catch(error){

console.log(JSON.stringify(error));

console.log(error);

console.log({

message:error.message

name:error.name

stack:error.stack

});

}

Let’sruntheexample:

[~/examples/example-10]$nodeindex.js

{}

[ReferenceError:refisnotdefined]

{message:'refisnotdefined',

name:'ReferenceError',

stack:'--REMOVED--'}

Itistolotsimplerandcleanertouselogger.error(error)thanlogger.error({message:error.message/*,...*/});.

Asmentionedearlier,bunyanusestheconceptofstreams,whichmeansthatwecanwritetoafile,stdout,oranyotherservicewewishtoextendto.

Towritetoafile,allweneedtodoisaddittotheoptionspassedtobunyanatsetup:

varBunyan=require('bunyan'),

logger;

logger=Bunyan.createLogger({

name:'example-11',

streams:[

{

level:Bunyan.INFO,

path:'./log.log'

}

]

});

logger.info(process.versions);

logger.info('Applicationstarted');

Byrunningtheexample,youwon’tseeanylogsbeingoutputtedtotheconsolebuttheywillbewrittentofileinstead:

[~/examples/example-11]$nodeindex.js

Ifyoulistwhat’sinthedirectoryyouwillseeanewfilehasbeencreated:

[~/examples/example-11]$ls

index.jslog.lognode_modules

Ifyoureadwhat’sinthefileyouwillseethatthelogshavealreadybeenwritten:

[~/examples/example-11]$catlog.log

{"name":"example-

11","hostname":"macbook.local","pid":3614,"level":30,"http_parser":"2.3","n

ode":"0.12.2","v8":"3.28.73","uv":"1.4.2-

node1","zlib":"1.2.8","modules":"14","openssl":"1.0.1m","msg":"","time":"20

15-06-07T12:29:46.606Z","v":0}

{"name":"example-

11","hostname":"macbook.local","pid":3614,"level":30,"msg":"Application

started","time":"2015-06-07T12:29:46.608Z","v":0}

Wecanrunthisthroughbunyaninordertoprintitoutnicely:

[~/examples/example-11]$catlog.log|bunyan

[~/examples/example-11]$catlog.log|bunyan

[2015-06-07T12:29:46.606Z]INFO:example-11/3614onmacbook.local:

(http_parser=2.3,node=0.12.2,v8=3.28.73,uv=1.4.2-node1,zlib=1.2.8,

modules=14,openssl=1.0.1m)

[2015-06-07T12:29:46.608Z]INFO:example-11/3614onmacbook.local:

Applicationstarted

Nowthatwecanlogtoafile,wealsowanttobeabletoseethemessagesastheyaredisplayed.Ifwewerejustloggingtoafile,wecoulduse:

[~/examples/example-11]$tail-flog.log|bunyan

Thiswilllogtostdoutasthefileitisbeingwrittento;alternativelywecouldjustaddanotherstreamtobunyan:

logger=Bunyan.createLogger({

name:'example-11',

streams:[

{

level:Bunyan.INFO,

path:'./log.log'

},

{

level:Bunyan.INFO,

stream:process.stdout

}

]

});

Runningtheexamplewilldisplaythelogstotheconsole:

[~/examples/example-11]$nodeindex.js|bunyan

[2015-06-07T12:37:19.857Z]INFO:example-11/3695onmacbook.local:

(http_parser=2.3,node=0.12.2,v8=3.28.73,uv=1.4.2-node1,zlib=1.2.8,

modules=14,openssl=1.0.1m)[2015-06-07T12:37:19.860Z]INFO:example-

11/3695onmacbook.local:Applicationstarted

Wecanalsoseethelogshavebeenappendedtothefile:

[~/examples/example-11]$catlog.log|bunyan

[2015-06-07T12:29:46.606Z]INFO:example-11/3614onmacbook.local:

(http_parser=2.3,node=0.12.2,v8=3.28.73,uv=1.4.2-node1,zlib=1.2.8,

modules=14,openssl=1.0.1m)

[2015-06-07T12:29:46.608Z]INFO:example-11/3614onmacbook.local:

Applicationstarted

[2015-06-07T12:37:19.857Z]INFO:example-11/3695onmacbook.local:

(http_parser=2.3,node=0.12.2,v8=3.28.73,uv=1.4.2-node1,zlib=1.2.8,

modules=14,openssl=1.0.1m)

[2015-06-07T12:37:19.860Z]INFO:example-11/3695onmacbook.local:

Applicationstarted

Great,nowwehavetheloggingdown,whatshallwedowithit?

Well,ithelpstoknowwhereourerrorsareoccurringanditstartstogetreallymessywhenyouhavelotsofanonymousfunctionsaroundtheplace.IfyounoticedintheexamplesthatcoveranHTTPserver,themajorityofthefunctionswerenamed.Thisisveryhelpfulintrackingdownerrorswhencallbacksareinvolved.

Let’slookatthisexample:

try{

a=function(callback){

returnfunction(){

callback();

};

};

b=function(callback){

returnfunction(){

callback();

}

};

c=function(callback){

returnfunction(){

thrownewError("I'mjustmessingwithyou");

};

};

a(b(c()))();

}catch(error){

logger.error(error);

}

Itmightlookabitmessyandthat’sbecauseitis.Let’srunthefollowingexample:

[~/examples/example-12]$nodeindex.js|bunyan

[2015-06-07T12:51:11.665Z]ERROR:example-12/4158onmacbook.local:I'm

justmessingwithyou

Error:I'mjustmessingwithyou

at/Users/fabian/examples/example-12/index.js:19:10

at/Users/fabian/examples/example-12/index.js:14:4

at/Users/fabian/examples/example-12/index.js:9:4

atObject.<anonymous>(/Users/fabian/examples/example-

12/index.js:22:16)

atModule._compile(module.js:460:26)

atObject.Module._extensions..js(module.js:478:10)

atModule.load(module.js:355:32)

atFunction.Module._load(module.js:310:12)

atFunction.Module.runMain(module.js:501:10)

atstartup(node.js:129:16)

Youcanseethattherearenofunctionnamesinourcodeandalsothereisnonaminginthestacktraceunlikethefirstfewfunctions.InNode.js,thenamingoffunctionswillcomefromeitherthevariablenameortheactualfunctionname.Forexample,ifyouuseCls.prototype.functhenthenamewillbeCls.funcbutifyouusethefunctionfuncthenthenamewillbefunc.

Youcanseethatthereisaslightbenefitherebutthisbecomesveryusefulonceyoustartusingpatternsinvolvingasynccallbacks:

[~/examples/example-13]$npminstallq

Let’sthrowanerrorinacallback:

varQ=require('q');

Q()

.then(function(){

//Promisedreturnedfromanotherfunction

returnQ()

.then(function(){

thrownewError('Helloerrors');

});

})

.fail(function(error){

logger.error(error);

});

Runningourexamplegivesus:

[~/examples/example-13]$nodeindex.js|bunyan

[2015-06-07T13:03:57.047Z]ERROR:example-13/4598onmacbook.local:Hello

errors

Error:Helloerrors

at/Users/fabian/examples/example-13/index.js:12:9

at_fulfilled(/Users/fabian/examples/example-

13/node_modules/q/q.js:834:54)

Thisiswhereitstartstogetdifficulttoread;assigningsimplenamestoourfunctionscanhelpusfindwheretheerroriscomingfrom:

returnQ()

.then(functionresultFromOtherFunction(){

thrownewError('Helloerrors');

});

Runningtheexample:

[~/examples/example-13]$nodeindex.js|bunyan

[2015-06-07T13:04:45.598Z]ERROR:example-13/4614onmacbook.local:Hello

errors

Error:Helloerrors

atresultFromOtherFunction(/Users/fabian/examples/example-

13/index.js:12:9)

at_fulfilled(/Users/fabian/examples/example-

13/node_modules/q/q.js:834:54)

ErrorhandlingAnotheraspectofdebuggingishandlingandexpectingerrorsbeforehand.Therearethreewaysinwhichwecanhandleourerrors:

asimpletry/catchcatchingthemattheprocesslevelcatchingerrorsonthedomainlevel

Atry/catchfunctionwillbesufficientifweexpectanerrortooccurandwewillbeabletocontinuewithoutknowingtheresultofwhateverwasbeingexecuted,orwecouldhandleandreturntheerror,asshown:

functionparseJSONAndUse(input){

varjson=null;

try{

json=JSON.parse(input);

}catch(error){

returnQ.reject(newError("Couldn'tparseJSON"));

}

returnQ(use(json));

}

Anothersimplewaytocatcherrorsistoaddanerrorhandlertoyourprocess;anyerrorsthatarecaughtatthislevelareusuallyfatalandshouldbetreatedassuch.Anexitoftheprocessshouldfollowandyoushouldbeusingapackage,suchasforeverorpm2:

process.on('uncaughtException',functionerrorProcessHandler(error){

logger.fatal(error);

logger.fatal('Fatalerrorencountered,exitingnow');

process.exit(1);

});

Youshouldalwaysexittheprocessfollowinganuncaughterror.Thefactthatitisuncaughtmeansthatyourapplicationisinanunknownstatewhereanythingcanhappen.Forexample,therecouldhavebeenanerrorinyourHTTProuterandnomorerequestscanberoutedtothecorrecthandlers.Youcanreadmoreaboutthisathttps://nodejs.org/api/process.html#process_event_uncaughtexception.

Abetterwaytohandleerrorsonagloballevelisusingdomain.Withdomainsyoucanalmostsandboxagroupofasynchronouscodetogether.

Let’sthinkinthecontextofarequesttoourserver.Wemakearequest,readfromadatabase,makecallstoexternalservices,writebacktoadatabase,dosomelogging,dosomebusinesslogic,andweexpectperfectdatacomingfromexternalsourcesallaroundthecode.However,intherealworlditisn’talwayssoandwecan’thandleeveryerrorthatcouldpossiblyoccur;moreover,wedon’twanttotakedownourentireserverjustbecauseofoneerrorforaveryspecificrequest.That’swhereweneeddomains.

Let’slookatthefollowingexample:

varDomain=require('domain'),

domain;

domain=Domain.create();

domain.on('error',function(error){

console.log('Domainerror',error.message);

});

domain.run(function(){

//Runcodeinsidedomain

console.log(process.domain===domain);

thrownewError('Errorhappened');

});

Let’srunthecode:

[~/examples/example-14]$nodeindex.js

true

DomainerrorErrorhappened

Thereisaproblemwiththiscode;however,aswearerunningthissynchronouslywearestillputtingtheprocessintoabrokenstate.Thisisbecausetheerrorbubbleduptothenodeitselfandthenwaspassedtotheactivedomain.

Whenwearecreatingthedomaininanasynchronouscallback,wecanbesurethattheprocesscancontinue.Wecanmimicthisbyusingprocess.nextTick:

process.nextTick(function(){

domain.run(function(){

thrownewError('Errorhappened');

});

console.log("Iwon'texecute");

});

process.nextTick(function(){

console.log('Nexttickhappend!');

});

console.log('Ihappenedbeforeeverythingelse');

Runningtheexampleshoulddisplaythecorrectlogs:

[~/examples/example-15]$nodeindex.js

Ihappenedbeforeeverythingelse

DomainerrorErrorhappened

Nexttickhappend!

SummaryInthischapterwehavecoveredafewpost-mortemdebuggingmethodstohelpusuncoverbugsincludinglogging,namingpractices,andsufficienterrorhandling.

Inthenextchapter,wewillcoverconfigurationofourapplications.

Chapter5.ConfigurationAsourapplicationsgetlargerandlarger,westarttolosesightofwhatisconfiguredtodowhat;wemayalsogetintoasituationwherewehavecoderunningin12differentplaces,eachneedingabitofcodethathastobechangedtodosomethingelse,forexampleconnectingtoadifferentdatabase.Then,foreachofthose12environments,wehavethreeversions:production,staging,anddevelopment.Allofasudden,itgetsverycomplicated.Thisiswhyweneedtobeabletoconfigureourcodefromahigher-levelsothatwedon’tbreakanythingintheprocess.

JSONfilesThereareafewwaysinwhichwecanconfigureourapplication.ThefirstwaythatwewilllookatisasimpleJSONfile.

Ifwelookattheextensionsrequiresupportsbydefault,wecanseethatwecanimportJSONrightintoourcode,asshown:

[~/examples/example-16]$node

>require.extensions

{'.js':[Function],

'.json':[Function],

'.node':[Function:dlopen]}

Let’screateasimpleserverwithaconfigurationfileratherthanahardcodedfile:

First,wehavetocreatetheconfigurationfile:

{

"host":"localhost",

"port":8000

}

Withthis,wecannowcreateourserver:

varConfig=require('./config.json'),

Http=require('http');

Http.createServer(function(request,response){

}).listen(Config.port,Config.host,function(){

console.log('Listeningonport',Config.port,'andhost',Config.host);

});

Now,wecanjustchangetheconfigfileinsteadofchangingthecodetochangetheportonwhichourserverisrunning.

Butourconfigfileisabittoogeneric;wehavenoideaastowhatisahostoraportandwhattheyarerelatedto.

Whileconfiguring,thekeysneedtobelessgenericsothatweknowwhattheyarebeingusedfor,unlessthecontextisgivendirectlybytheapplication.Forexample,iftheapplicationwastoservepurelystaticcontentthenitmaybeacceptabletohavemoregenerickeys.

Tomaketheseconfigurationkeyslessgeneric,wecanwrapthemallinaserverobject:

{

"server":{

"host":"localhost",

"port":8000

}

}

Sonow,inordertoknowabouttheportoftheserverweneedtousethefollowingcode:

Config.server.port

Anexamplewherethiswillbeusefulcouldbeforaserverthatconnectstoadatabase,astheycanacceptboththeportandhostastheparameters:

{

"server":{

"host":"localhost",

"port":8000

},

"database":{

"host":"db1.example.com",

"port":27017

}

}

EnvironmentalvariablesAnotherwayinwhichwecanconfigureourapplicationsisthroughtheuseofenvironmentalvariables.

Thesecanbedefinedbytheenvironmentyouarerunningyourapplicationinorinthecommandthatyouareusingtostartyourprocesswith.

InNode.js,youcanaccesstheenvironmentalvariablesusingprocess.env.Whenusingenv,youdon’twanttobepollutingthisspacetoomuchandsoitisagoodideatoprefixthekeywithsomethingrelatedtoyourself—yourprogramorcompany.Forexample,Config.server.hostbecomesprocess.env.NAME_SERVER_HOST;thereasonforthisisthatwecanclearlyseewhatisrelatedtoyourprogramandwhatisn’t.

Usingenvironmentalvariablestoconfigureourserver,ourcodewilllookasfollows:

varHttp=require('http'),

server_port,

server_host;

server_port=parseInt(process.env.FOO_SERVER_PORT,10);

server_host=process.env.FOO_SERVER_HOST;

Http.createServer(function(request,response){

}).listen(server_port,server_host,function(){

console.log('Listeningonport',server_port,'andhost',server_host);

});

Torunthiscodewithourvariables,wewilluse:

[~/examples/example-17]$FOO_SERVER_PORT=8001\

FOO_SERVER_HOST=localhostnodeserver.js

Listeningonport8001andhostlocalhost

YouprobablynoticedthatIhadtouseparseIntforFOO_SERVER_PORT;thisisbecauseallvariablespassedinthismannerareessentiallystrings.Wecanseethisbyexecutingtypeofprocess.env.FOO_ENV:

[~/examples/example-17]$FOO_ENV=1234node

>typeofprocess.env.FOO_ENV

'string'

>typeofparseInt(process.env.FOO_ENV,10)

'number'

Althoughthiskindofconfigurationisverysimpletocreateandconsume,itmaynotbethebestmethod,asthevariablesarehardtokeeptrackofiftherearealotofthemandtheycanbedroppedveryeasily.

ArgumentsAnotherwayinwhichtheconfigurationcanbedoneisthroughtheuseofargumentsthatarepassedtoNode.jsastheprocessstarts,youcanaccesstheseusingprocess.argv,withargvstandingforargumentvector.

Thearraythatprocess.argvreturnswillalwayshaveanodeatindex0.Forexample,ifyourunnodeserver.jsthenprocess.argvwillhavethevalueof['node','/example/server.js'].

IfyoupassanargumenttoNode.jsthenitwillbeaddedtotheendofprocess.argv.

Ifyourunnodeserver.js--port=8001,theprocess.argvwillcontain['node','/example/server.js','--port=8001'],prettysimple,right?

Eventhoughwecanhaveallthisconfiguration,weshouldalwaysrememberthatconfigurationcanbesimplyexcludedandwewillstillwantourapplicationtorunwhenthishappens.Usually,youshouldprovidedefaulthardcodedvaluesasabackupwhenyouhaveconfigurationoptions.

Parameterssuchaspasswordsandprivatekeysshouldneverhaveadefaultvaluebutlinksandoptionsthatareusuallystandardshouldbegivendefaults.ItisprettyeasytogiveadefaultvalueinNode.js,allyouneedtodoisusetheORoperator.

value=value||'default';

Essentially,whatthisdoesischeckifthevalueisfalsy;ifitis,thenusethedefaultvalue.Youneedtowatchoutforvaluesthatyouknowcouldbefalsy,booleansandnumbersdefinitelyfallintothiscategory.

Inthesecasesyoucanuseanifstatementcheckingforanullvalue,asshown:

if(value==null)value=1

SummaryThat’sallforconfiguration.Inthischapteryoulearnedaboutthethreemethodsthatyoucanusetocreateadynamicapplication.Welearnedthatweshouldnameourconfigurationkeysinawaythatwecanidentifywhatthevaluesarechangingtoandhowtheywillaffectourapplication.Wealsolearnedabouthowwecanpasssimpleargumentstoourapplicationusingenvironmentalvariablesandargv.

Withthisinformation,wecanmoveforwardtoconnectingandutilizingdatabasesinthenextchapter.

Chapter6.LevelDBandNoSQLInthischapter,wewillcovertwovariationsofdatabasesthatcanbeusedwithNode.js;oneprovidesaverylightweightandsimplesetoffeatures,whiletheothergivesusmoreflexibilityandageneral-purposesetoffeatures.Inthischapter,wearegoingtocoverLevelDBandMongoDB

LevelDBOneofthegreatthingswithNode.jsisthatweusethesamelanguageforboththefrontandbackendandthesamegoesforNoSQLdatabases.ThemajorityofthemsupportJSONrightoffthemark;thisisgreatforanyoneusingNode.jsasthereisnotimespentinmakingarelationalmodel,turningitintoaJSON-likestructure,passingittothebrowser,doingsomethingwithit,andreversingtheprocess.

WithadatabasethatsupportsJSONnatively,youcangetrightdowntobusinessandplayball.

GooglehasprovideduswithasimplehookintoaNoSQLdatabasethatcanbeinstalledandcanbemadereadytousewithjustonecommand:

[~/examples/example-18]$npminstalllevel

YouwillseethatthiswillinstallbothLevelDOWNandLevelUP.

LevelDOWNisthelow-levelbindingtoLevelDBandLevelUPisthesimplewrapperaroundthis.

LevelDBisverysimpleintermsofsetup.Onceitisinstalled,wejustcreateaninstanceofLevelUPandpassitwherewewantourdatabasetobestored:

varLevelUP=require('level'),

db=newLevelUP('./example-db');

Nowwehaveafastandsimplewaytostoredata.

AsLevelDBisjustasimplekey/valuestore,itdefaultstostringkeysandstringvalues.Thisisusefulifthat’salltheinformationyouwishtostore.Youcanalsouseitasasimplecachestore.IthasaverysimpleAPI,atthisstageweareonlygoingtofocusonfourmethods:put,get,del,andcreateReadStream;it’sprettyobviouswhatmostofthemdo:

Method Usedfor Arguments

put insertingpairs key,value,callback(error)

get fetchingpairs key,callback(error,value)

del deletingpairs key,callback(error)

createReadStream fetchingmanypairs

Toinsertdataoncewehavecreatedourdatabase,allweneedtodois:

db.put('key','value',function(error){

if(error)returnconsole.log('Error!',error)

db.get('key',function(error,value){

if(error)returnconsole.log('Error!',error)

console.log("key=",value)

});

});

Ifwerunthecode,wewillseethatweinsertedandretrievedourvalue:

[~/examples/example-18]$nodeindex.js

key=value

Thisisn’toursimpleJSONstructure;however,it’sjustastring.TogetourstoretosaveJSON,allweneedtodoistopassthevalueencodingasanoptiontothedatabase,asshown:

varLevelUP=require('level'),

db=newLevelUP('./example-db',{

valueEncoding:'json'

});

NowwecanstoreJSONdata:

db.put('jsonKey',{inner:'value'},function(error){

if(error)returnconsole.log('Error!',error)

db.get('jsonKey',function(error,value){

if(error)returnconsole.log('Error!',error)

console.log("jsonKey=",value)

});

});

However,astringcanbestoredasJSONandwecanstillpassstringsasavalueandalsoretrieveitassuch.

Runningthisexamplewillshowthefollowing:

[~/examples/example-18]$nodeindex.js

key=value

jsonKey={inner:'value'}

Now,wehavethesimplemethodsdownandwecannowmoveontocreateReadStream.

ThisfunctionreturnsanobjectthatcanbecomparedtoNode.jsbuiltinReadableStream.Foreachkey/valuepairinourdatabase,itwillemitadataevent;italsoemitsotherevents,suchaserrorandend.Iferrordoesn’thaveaneventlistener,thenitwillpropagate,therebykillingyourentireprocess(ordomain),asshown:

db.put('key1',{inner:'value'},function(error){

if(error)returnconsole.log('Error!',error)

varstream=db.createReadStream();

stream

.on('data',function(pair){

console.log(pair.key,"=",pair.value);

})

.on('error',function(error){

console.log(error);

})

.on('end',function(){

console.log('end');

});

});

Runningthisexample:

[~/examples/example-20]$nodeindex.js

key1={inner:'value'}

end

Ifweputmoredatainthedatabasewewillhavemultipledataeventsemitted:

[~/examples/example-20]$nodeindex.js

key1={inner:'value'}

key2={inner:'value'}

end

MongoDBAsyoucansee,databaseswithNode.jscanbeverysimple.IfwewantsomethingabitmorecompletewecanuseanotherNoSQLdatabasecalledMongoDB–anotherverypopulardocument-baseddatabase.

Forthissetofexamples,youcaneitheruseahosteddatabaseusingaprovidersuchasMongoLab(theyprovideafreetierfordevelopment)oryoucansetupadatabaselocallyfollowingtheinstructionsathttp://docs.mongodb.org/manual/installation.

Wecancontinueonceyouhaveadatabasetoconnectto.

MongoDBhasseveralmodulesthatcanbeusedwithNode.js,themostpopularoneisMongoose;however,wewillbeusingthecoreMongoDBmodule:

[~/examples/example-21]$npminstallmongodb

Touseourdatabase,wefirstneedtoconnecttoit.Weneedtoprovidetheclientwithaconnectionstring,agenericURIwiththeprotocolofmongodb.

Ifyouhavealocalmongodatabaserunningwithnocredentialsyouwilluse:

mongodb://localhost:27017/database

Thedefaultportis27017,soyoudon’tneedtospecifythat;however,itisincludedforcompleteness.

IfyouareusingMongoLab,theywillprovideyouwithaconnectionstring;itshouldbeintheformatof:

mongodb://<dbuser>:<dbpassword>@<ds>.mongolab.com:<port>/<db>

Connectingtoourdatabaseisactuallyprettysimple.Allweneedtodoisprovidethedriverwithaconnectionstringandwegetbackadatabase:

varMongoDB=require('mongodb'),

MongoClient=MongoDB.MongoClient;

connection="mongodb://localhost:27017/database"

MongoClient.connect(connection,function(error,db){

if(error)returnconsole.log(error);

console.log('Wehaveaconnection!');

});

EachsetofdatainMongoDBisstoredinacollection.Oncewehaveadatabasewecanfetchacollectiontoruntheoperationson:

varcollection=db.collection('collection_name');

Inacollection,wehaveafewsimplemethodsthatholdlotsofpower,givingusafullCRUD“API”.

EachdocumentinMongoDBhasanid,whichisaninstanceofObjectId.Theproperty

theyuseforthisidis_id.

Tosaveadocumentwejustneedtocallsave,itacceptsanobjectoranarrayofobjects.Asingleobjectinacollectionisreferredtoasadocument:

vardoc={

key:'value_1'

};

collection.save(doc,{w:1},function(){

console.log('Documentsaved')

});

IfwecallthesavefunctionwithadocumentthathasanIDthenthedocumentwillbeupdatedratherthaninserted:

varObjectId=MongoDB.ObjectId

//Thisdocumentalreadyexistsinmydatabase

vardoc_id={

_id:newObjectId("55b4b1ffa31f48c6fa33a62a"),

key:'value_2'

};

collection.save(doc_id,{w:1},function(){

console.log('DocumentwithIDsaved');

});

Nowthatwehavedocumentsinourdatabase,wecanqueryforthem,asshown:

collection.find().toArray(function(error,result){

console.log(result.length+"documentsinourdatabase!")

});

Ifnocallbackisprovidedtofindthenitwillreturnacursor;thisallowsustousemethodssuchaslimit,sort,andtoArray.

Youcanpassaquerytofindtolimitwhatisreturned.InordertofindanobjectbyitsIDweneedtousesomething,suchas:

collection.find(

{_id:newObjectId("55b4b1ffa31f48c6fa33a62a")},

function(error,documents){

console.log('Founddocument',documents[0]);

}

);

Wecanalsofilteritbyanyotherpropertyyoumightuse:

collection.find(

{key:'value'},

function(error,documents){

console.log('Found',documents.length,'documents');

}

);

IfyouhaveusedSQLbefore,youmusthavenoticedthelackofoperators,suchasOR,AND,orNOT.However,youdon’tneedtoworrybecausemongoprovidesmanyequivalents.

Youcanseeacompletelisthere:

http://docs.mongodb.org/manual/reference/operator/query/.

Alloperatorsareprefixedwiththedollarsign,forexample$and,$or,$gt,and$lt.

Youcanseethespecificsyntaxtousethesebylookingatthedocumentation.

Tousean$orcondition,youneedtoincludeitasifitisaproperty:

collection.find(

{

$or:[

{key:'value'},

{key:'value_2'}

]

},

function(error,documents){

console.log('Found',documents.length,'documents');

}

);

UsingadatabasesuchasMongoDBgivesusmorepowertoretrieveourdataandcreateamorefeaturefullsoftware.

SummaryNowwehaveplaceswherewecanstoreourdata.Ononeendwehaveasimplekey/valuestorethatprovidesuswithasuper-convenientwaytostoredataandontheotherendwehaveafeaturefulldatabasethatprovidesuswithafullsetofqueryoperators.

Boththesedatabaseswillhelpusinthenextchaptersaswemoveclosertocreatingourfullstackapplication.

InthenextchapterwewillcoverSocket.IO,areal-timecommunicationframeworkbuiltontopofWebSockets.

Chapter7.Socket.IOSimpleHTTPisgreatforthingsthatdon’tneedreal-timedata,butwhataboutwhenweneedtoknowaboutthingsastheyhappen.Forexample,ifwewerecreatingawebsitethathadachatinterfaceorsimilar?

ThisiswhensomethinglikeWebsocketscomeintoplay.WebsocketsareusuallyreferredtoasWebSocketsandarefullduplexortwo-waylow-latencycommunicationchannels.Theyaregenerallyusedbymessagingapplicationsandgameswheremessagesneedtoberelayedbetweentheserverandclient.Thereisareallyhandynpmmodulecalledsocket.io,whichcanaddWebsocketstoanyNode.jsapplication.

Toinstallitwejustneedtorun:

[~/examples/example-27]npminstallsocket.io

Socket.IOcanbesetupverysimplytolistenforconnections.First,wewanttobeabletoserveoutastatichtmlpagetorunclientsidecodewith:

varHttp=require('http'),

FS=require('fs');

varserver=Http.createServer(handler);

server.listen(8080);

functionhandler(request,response){

varindex=FS.readFileSync('index.html');

index=index.toString();

response.writeHead(200,{

'Content-Type':'text/html',

'Content-Length':Buffer.byteLength(index)

});

response.end(index);

}

Now,letscreateanHTMLfileaswell,namedindex.html,inthesamedirectory:

<html>

<head>

<title>WSExample</title>

</head>

<body>

<h2>WSExample</h2>

<pid="output"></p>

<!--SocketIOClientlibrary-->

<scriptsrc="/socket.io/socket.io.js"></script>

<scripttype="application/javascript">

/*Ourclientsidecodewillgohere*/

</script>

</body>

</html>

Let’srunourexampleandensurethatwegetourpage,weshouldbeabletoseeWSExampleonscreen.Now,toaddsocketsupporttoourapplicationwejustneedtorequiresocket.ioandspecifywhathttpservertolistenwithtoIOServer:

varIOServer=require('socket.io');

vario=newIOServer(server);

Now,wheneverthereisanewsocketconnectionover8080wewillgetaconnectioneventonio:

io.on('connection',function(socket){

console.log('NewConnection');

});

Letsaddsomecodetotheclient.Socket.IOprovidesuswithaclientlibraryandtheyexposethisthroughtheendpoint/socket.io/socket.io.js.Thisisincludedintheprecedingindex.htmlfile.

TipAlltheclientsidecodeiscontainedwithinthesecondscripttagoftheindex.htmlfile.

Tocreateaconnectionwiththeserverallweneedtodoiscallio.connectandpassthelocation.Thiswillreturnasocketforuswithwhichwecancommunicatetoourserver.

WeareusingtheclientprovidedbySocket.IOhere,asitwilldetectwhetherWebSocketsareavailable,andifpossibleusethem.Otherwise,itwillutilizeothermethodssuchaspolling,whichmakessurethatitworkseverywhereratherthanjustonevergreenbrowsers:

varsocket=io.connect('http://localhost:8080');

Wewilluseapelementtologmessagestothescreenwith.Wecandothatwiththiscode,thenallweneedtodoiscalllogScreen:

varoutput=document.getElementById('output');

functionlogScreen(text){

vardate=newDate().toISOString();

line=date+""+text+"<br/>";

output.innerHTML=line+output.innerHTML

}

Onceaconnectionismade,justlikeontheserversideaconnectioneventisemitted,wecanlistentothisusingon:

socket.on('connection',function(){

logScreen('Connection!');

});

Now,wecanrunourserveroncewenavigatetohttp://localhost:8080.YoushouldbeabletoseeConnection!showingup:

Toreceiveamessageonserverside,wejustneedtolistenforthemessageevent.Fornow,wewilljustechothemessageback:

socket.on('connection',function(){

socket.on('message',function(message){

socket.send(message);

});

});

Ontheclientside,wejustneedtocallsendtosendamessageandwewanttodothisinsideourconnectionevent.Theapioneachsideisverysimilartoeachother,asyoucansee:

socket.send('Hello');

Ontheclientside,wealsowanttolistenformessagesandlogthemtothescreen:

socket.on('message',logScreen);

Oncewerestarttheserverandrefreshourpage,weshouldbeabletoseeanadditionalHellomessageappearonscreen.

[~/examples/example-27]$nodeindex.js

Hello

Thishappensbecausetheservercannowsendtheclientpacketsofdata.Italsomeansthatwecanupdatetheclientatanytime.Forexample,everysecondwecansendanupdatetotheclient:

socket.on('connection',function(){

functiononTimeout(){

socket.send('Update');

}

setInterval(onTimeout,1000);

});

Now,whenwerestartourserverweshouldbeabletoseeanupdatemessageeverysecond.

Youmighthavenoticedthatyoudidn’tneedtorefreshyourwebpagefortheconnectiontobeopenedagain.Thisisbecausesocket.iotransparentlykeepsourconnections“alive”

aswellasreconnectingifneeded.Thistakesallthepainoutofusingsockets,aswehavenoneofthesetroubles.

RoomsSocket.IOalsohastheconceptofrooms,wheremultipleclientscanbegroupedintodifferentrooms.Toemulatethis,allyouwillneedtodoisnavigatetohttp://localhost:8080inmultipletabs.

Onceaclientconnects,weneedtocallthejoinmethodtotellthesocketwhatroomtobein.Ifwewishtodosomethingsuchasagroupchatwithspecificusersonly,weneedhavearoomidentifierinadatabaseorcreateone.Fornowwewilljusthaveeveryonejointhesameroom:

socket.on('connection',function(){

console.log('NewConnection');

varroom='ourroom';

socket.join(room,function(error){

if(error)returnconsole.log(error);

console.log('Joinedroom!');

});

});

Foreverytabweopen,weshouldseeamessagethatwehavejoinedaroom:

[~/examples/example-27]$nodeindex.js

NewConnection

Joinedroom!

NewConnection

Joinedroom!

NewConnection

Joinedroom

Withthis,wecanbroadcastamessagetotheentireroom.Let’sdothiseverytimesomeonejoins.Withinthejoincallback:

socket

.to(room)

.emit(

'message',

socket.id+'joinedtheroom!'

);

Ifyoulookinyourbrowser,witheachconnectiontheotherclientsgetanotificationthatsomeoneelsehasjoined:

x3OwYOkOCSsa6Qt5AAAFjoinedtheroom!

mlx-Cy1k3szq8W8tAAAEjoinedtheroom!

Connection!

Connecting

Thisisgreat,wecannowcommunicatealmostdirectlybetweenbrowsers!

Ifwewanttoleavearoom,allweneedtodoiscallleave,wewillbroadcastthisbeforewecallthefunction:

socket

.to(room)

.emit(

'message',

socket.id+'isleavingtheroom'

);

socket.leave(room);

Whilerunningthis,youwillnotseeanymessagesfromanotherclientbecauseyouareleavingrightaway:however,ifyouweretoputadelayonthisyoumightseeanotherclientcomeandgo:

leave=function(){

socket

.to(room)

.emit(

'message',

socket.id+'isleavingtheroom'

);

socket.leave(room);

};

setTimeout(leave,2000);

AuthenticationForauthentication,wecanusethesamemethodthatweusedwithourHTTPserverandwecanacceptaJSONWebToken

Intheseexamples,forsimplicitywewilljusthaveasingleHTTProutetologin.WewillsignaJWTthatwewilllaterauthenticatebycheckingthesignature

Weneedtoinstallacoupleofextranpmmodulesforthis;wewillincludechancesothatwecangeneratesomerandomdata.

[~/examples/example-27]npminstallsocketio-jwtjsonwebtokenchance

First,wearegoingtoneedaroutetologin.Wewillmodifyourhandlertowatchfortheurl/login:

if(request.url==='/login'){

returngenerateToken(response)

}

OurnewfunctiongenerateTokenwillcreateaJSONWebTokenwithsomerandomdatausingchance.Wewillalsoneedasecretforourtokens:

varJWT=require('jsonwebtoken'),

Chance=require('chance').Chance();

varjwtSecret='Oursecret';

functiongenerateToken(response){

varpayload={

email:Chance.email(),

name:Chance.first()+''+Chance.last()

}

vartoken=JWT.sign(payload,jwtSecret);

response.writeHead(200,{

'Content-Type':'text/plain',

'Content-Length':Buffer.byteLength(token)

})

response.end(token);

}

Now,wheneverwerequesthttp://localhost:8080/loginwewillreceiveatokenthatwecanuse:

[~]$curl-XGEThttp://localhost:8080/login

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJlbW

joiR2VuZSBGbGVtaW5nIiwiaWF0IjoxNDQxMjcyMjM0

e1Y

Wecanenterthisintothedebuggerathttp://jwt.io/andseethecontents:

{

"email":"jefoconeh@ewojid.io",

"name":"GeneFleming",

"iat":1441272234

}

Awesome,wehaveatokenandarandomuserbeinggeneratedforus.Now,wecanusethistoauthenticateourusers.Socket.IOhasamethodontheservertodothisandwejustneedtopassahandlertypefunctiontoit.Thisiswheresocketio-jwtcomesin,wepassitoursecretanditwillensureitisarealtoken,prettysimple:

varSocketIOJWT=require('socketio-jwt');

io.use(SocketIOJWT.authorize({

secret:jwtSecret,

handshake:true}));

Now,whenwetrytoconnecttoourserverfromtheclientitwillneveremittheconnectevent,asourclientisn’tauthenticated.Thisisexactlywhatwewant.

WefirstwanttowrapupourSocket.IOcode(wewillcallthislater);wealsowanttogiveitaparameteroftoken:

functionsocketIO(token){

varsocket=io.connect('http://localhost:8080');

varoutput=document.getElementById('output');

functionlogScreen(text){

vardate=newDate().toISOString();

line=date+""+text+"<br/>";

output.innerHTML=line+output.innerHTML

}

logScreen('Connecting');

socket.on('connect',function(){

logScreen('Connection!');

socket.send('Hello');

});

socket.on('message',logScreen);

}

Next,wewillcreatealoginfunction,thiswillrequesttheloginURLandthenpasstheresponsetothesocketIOfunction,asshown:

functionlogin(){

{

varrequest=newXMLHttpRequest();

request.onreadystatechange=function(){

if(

request.readyState!==4||

request.status!==200

)return

socketIO(request.responseText);

}

request.open("GET","/login",true);

request.send(null);

}

Thenwewanttocalltheloginfunction:

login();

Wecanpassthetokenontotheserverbychangingtheconnectcalltopassaquerystring:

varsocket=io.connect('http://localhost:8080',{

query:'token='+token

});

Now,whenrunningourserverandnavigatingtoourclientweshouldbeabletoconnect—awesome!Sincewehaveauthenticatedwecanalsorespondwithapersonalizedmessageforeachuser,insideourserver-sideconnectioneventhandlerwewillemitamessagetotheclient.

Oursocketwillhaveanewpropertycalleddecoded_token;usingthiswewillbeabletoviewthecontentsofourtoken:

varpayload=socket.decoded_token;

varname=payload.name;

socket.emit('message','Hello'+name+'!');

Oncewejoinourroom,wecantelltherestoftheclientswhohavealsojoined:

socket

.to(room)

.emit(

'message',

name+'joinedtheroom!'

);

SummarySocket.IObringsamazingcapabilitiestoourapplications.Wecannowinstantlycommunicatewithothers,eitherindividuallyorbybroadcastinginaroom.Withtheabilitytoidentifyusers,wecanrecordmessagesorthehistoryofthatuser,readytobeservedupbyaRESTfulAPI.

Wearenowreadytobuildreal-timeapplications!

Chapter8.CreatingandDeployingPackagesNowthatwehaveallofthepiecesthatareneededtocreateNode.jsapplicationsandservers,wewillnowfocusmoreonsharingourmodulesandcontributingtotheeco-system.

Allthepackagesonnpmhavebeenuploaded,maintained,andcontributedbysomeoneinthecommunity,solet’shavealookathowwecandothesameourselves.

CreatingnpmpackagesWecanstartwiththefollowingsteps:

Firstweneedtocreateauser:

[~]$npmadduser

Username:<username>

Password:

Email:(thisISpublic)<email>

Oncewehaveauserwehaveopenedthegatestonpm.

Now,let’screateapackage:

[~/examples/example-22]$npminit

{

"name":"njs-e-example-package",

"version":"1.0.0",

"description":"",

"main":"index.js",

"scripts":{

"test":"echo\"Error:notestspecified\"&&exit1"

},

"author":"",

"license":"ISC"

}

Topublishthispackageallweneedtodoisrunnpmpublish:

[~/examples/example-22]$npmpublish

+njs-e-example-package@1.0.0

Youcanseethatwehavepublishedourpackagesuccessfully,youcanviewtheoneIpublishedat:

https://www.npmjs.com/package/njs-e-example-package

Youwillhavetonameyourpackagesomethingelseinordertopublishit;otherwise,wewillhaveaconflict.

Nowwecanrunthefollowingcommand:

[~/examples/example-21]$npminstallnjs-e-example-package

njs-e-example-package@1.0.0node_modules/njs-e-example-package

Thenwewillhavethepackage!Isn’tthatprettycool?

Ifwetrytopublishagain,wewillgetanerrorbecauseversion1.0.2isalreadypublished,asshowninthefollowingscreenshot:

Toincrementourpackageversion,allweneedtodoisexecute:

[~/examples/example-22]$npmversionpatch

v1.0.1

Nowwecanpublishagain:

[~/examples/example-22]$npmpublish

+njs-e-example-package@1.0.1

Youcangotoyourpackagespageonnpmandyouwillseethattheversionnumberandreleasecounthasbeenupdated.

VersioninginNode.jsfollowsthesemverschema,whichismadeupofmajor,minor,andpatchversions.Whenthepatchversionisincremented,itmeansthattheAPIhasstayedthesamehoweversomethinghasbeenfixedbehindthescenes.Iftheminorversionhasbeenincremented,itmeansthatanon-breakingAPIchangehasoccurred,suchasamethodhasbeenadded.Ifthemajorversionisupdated,itmeansthattherehasbeenabreakingAPIchange;forexampleamethodhasbeendeletedoramethodsignaturehaschanged.

Sometimes,therearethingsinyourprojectthatyoudon’twanttobepushedoutforotherpeopletohave.Thiscouldbeanoriginalsource,somecertificates,ormaybesomekeysfordevelopment.Justlikewhenusinggit,wehaveanignorefilecalled.npmignore.

Bydefault,ifthereisno.npmignorebutthereisa.gitignore,npmwillignorewhatismatchedbythe.gitignorefile.Ifyoudon’tlikethisbehaviorthenyoucanjustcreateanempty.npmignorefile.

The.npmignorefilefollowsthesamerulesas.gitignore,whichareasfollows:

Blanklinesorlinesstartingwith#areignoredStandardglobpatternsworkYoucanendpatternswithaforwardslash/tospecifyadirectoryYoucannegateapatternbystartingitwithanexclamationpoint!

Forexample,ifwehadadirectoryofcertificatesinwhichwehadakey:

[~/examples/example-22]$mkdircertificates

[~/examples/example-22]$touchcertifticates/key.key

Weprobablydon’twantthistobepublished,soinourignorefilewewillhave:

certificates/

Wealsodon’twantanykeyfilesthatwehavelyingaround,soweaddthisaswell:

*.key

Now,let’spublish:

[~/examples/example-22]$npmversionpatch

v1.0.2

[~/examples/example-22]$npmpublish

+njs-e-example-package@1.0.2

Now,let’sinstallourpackage:

[~/examples/example-23]$npminstallnjs-e-example-package@1.0.2

Now,whenwelistwhat’sinthedirectory,wedon’tseeallourcertificatesbeingpassedaround:

[~/examples/example-23]$lsnode_modules/njs-e-example-package

package.json

Thisisgreat,butwhatifwewanttoprotectourentirepackageandnotjustafewcertificates?

Allweneedtodoissetprivatetotrueinourpackage.jsonfileanditwillpreventnpmfrompublishingthemodulewhenwerunnpmpublish:

Ourpackage.jsonshouldlooksomethinglike:

{

"name":"example-23",

"version":"1.0.0",

"description":"",

"main":"index.js",

"scripts":{

"test":"echo\"Error:notestspecified\"&&exit1"

},

"author":"",

"license":"UNLICENSED",

"dependencies":{

"njs-e-example-package":"^1.0.2"

},

"private":true

}

Now,whenwerunnpmpublish:

[~/examples/example-23]$npmpublish

npmERR!Thispackagehasbeenmarkedasprivate

Awesome,that’sexactlywhatwewantedtosee.

SummaryLookslikewearegettingclosertobeingreadywithallthingsaboutNode.js.Weknownowhowtosetup,debug,develop,anddistributeoursoftware.

Inthenextchapter,wewillcoveronemoreconceptweneedtoknowabout:unittesting.

Chapter9.UnitTestingWehavecomethisfarbuthaven’tdoneanytesting!That’snotverygood,isit?Usually,ifnotalways,testingisamajorconcerninsoftwaredevelopment.Inthischapter,wewillcoverunittestingconceptswithNode.

TherearemanytestingframeworksforNode.jsandinthischapterwewillbecoveringMocha.

InstallingmochaToensurethatmochagetsinstalledeverywhere,weneedtoinstallitglobally.Thiscanbedoneusingthe-gflagwithnpminstall:

[~/examples/example-24]$npminstall-gmocha

Now,wecanuseMochathroughtheterminalconsole.

Typically,wewillcontainallourtestingcodeinatestsub-directorywithintheproject.Allweneedtodotogetourcoderunningisrunmocha,assumingwehavewrittensometestsfirst.

Aswithmany(ifnotall)unittestingframeworks,Mochausesassertionstoensurethatatestrunscorrectly.Ifanerroristhrownandisnothandledthenthetestisconsideredtohavefailed.Whatassertionlibrariesdoisthrowerrorswhenanunexpectedvalueispassed,sothisworkswell.

Node.jsprovidesasimpleassertionmodule,let’shavealookatthefollowing:

[~/examples/example-24]$node

>assert=require('assert')

>expected=1

>actual=1

>assert.equal(actual,expected)

>actual=1

>assert.equal(actual,expected)

AssertionError:2==1

Aswecansee,anerroristhrowniftheassertiondoesn’tpass.However,theerrormessageprovidedwasn’tveryhandy;tofixthiswecanpassanerrormessageaswell:

>assert.equal(actual,expected,'Expected1')

AssertionError:Expected1

Withthiswecancreateatest.

Mochaprovidesmanywaysofcreatingtests,thesearecalledinterfacesandthedefaultiscalledBDD.

Youcanviewallinterfacesathttp://mochajs.org/#interfaces.

TheBDD(BehaviorDrivenDevelopment)interfacecanbecomparedtoGherkinwherewespecifyafeatureandasetofscenarios.Itprovidesmethodstohelpdefinethesesets,describeorcontextisusedtodefineafeature,andtheitorspecifyfunctionsareusedtodefineascenario.

Forexample,ifweweretohaveafunctionthatjoinssomeone’sfirstandlastname,thetestsmightlooksomethinglikethefollowing:

varGetFullName=require('../lib/get-full-name'),

assert=require('assert');

describe('Fetchfullname',function(){

it('shouldreturnbothafirstandlastname',function(){

varresult=GetFullName({first:'Node',last:'JS'})

assert.equal(result,'NodeJS');

})

})

Wecanalsoaddafewmoretestsforthis;forexample,ifitthrewanerrorincaseofnoobjectbeingpassed:

it('shouldthrowanerrorwhenanobjectwasnotpassed',function(){

assert.throws(

function(){

GetFullName(null);

},

/Objectexpected/

)

})

Youcanexploremanymoremocha-specificfeaturesathttp://mochajs.org/.

ChaiAlongwiththemanytestingframeworks,therearealsomanyassertionframeworks,oneofwhichiscalledChai.Completedocumentationcanbefoundathttp://chaijs.com/.

Insteadofjustusingthebuilt-inassertionmoduleprovidedbyNode.js,wemaywanttouseamodulesuchasChaitoextendourpossibilities.

Chaihasthreesetsofinterfaces,should,expect,andassert.Inthischapter,wewillbecoveringexpect.

Whenusingexpect,youareusingnaturallanguagetodescribewhatyouwant;forexample,ifyouwantsomethingtoexist,youcansay,expect(x).to.existratherthanassert(!!x):

varExpect=require('chai').expect

varAssert=require('assert')

varvalue=1

Expect(value).to.exist

assert(!!value)

Usingnaturallanguagemakesthingsalotclearerforpeoplereadingyourtests.

Thislanguagecanbelinkedtogether;wehaveto,be,been,is,that,which,and,has,have,with,at,of,andsame,whichcanhelpustobuildsentenceslike:

Expect(value).to.be.ok.and.to.equal(1)

However,thesewordsareonlyforreliabilityandtheydon’tmodifytheresult.Therearealotofotherwordsthatcanbeusedtoassertthings,suchasnot,exists,ok,andmanymore.Youcanviewthemallathttp://chaijs.com/api/bdd/.

Someexamplesofchaiinuseare:

Expect(true).to.be.ok

Expect(false).to.not.be.ok

Expect(1).to.exists

Expect([]).to.be.empty

Expect('hi').to.equal('hi')

Expect(4).to.be.below(5)

Expect(5).to.be.above(4)

Expect(function(){}).to.be.instanceOf(Function)

StubbingmethodsIfitlookslikeaduck,swimslikeaduck,andquackslikeaduck,thenitprobablyisaduck.

Asyouwriteyourtestsyouwanttobeonlybetestingunitsofcode.Generallythiswillbeamethod,provideitsomeinput,andexpectanoutputofsomekind,orifitisavoidfunction,expectnothingtobereturned.

Withthisinmind,youhavetothinkofyourapplicationasbeinginasandboxedstatewhereitcan’ttalktotheoutsideworld.Forexample,itmightnotbeabletotalktoadatabaseormakeanykindofexternalrequest.Havingthisassumptionisgreatifyouaregoingto(andyouusuallyshould)implementcontinuousintegrationanddeployment.ItalsomeansthattherearenoexternalrequirementsforthemachineyouaretestingonexceptforNode.jsandthetestingframework,whichcouldjustbeapartofyourpackageanyway.

Unlessthemethodyouaretestingisrathersimpleanddoesn’thaveanyexternaldependencies,youwillprobablywanttomockthemethodsthatyouknowitisgoingtoexecute.AgreatmoduletodothisiscalledSinon.js;itallowsyoutocreatestubsandspiestomakesurethatthecorrectdatareturnsfromothermethodsandtoensurethattheywerecalledinthefirstplace.

sinonprovidesmanyhelpers,asmentionedbeforeandoneoftheseiscalledaspy.Aspyisusedmainlytojustwrapafunctiontoseewhatitsinputandoutputwas.Onceaspyhasbeenappliedtoafunction,totheoutsideworlditbehavesexactlythesame.

varSinon=require('sinon');

varreturnOriginal=function(value){

returnvalue;

}

varspy=Sinon.spy(returnOriginal);

result=spy(1);

console.log(result);//Logs1

Wecanuseaspytocheckifafunctionwascalled:

assert(spy.called)

Orwhatargumentswerepassedforeachcall:

assert.equal(spy.args[0][0],1)

Ifweprovidedspywithanobjectandamethodtoreplacethenwecanrestoretheoriginaloncewearefinished.Wewillusuallydothisintheteardownofourtest:

varobject={

spyOnMe:function(value){

returnvalue;

}

}

Sinon.spy(object,'spyOnMe')

varresult=object.spyOnMe(1)

assert(result.called)

assert.equal(result.args[0][0],1)

object.spyOnMe.restore()

Wealsohaveastubfunction,whichinheritsallthefunctionalityofspybutinsteadofcallingtheoriginalfunctionitcompletelyreplacesit.

Thisissowecandefinethebehavior,forexample,whatitreturns:

varstub=Sinon.stub().returns(42)

console.log(stub())//logs42

Wecanalsodefineareturnvalueforasetofargumentspassed:

varstub=Sinon.stub()

stub.withArgs(1,2,3).returns(42)

stub.withArgs(3,4,5).returns(43)

console.log(stub(1,2,3))//logs42

console.log(stub(3,4,5))//logs43

Let’ssaywehadthissetofmethods:

functionUsers(){

}

Users.prototype.getUser=function(id){

returnDatabase.findUser(id);

}

Users.prototype.getNameForUser=function(id){

varuser=this.getUser(id);

returnuser.name;

}

module.exports=Users

Now,weonlycareaboutthescenariowhereauserisreturned,asthegetUserfunctionwillthrowanerrorifitcan’tfindit.Knowingthis,wejustwanttotestthatwhenauserisfounditreturnstheirname.

Thisisaperfectexampleofwhenwewanttostubamethod:

varSinon=require('sinon');

varUsers=require('../lib/users');

varAssert=require('assert');

it('shouldreturnausersname',function(){

varname='NodeJS';

varuser={name:name};

varstub=Sinon.stub().returns(user);

varusers=newUsers();

users.getUser=stub;

varresult=users.getNameForUser(1);

assert.equal(result,name,'Namenotreturned');

});

Insteadofreplacingthefunctionwecanalsopassthefunctionusingthescope,replacingthiswiththeobjectwepassed;eitherwayissufficient.

varresult=users.getNameForUser.call(

{

getUser:stub

},

1

);

SummaryEverythingweneedtocreateaNode.jsapplicationisnowatourfingertips.Testingisjustoneofthosethingsthatareessentialtoanysuccessfulsoftware.Wecoveredusingmochaasatestingframeworkandchaiasanassertionframework.

Inthenextchapter,wewillcoverusinganotherlanguagewithNode.js,CoffeeScript!

Chapter10.UsingMoreThanJavaScriptThroughoutthisbookwehaveusedonlyJavaScript.Well,it’scalledNode.jsisn’tit?

However,thatdoesn’tmeanthatwecan’tuseotherlanguageswithit.WecanandaslongasitcompilestoJavaScriptyouaregoodtogo.

Thereisabiglistofcommonlanguagesthatareavailableat:https://github.com/jashkenas/coffeescript/wiki/list-of-languages-that-compile-to-JS.

Ifyouaremissingyourstronglytypedlanguageorjustwantaslightlydifferentsyntax,thentherewillsurelybeoneoptionoutthereforyousomewhere.

AcoupleofcommonlanguagesincludeCoffeeScriptandTypeScript,theyworkgreatwithNode.jsastheybothcompiletoJavaScript.Inthischapter,wewillcovertheusageofCoffeeScript.TypeScriptissimilarinusage;however,thesyntaxfollowsasimilarpathtoC#andJava.

CoffeeScriptIt’sverysimpletoinstallandstartusingadditionallanguages.Let’shavealookatCoffeeScript:

WeneedtoinstallCoffeeScriptglobally,sothatwecanuseacommandsimilartonode:

[~]npminstall-gcoffee-script

Nowwecanruncoffee:

[~]coffee

>

ThesyntaxisverysimilartoJavaScript:

[~]coffee

>1+1

2

>console.log('Hello')

Hello

Insteadofusingthe.jsextension,weuse.coffee.

First,wewillcreateaCoffeeScriptfile:

/*index.coffee*/

console.log('HelloCoffeeScript!')

Thentorunit,allweneedtodoisusethecoffeecommand,similartothenodecommand:

[~/examples/example-25]coffeeindex.coffee

HelloCoffeScript!

Tocompileour.coffeefilesinto.js,wecanuse-c.Oncecompiled,wecanrunthemdirectlywithNode.js:

[~/examples/example-25]coffee-cindex.coffee

[~/examples/example-25]nodeindex.js

HelloCoffeeScript!

IfwehaveabunchofCoffeeScriptthatwewanttocompiletoJavaScriptallatonce,wecanusecoffee-c-o./lib./src.Thiswilltakeall.coffeefilesfrom./src,compilethemto.jsandthenoutputthemto./lib.

YouwillneedtocompileallyourfilesforotheruserstouseourCoffeeScriptcodealongsidetheirJavaScriptcode.ThealternativeistoincludeCoffeeScriptasadependencyandrequiretheregisterfileintoyourapplication,asshown:

/*index.js*/

require('coffee-script/register');

require('./other.coffee');

YoumayneedtodothisifdonotyouwishtocompileyourCoffeeScript,orifyouareusingatoolthatrequiresaJavaScriptfilesuchasGulporGrunt.

TipToseetheequivalentsbetweenJavaScriptandCoffeeScriptyoucanusethesitehttp://js2.coffee/,itprovidesasimplewaytocomparethetwoonthefly.

CoffeeScriptisbasicallyjustJavaScript;however,ithastargetedreadabilityandsimplicity.WithsimplicityitalsotriestolimittheuseofthebadpartsofJavaScriptandexposesthegoodparts.

UsingCoffeeScriptisusuallygreatforbeginners,(andforexperts),asitusesEnglishlanguageratherthancomputerlanguage.Forexample,insteadofusing===(tripleequals)tocheckiftwovaluesequal,wecanjustusetheEnglishwordis.So,x===ybecomesxisy,whichmeansthatthereisnotranslatingrequiredwhenreading.

Alongwithis,thereareotherkeywords,suchasisnt,not,or,and,yesandno.

Usingthesekeywordsinsteadofsymboloperatorsgivesclaritytothereadersandprogrammers.TheCoffeeScripthassimilarformattingtoPythoninthewayfunctionsandcodeblocksaredeclared;theindentationindicateswhentheblockendsandbegins

CodeblocksandfunctionsInJavaScriptyouwillusuallygrouptogetherblocksusingcurlybraces,asshowninthefollowingexample:

if(true)

{

console.log('Itwastrue!')

}

WhereasinCoffeeScriptyouwillleaveoutallthecurlybraces,infactallthebracesareleftout:

iftrue

console.log('Itwastrue!')

Thesameistruewhendeclaringafunction,noticethatweareusinganarrowratherthanthekeywordfunction.Theparameterlistisonlyrequiredifyouwantnamedarguments:

func=->

console.log('Iexecuted')

CoffeeScripttriestoassumeasmuchaspossiblewhilestillgivingtheprogrammerenoughcontrol.

YoumayhavealsonoticedthatIdidn’tusethevarkeywordwhendeclaringafunction.Thisisbecauseitisimplicitlydeclared,asyoucanseebycompilingtheabovecodetoJavaScript:

varfunc;

func=function()

{

returnconsole.log('Iexecuted');

};

Youcanseeinthiscompiledcodethatthelaststatementinthefunctionisthereturnvalue,thismeansthatwedon’tneedtodeclarethereturnvalueandjustassumethatthelastvalueisreturned.Thismakesitverysimpletocreateonelinefunctions,suchas:

add=(a,b)->a+b

UnlikeJavaScript,youmayprovidedefaultargumentsforafunctionandthiscanbecomparedtoC#;however,it’snotlimitedtoonlyconstantsasitessentiallyexecutesthestatementwithinthefunction:

keys={}

func=(key,date=newDate)->

keys[key]=date

Youcanseethisbycompilingtheabovefunctionas:

varfunc,keys;

keys={};

func=function(key,date)

{

if(date==null)

{

date=newDate();

}

returnkeys[key]=date;

};

Essentially,allCoffeeScriptdoesischeckifthevalueisnullorundefined.

TheexistentialoperatorYoucanchecktoseeifavalueisnullorundefinedusingtheexistentialoperator,whichcheckstoseeifthevalueexists.Thisisindicatedbyusingthequestionmarksymbolafteravariable;thestatementwillbetrueifthevalueexistsandotherwisefalse.

Tousethisinanexpression:

date=null

ifnotdate?

date=newDate()

console.log(date)

Youcanusethisasashorthandoperatoraswell,forexample:

date?=newDate()

console.log(date)

Theabovetwoexamplesofcodewillbehaveexactlythesameandwillactuallycompiletogivethesamecode:

vardate;

date=null;

if(date==null)

{

date=newDate();

}

Youmayalsousetheexistentialoperatortoensureavalueexistsbeforeaccessingapropertyofit.Forexample,ifyouwanttogetthetimefromadate,or-1ifthedatedoesn’texist:

getTime=(date=null)->date?.getTime()?-1

Givingdatethenullvalueshowsthatwedon’tmindifnovalueispassed:

Whenanobjectdoesn’texistandtheoperatorisusedthenthereturnedvalueisundefined,thismeansthatwecanusethesameoperatoragaintoreturnadefaultvalue.

ObjectsandarraysAlongwithalltheassumptionsthatCoffeeScripttriestomake,itsurelydoestrytoremovealltheun-neededsyntaxplainJavaScriptrequires.Anotherinstanceofthiscanbeseenwhiledefiningarraysandobjectsinwhichtheuseofanewlinedeclaresanewitem.Forexample,youwillusuallydefineanarrayas:

array=[

1,

2,

3

]

Thisstillworks;however,withCoffeeScriptyoucanleaveoutthecommasseparatingeachitem:

array=[

1

2

3

]

Youcanalsomixthetwostylestogether:

array=[

'a','b','c'

1,2,3

true,false

]

Youcandothesamewithobjects,suchas:

object={

foo:1

bar:2

}

Withobjectsyoucanevenleaveoutthecurlybracesanduseindentationtoshowthedifferencesintheobject:

object=

foo:1

bar:2

foobar:

another:3

key:4

ToloopanarrayinCoffeeScript,allyouneedtodoisusethefor…inloop,suchas:

forvalue,indexinarray

console.log(value,index)

continueiftypeofvalueis'string'

console.log('Valuewasnotastring')

Ifyoudonotwishtousetheindexofyouritem,yousimplydon’taskforit:

forvalueinarray

console.log(value)

AswithJavaScriptloops,youcanusebreakandcontinuetocontroltheflow.

ToloopanobjectinCoffeeScriptyoucanusethefor…ofloop,thisisabitdifferentfromthefor…ofloopprovidedbyJavaScript:

forkey,valueofobject

console.log(key,value)

Aswiththefor…inloop,ifyoudon’twantthevalue,excludeit:

forkeyofobject

console.log(key)

Forbothtypesofloops,thenamingisirrelevant:

forkey,valueofobject

#Notethatthiswillletdatesandarraysthrough(etc)

continueunlessvalueinstanceofObject

fornestedKey,nestedValueofvalue

console.log(nestedKey,nestedValue)

ClassesUnlikeJavaScript,CoffeeScriptprovidesanaturalwaytodeclareclassesandinheritance.

TodefineaclassinJavaScript,youneedtodeclareafunctionfirst:

functionUser(username){

this.username=username;

}

Thenyouwilldeclaretheprototypemethods:

User.prototype.getUsername=function(){

returnthis.username;

}

Ifyouhaveastaticmethod,youcandefinethisonthefunctionratherthantheprototype:

User.createUser=function(username){

returnnewUser(username);

}

InCoffeeScriptyoucanusetheclasskeywordandgivetheclassaname.Youcanthendeclaretheconstructor,static,andinstance(prototype)methods:

classUser

@createUser:(username)->

returnnewUser(username)

constructor:(username)->

this.username=username

getUsername:->

returnthis.username

Usually,youplaceallyourstaticmethodsaboveyourconstructorsothattheystayseparatefromyourinstancemethods.Thisavoidsconfusion,youmayhavenoticedthatIdeclaredthestaticmethodcreateUserwitha@prefix,thisishowyoudefineastaticmethodinCoffeeScript.However,youcanalsousethetraditionalJavaScriptmethodofUser.createUser=->,eitherwaywillworkhere.

Thecodethatisrunwhentheinstanceisbeingcreatedorconstructediscalledtheconstructor.Thisisthesameterminologythatisusedinmanyotherlanguagessoitshouldbefamiliar.Aconstructorisessentiallyafunction.

Alltheinstancemethodsaredeclaredsimilarlytopropertiesofanobject.

Withclassescomesanothersymbol,the@symbol.Whenusedonaninstance,youcanuseittorefertothethiskeyword.Forexample,thegetUsernamemethodcanbewrittenas:

getUsername:->

return@username

Or,ifwewanttodropthereturnstatementandmakeitaoneliner:

getUsername:->@username

The@symbolcanalsobeusedinparameterliststodeclarethatwewanttheinstancepropertytobesetasthepassedvalue.Forexample,ifwehadasetUsernamemethodwecaneitherdo:

setUsername:(username)->

@username=username

Orwecando:

setUsername:(@username)->

BoththemethodswillcompiletothesameJavaScriptcode.

Giventhefactthatwecanusethe@symbolinourparameterlist,wecanrefactorourconstructorfunctionto:

constructor:(@username)->

AnotheradvantageofusingCoffeeScriptclassisthatwecandefineinheritance.Todoso,allweneedtodoisusetheextendskeyword,thisissimilartootherlanguages.

Intheseexamples,wewanttohavetwoclasses,PersonandRobotthatextendthebaseUserclass.

Forourperson,wewanttobeabletogivethemanameandanagealongwiththeusernamethattheUserclassrequires.

First,weneedtodeclareourclass:

classPersonextendsUser

Thendeclareourconstructor.Inourconstructor,wewillcallthesuperfunction,thiswillexecutetheconstructoroftheparentclassUserandwewanttopasstheusernametoit,asshown:

constructor:(username,@name,@age)->

super(username)

Wethenaddtwomethods,getNameandgetAge:

getName:->@name

getAge:->@age

Next,wewilldothesameforRobot,exceptthistimeweonlywantausernameand@usage:

classRobotextendsUser

constructor:(username,@usage)–>

super(username)

getUsage:->@usage

Wecannowcreateinstancesofourclassesandcomparethem,asshownhere:

SummaryCoffeeScripttriestomakegoodassumptionswithyourcode.ThishelpstoremovesomeproblemsthatJavaScriptdeveloperscomeacross.Forexample,thedifferencebetween==and===.

YoucanlearnmoreaboutthespecificsyntaxofCoffeeScriptathttp://coffeescript.org/.

Inthischapterwehavecoveredutilizinganotherlanguage.ThiscanhelpalleviatethestruggleswithJavaScript’sstyleorsyntaxforbeginners.Forpeoplewhoareusedtomorelanguagefeatures,thisisabigadvantageasithelpsremovethepitfallsthatpeopleusuallycomeacross.

IndexA

argumentsabout/Arguments

authenticationbasicauthentication/Basicauthentication

BBDD(BehaviorDrivenDevelopment)/InstallingmochaBearertoken

about/Bearertokensbunyan

reference/Logging

Cchai

about/ChaiURL/Chai

CoffeeScriptabout/CoffeeScriptinstalling/CoffeeScriptreference/CoffeeScriptcodeblocks/Codeblocksandfunctionsfunctions/Codeblocksandfunctionsexistentialoperator,using/Theexistentialoperatorobjects/Objectsandarraysarrays/Objectsandarraysclasses/Classes

configurationJSONfiles/JSONfilesenvironmentalvariables/Environmentalvariablesarguments/Arguments

Eenvironmentalvariables

about/Environmentalvariableserrorhandling

about/Errorhandlingexistentialoperator

about/Theexistentialoperator

HHellorequireexample

about/HellorequireHTTPmethods

POST/IntroducingroutingGET/IntroducingroutingDELETE/Introducingrouting

JJSONfiles

about/JSONfilesJSONWebToken(JWT)

about/BearertokensURL/Bearertokens

LLevelDB

about/LevelDBlogging

about/Logging

Mmethods

stubbing/Stubbingmethodsmocha

installing/Installingmochareference,forinterfaces/InstallingmochaURL/Installingmocha

MongoDBabout/MongoDB

MongoLababout/MongoDB

morganabout/Loggingreference/Logging

NNode.js

settingup/SettingupURL/Settingup

npmabout/HellonpmURL/Hellonpmscriptsobject/Hellonpm

npmpackagescreating/Creatingnpmpackages

OOAuth

about/OAuthURL/OAuth

Rrouting

about/Introducingrouting

SSinon.js/StubbingmethodsSocket.IO

rooms/Roomsauthentication/Authentication

spy/Stubbingmethodsstubfunction/Stubbingmethods

Ttry/catchfunction/Errorhandling

top related