731

Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

  • Upload
    others

  • View
    6

  • Download
    2

Embed Size (px)

Citation preview

Page 1: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you
Page 2: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Node.jsWebDevelopmentFourthEdition

Server-sidedevelopmentwithNode10madeeasy

Page 3: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

DavidHerron

Page 4: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

BIRMINGHAM-MUMBAI

Page 5: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Node.jsWebDevelopmentFourthEditionCopyright©2018PacktPublishingAllrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.

Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthor,norPacktPublishingoritsdealersanddistributors,willbeheldliableforanydamagescausedorallegedtohavebeencauseddirectlyorindirectlybythisbook.

PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.

CommissioningEditor:AmarabhaBanerjeeAcquisitionEditor:LarissaPintoContentDevelopmentEditor:GauriPradhanTechnicalEditor:LeenaPatilCopyEditor:SafisEditingProjectCoordinator:SheejalShahProofreader:SafisEditingIndexer:MariammalChettiyarGraphics:JasonMonteiroProductionCoordinator:ShraddhaFalebhai

Firstpublished:August2011

Secondedition:July2013

Thirdedition:June2016

Fourthedition:May2018

Productionreference:1240518

PublishedbyPacktPublishingLtd.LiveryPlace35LiveryStreetBirminghamB32PB,UK.

ISBN978-1-78862-685-9

www.packtpub.com

Page 6: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Tomymother,Herron,andtothememoryofmyfather,James,sinceIwouldnotexistwithoutthemTomypartnerMaggieforbeingmylovingpartnerthroughoutourjointlife-journey

Page 7: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

mapt.io

Maptisanonlinedigitallibrarythatgivesyoufullaccesstoover5,000booksandvideos,aswellasindustryleadingtoolstohelpyouplanyourpersonaldevelopmentandadvanceyourcareer.Formoreinformation,pleasevisitourwebsite.

Page 8: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Whysubscribe?SpendlesstimelearningandmoretimecodingwithpracticaleBooksandVideosfromover4,000industryprofessionals

ImproveyourlearningwithSkillPlansbuiltespeciallyforyou

GetafreeeBookorvideoeverymonth

Maptisfullysearchable

Copyandpaste,print,andbookmarkcontent

Page 9: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

PacktPub.comDidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusatservice@packtpub.comformoredetails.

Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewsletters,andreceiveexclusivediscountsandoffersonPacktbooksandeBooks.

Page 10: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Contributors

Page 11: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

AbouttheauthorDavidHerronisasoftwareengineerinSiliconValley,workingonprojectsfromanX.400e-mailservertoassistlaunchingtheOpenJDKproject,toYahoo'sNode.jsapplication-hostingplatform,andasolararrayperformancemonitoringservice.Davidwritesaboutelectricvehicles,greentechnologyonTheLongTailPipewebsite,andaboutothertopics,includingNode.js,onTechSparxwebsite.UsingNode.js,hedevelopedtheAkashaCMSstaticwebsitegenerator.

Iwishtothankmymother,Evelyn,foreverything;myfather,Jim;mysister,Patti;mybrother,Ken;mypartnerMaggieforbeingthereandencouragingme,andthemanyyearsweexpecttohavewitheachother.IwishtothankDr.KubotaoftheUniversityofKentuckyforbelievinginme,givingmemyfirstcomputingjob,andoverseeing6yearsoflearningtheartofcomputersystemmaintenance.IamgratefultoRyanDahl,thecreatorofNode.js,andthecurrentNode.jscoreteammembers.Someplatformsarejustplainhardtoworkwith,butnotNode.js.

Page 12: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

WhatthisbookcoversChapter1,AboutNode.js,introducesyoutotheNode.jsplatform.Itcoversitsuses,thetechnologicalarchitecturechoicesinNode.js,itshistory,thehistoryofserver-sideJavaScript,whyJavaScriptshouldbeliberatedfromthebrowser,andimportantrecentadvancesintheJavaScriptscene.

Chapter2,SettingupNode.js,goesoversettingupaNode.jsdeveloperenvironment.ThisincludesinstallingNode.jsonWindows,macOS,andLinux.Importanttoolsarecovered,includingthenpmandyarnpackagemanagementsystemsandBabel,whichisusedfortranspilingmodernJavaScriptintoaformthat'srunnableonolderJavaScriptimplementations.

Chapter3,Node.jsModules,exploresthemoduleastheunitofmodularityinNode.jsapplications.WedivedeepintounderstandinganddevelopingNode.jsmodulesandusingnpmtomaintaindependencies.Welearnaboutthenewmoduleformat,ES6Modules,thatshouldsupplanttheCommonJSmoduleformatcurrentlyusedinNode.js,andarenativelysupportedinNode.js10.x.

Chapter4,HTTPServersandClients,startsexploringwebdevelopmentwithNode.js.WedevelopseveralsmallwebserverandclientapplicationsinNode.js.WeusetheFibonaccialgorithmtoexploretheeffectsofheavy-weight,long-runningcomputationsonaNode.jsapplication.Wealsolearnseveralmitigationstrategies,andhaveourfirstexperiencewithdevelopingRESTservices.

Chapter5,YourFirstExpressApplication,beginsthesectionondevelopinganote-takingapplication.Thefirststepisgettingabasicapplicationrunning.

Chapter6,ImplementingtheMobile-FirstParadigm,usesBootstrapV4to

Page 13: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

implementresponsivewebdesign.Wetakealookatintegratingapopulariconsetsothatwecanhavepictorialbuttons,andgoovercompilingacustomBootstraptheme.

Chapter7,DataStorageandRetrieval,ensuresthatwedon'tloseournoteswhenwerestarttheapplication.Weexploreseveraldatabaseenginesandamethodtoenableeasilyswitchingbetweenthematwill.

Chapter8,MultiuserAuthenticationtheMicroserviceWay,addsuserauthenticationtothenote-takingapplication.Bothlogged-inandanonymoususerscanaccesstheapplication,withvaryingcapabilitiesbasedonrole.AuthenticationissupportedbothforlocallystoredcredentialsandforusingOAuthagainstTwitter.

Chapter9,DynamicClient/ServerInteractionwithSocket.IO,letsouruserstalkwitheachotherinrealtime.JavaScriptcodewillruninboththebrowserandtheserver,withSocket.IOprovidingtheplumbingneededforreal-timeeventexchange.Userswillseenoteschangeasthey'reeditedbyotherusersandcanleavemessages/commentsforothers.

Chapter10,DeployingNode.jsApplications,helpsusunderstandNode.jsapplicationdeployment.WelookatbothtraditionalLinuxservicedeploymentusinganetcinitscriptandusingDockerforbothlocaldevelopmentanddeploymentoncloudhostingservices.

Chapter11,UnitTestingandFunctionalTesting,takesalookatthreetestdevelopmentmodels:unittesting,RESTtesting,andfunctionaltesting.We'llusethepopularMochaandChaiframeworksforthefirsttwo,andPuppeteerforthethird.PuppeteerusesaheadlessversionofChrometosupportrunningtests.Dockerisusedtofacilitatesettingupandtearingdowntestenvironments.

Chapter12,Security,explorestechniquesandtoolsrequiredtomitigatetheriskofsecurityintrusions.IntelligentlyusingDockerisagreatfirststepifonlybecauseitcaneasilylimittheattacksurfaceofyourapplication.TheNode.jscommunityhasdevelopedasuiteoftoolsthatintegratewithExpresstoimplementseveralcriticalsecuritytechnologies.

Page 14: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

AboutthereviewerNicholasDuffyhashadawide-rangingcareer,holdingpositionsfromanalysttobusinessintelligencearchitect,tosoftwareengineer,andevengolfprofessional.Hehasapassionforallthingsdataandsoftwareengineering,specializingincloudarchitecture,Python,andNode.js.HeisafrequentcontributortoopensourceprojectsandisalsoalifelongNewYorkMetsfan.

I'dliketothankmywife,Anne,andourboys,JackandChuck,fortheirneverending-supportinwhateverendeavorIpursue.

Page 15: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

PacktissearchingforauthorslikeyouIfyou'reinterestedinbecominganauthorforPackt,pleasevisitauthors.packtpub.comandapplytoday.Wehaveworkedwiththousandsofdevelopersandtechprofessionals,justlikeyou,tohelpthemsharetheirinsightwiththeglobaltechcommunity.Youcanmakeageneralapplication,applyforaspecifichottopicthatwearerecruitinganauthorfor,orsubmityourownidea.

Page 16: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TableofContentsTitlePage

CopyrightandCredits

Node.jsWebDevelopment

FourthEdition

Dedication

PacktUpsell

Whysubscribe?

PacktPub.com

Contributors

Abouttheauthor

Aboutthereviewer

Packtissearchingforauthorslikeyou

Preface

Whothisbookisfor

Whatthisbookcovers

Togetthemostoutofthisbook

Downloadtheexamplecodefiles

Conventionsused

Page 17: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Getintouch

Reviews

1. AboutNode.js

ThecapabilitiesofNode.js

Server-sideJavaScript

WhyshouldyouuseNode.js?

Popularity

JavaScriptatalllevelsofthestack

LeveragingGoogle'sinvestmentinV8

Leaner,asynchronous, event-drivenmodel

Microservicearchitecture

Node.jsisstrongerforhavingsurvivedamajorschisma

ndhostilefork

Threadedversusevent-drivenarchitecture

Performanceandutilization

IsNode.jsacancerousscalabilitydisaster?

Serverutilization,thebusinessbottomline,andgreen

webhosting

EmbracingadvancesintheJavaScriptlanguage

DeployingES2015/2016/2017/2018JavaScriptcode

Page 18: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Node.js,themicroservicearchitecture,andeasilytestablesyste

ms

Node.jsandtheTwelve-Factorappmodel

Summary

2. SettingupNode.js

Systemrequirements

InstallingNode.jsusingpackagemanagers

InstallingonmacOSwithMacPorts

InstallingonmacOSwithHomebrew

InstallingonLinux,*BSD,orWindowsfrompackagemanag

ementsystems

InstallingNode.jsintheWindowsSubsystemfor

Linux(WSL)

Openinganadministrator-privilegedPowerShell

onWindows

InstallingtheNode.jsdistributionfromnodejs.org

InstallingfromsourceonPOSIX-likesystems

Installingprerequisites

InstallingdevelopertoolsonmacOS

InstallingfromsourceforallPOSIX-likesystems

InstallingfromsourceonWindows

InstallingmultipleNode.jsinstanceswithnvm

InstallingnvmonWindows

Page 19: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Nativecodemodulesandnode-gyp

Node.jsversionspolicyandwhattouse

Editorsanddebuggers

Runningandtestingcommands

Node.js'scommand-linetools

RunningasimplescriptwithNode.js

ConversiontoasyncfunctionsandthePromiseparadigm

LaunchingaserverwithNode.js

NPM –theNode.jspackagemanager

Node.js,ECMAScript2015/2016/2017,andbeyond 

UsingBabeltouseexperimentalJavaScriptfeatures

Summary

3. Node.jsModules

Definingamodule

CommonJSandES2015moduleformats

CommonJS/Node.jsmoduleformat

ES6moduleformat

JSONmodules

SupportingES6modulesonolderNode.jsversion

s

Demonstratingmodule-levelencapsulation

Page 20: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

FindingandloadingCommonJSandJSONmodulesusingrequire

Filemodules

ModulesbakedintoNode.jsbinary

Directoriesasmodules

Moduleidentifiersandpathnames

Anexampleofapplicationdirectorystructure

FindingandloadingES6modulesusingimport

HybridCommonJS/Node.js/ES6modulescenarios

Dynamicimportswithimport()

Theimport.metafeature

npm-theNode.jspackagemanagementsystem

Thenpmpackageformat

Findingnpmpackages

Othernpmcommands

Installingannpmpackage

Installingapackagebyversionnumber

Globalpackageinstalls

Avoidingglobalmoduleinstallation

Maintainingpackagedependencieswithnpm

Automaticallyupdatingpackage.jsondependencie

s

Fixingbugsbyupdatingpackagedependencies

Page 21: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Packagesthatinstallcommands

ConfiguringthePATHvariabletohandlecommand

sinstalledbymodules

ConfiguringthePATHvariableonWindows

AvoidingmodificationstothePATHvariable

Updatingoutdatedpackagesyou'veinstalled

Installingpackagesfromoutsidethenpmrepository

Initializinganewnpmpackage

DeclaringNode.jsversioncompatibility

Publishingannpmpackage

Explicitlyspecifyingpackagedependencyversionnumbers

TheYarnpackagemanagementsystem

Summary

4. HTTPServersandClients

SendingandreceivingeventswithEventEmitters

JavaScriptclassesandclassinheritance

TheEventEmitterClass

TheEventEmittertheory

HTTPserverapplications

ES2015multilineandtemplatestrings

HTTPSniffer–listeningtotheHTTPconversation

Webapplicationframeworks

GettingstartedwithExpress

Page 22: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SettingenvironmentvariablesinWindowscmd.execommand

line

WalkingthroughthedefaultExpressapplication

TheExpressmiddleware

Middlewareandrequestpaths

Errorhandling

CalculatingtheFibonaccisequencewithanExpressapplication

Computationallyintensivecodeandthe Node.jseven

tloop

Algorithmicrefactoring

MakingHTTPClientrequests

CallingaRESTbackendservicefromanExpressapplication

ImplementingasimpleRESTserver withExpress

RefactoringtheFibonacciapplicationforREST

SomeRESTfulmodulesandframeworks

Summary

5. YourFirstExpressApplication

Promises,asyncfunctions,andExpressrouterfunctions

Promisesanderrorhandling

Flatteningourasynchronouscode

Promisesandgeneratorsbirthedasyncfunctions

ExpressandtheMVCparadigm

Page 23: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

CreatingtheNotesapplication

YourfirstNotesmodel

UnderstandingES-2015classdefinitions

Fillingoutthein-memoryNotesmodel

TheNoteshomepage

Addinganewnote–create

Viewingnotes–read

Editinganexistingnote–update

Deletingnotes–destroy

ThemingyourExpressapplication

Scalingup–runningmultipleNotesinstances

Summary

6. ImplementingtheMobile-FirstParadigm

Problem–theNotesappisn'tmobilefriendly

Mobile-firstparadigm

UsingTwitterBootstrapontheNotesapplication

Settingitup

AddingBootstraptoapplicationtemplates

Alternativelayoutframeworks

FlexboxandCSSGrids

Mobile-firstdesignfortheNotesapplication

LayingtheBootstrapgridfoundation

Page 24: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ResponsivepagestructurefortheNotesapplication

Usingiconlibrariesandimprovingvisualappeal

Responsivepageheadernavigationbar

ImprovingtheNoteslistonthefrontpage

CleaninguptheNoteviewingexperience

Cleaninguptheadd/editnoteform

Cleaningupthedelete-notewindow

BuildingacustomizedBootstrap

Pre-builtcustomBootstrapthemes

Summary

7. DataStorageandRetrieval

Datastorageandasynchronouscode

Logging

RequestloggingwithMorgan

Debuggingmessages

Capturingstdoutandstderr

Uncaughtexceptions

UnhandledPromiserejections

UsingtheES6moduleformat

Rewritingapp.jsasanES6module

Rewritingbin/wwwasanES6module

RewritingmodelscodeasES6modules

Page 25: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

RewritingroutermodulesasES6modules

Storingnotesinthefilesystem

DynamicimportofES6modules

RunningtheNotesapplicationwithfilesystemstorage

StoringnoteswiththeLevelUPdatastore

StoringnotesinSQLwithSQLite3

SQLite3databaseschema

SQLite3modelcode

RunningNoteswithSQLite3

StoringnotestheORMwaywithSequelize

SequelizemodelfortheNotesapplication

ConfiguringaSequelizedatabaseconnection

RunningtheNotesapplicationwithSequelize

StoringnotesinMongoDB

MongoDBmodelfortheNotesapplication

RunningtheNotesapplicationwithMongoDB

Summary

8. MultiuserAuthenticationtheMicroserviceWay

Creatingauserinformationmicroservice

Userinformationmodel

Page 26: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ARESTserverforuserinformation

Scriptstotestandadministertheuserauthentications

erver

LoginsupportfortheNotesapplication

AccessingtheuserauthenticationRESTAPI

Loginandlogoutroutingfunctions

Login/logoutchangestoapp.js

Login/logoutchangesinroutes/index.mjs

Login/logoutchangesrequiredinroutes/notes.m

js

Viewtemplatechangessupportinglogin/logout

RunningtheNotesapplicationwithuserauthent

ication

TwitterloginsupportfortheNotesapplication

RegisteringanapplicationwithTwitter

ImplementingTwitterStrategy

Securelykeepingsecretsandpasswords

TheNotesapplicationstack

Summary

9. DynamicClient/ServerInteractionwithSocket.IO

IntroducingSocket.IO

InitializingSocket.IOwithExpress

Real-timeupdatesontheNoteshomepage

TheNotesmodelasanEventEmitterclass

Page 27: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Real-timechangesintheNoteshomepage

Changingthehomepageandlayouttemplates

RunningNoteswithreal-timehomepageupdates

Real-timeactionwhileviewingnotes

Changingthenoteviewtemplateforreal-timea

ction

RunningNoteswithreal-timeupdateswhileview

inganote

Inter-userchatandcommentingforNotes

Datamodelforstoringmessages

AddingmessagestotheNotesrouter

Changingthenoteviewtemplateformessages

UsingaModalwindowtocomposemessages

Sending,displaying,anddeletingmessages

RunningNotesandpassingmessages

OtherapplicationsofModalwindows

Summary

10. DeployingNode.jsApplications

Notesapplicationarchitectureanddeploymentconsiderations

TraditionalLinuxNode.jsservicedeployment

Prerequisite–provisioningthedatabases

InstallingNode.jsonUbuntu

Page 28: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SettingupNotesanduserauthentication ontheser

ver

AdjustingTwitterauthenticationtoworkonthe

server

SettingupPM2tomanageNode.jsprocesses

Node.jsmicroservicedeployment withDocker

InstallingDockeronyourlaptop

StartingDockerwithDockerforWindows/macOS

KickingthetiresofDocker

CreatingtheAuthNetfortheuserauthenticationservice

MySQLcontainerforDocker

InitializingAuthNet

ScriptexecutiononWindows

LinkingDockercontainers

Thedb-userauthcontainer

Dockerfilefortheauthenticationservice

ConfiguringtheauthenticationserviceforDock

er

Buildingandrunningtheauthenticationservice

Dockercontainer

ExploringAuthnet

CreatingFrontNetfortheNotesapplication

MySQLcontainerfortheNotesapplication

DockerizingtheNotesapplication

Page 29: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ControllingthelocationofMySQLdatavolumes

Dockerdeploymentofbackgroundservices

DeployingtothecloudwithDockercompose

Dockercomposefiles

RunningtheNotesapplicationwithDockercompo

se

DeployingtocloudhostingwithDockercompose

Summary

11. UnitTestingandFunctionalTesting

Assert–thebasisoftestingmethodologies

TestingaNotesmodel

MochaandChai­–thechosentesttools

Notesmodeltestsuite

Configuringandrunningtests

MoretestsfortheNotesmodel

Testingdatabasemodels

UsingDockertomanagetestinfrastructure

DockerComposetoorchestratetestinfrastructure

ExecutingtestsunderDockerCompose

MongoDBsetupunderDockerandtestingNotesag

ainstMongoDB

Page 30: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TestingRESTbackendservices

Automatingtestresultsreporting

Frontendheadlessbrowsertesting withPuppeteer

SettingupPuppeteer

ImprovingtestabilityintheNotesUI

PuppeteertestscriptforNotes

Runningtheloginscenario

TheAddNotescenario

Mitigating/preventingspurioustesterrorsinPuppeteer

scripts

Configuringtimeouts

TracingeventsonthePageandthePuppeteerin

stance

Insertingpauses

AvoidingWebSocketsconflicts

Takingscreenshots

Summary

12. Security

HTTPS/TLS/SSLusingLet'sEncrypt

AssociatingadomainnamewithDocker-basedcloudhostin

g

ADockercontainertomanageLet'sEncryptSSLcertifica

tes

Cross-containermountingofLet'sEncryptdirectoriesto

thenotescontainer

Page 31: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

AddingHTTPSsupporttoNotes

PutonyourHelmetforacross-the-boardsecurity

UsingHelmettosettheContent-Security-Policyheader

UsingHelmettosettheX-DNS-Prefetch-Controlheader

UsingHelmettosettheX-Frame-Optionsheader

UsingHelmettoremovetheX-Powered-Byheader

ImprovingHTTPSwithStrictTransportSecurity

MitigatingXSSattackswithHelmet

AddressingCross-SiteRequestForgery(CSRF)attacks

DenyingSQLinjectionattacks

Sequelizedeprecationwarningregardingoperatorinjecti

onattack

Scanningforknownvulnerabilities

Usinggoodcookiepractices

Summary

OtherBooksYouMayEnjoy

Leaveareview-letotherreadersknowwhatyouthink

Page 32: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

PrefaceNode.jsisaserver-sideJavaScriptplatformusinganevent-driven,non-blockingI/Omodel,allowinguserstobuildfastandscalabletransaction-intensiveapplicationsrunninginrealtime.ItplaysasignificantroleinthesoftwaredevelopmentworldandliberatesJavaScriptfromthewebbrowser.WithNode.js,wecanreuseourJavaScriptskillsforgeneralsoftwaredevelopmentonalargerangeofsystems.

Itrunsatoptheultra-fastJavaScriptengineattheheartofGoogle'sChromebrowser,V8,andaddsafastandrobustlibraryofasynchronousnetworkI/Omodules.

TheprimaryfocusofNode.jsisdevelopinghighperformance,highlyscalablewebapplications,anditalsoseesawidespreaduseinotherareas.Electron,theNode.js-basedwrapperaroundtheChromeengine,isthebasisforpopulardesktopapplications,suchasAtomandVisualStudioCodeeditors,GitKraken,Postman,Etcher,andthedesktopSlackclient.Node.jsispopularfordevelopingInternetofThingsdevicesandseesatremendousadoptioninmicroservicedevelopmentandforbuildingtoolsforfrontendwebdevelopersandmore.Node.js,asalightweighthigh-performanceplatform,fitsmicroservicedevelopmentlikeaglove.

TheNode.jsplatformusesanasynchronoussingle-threadsystem,withasynchronouslyinvokedcallbackfunctions(alsoknownaseventhandlers)andaneventloop,asopposedtoatraditionalthread-basedarchitecture.

Thetheoryisthatthreadedsystemsarenotoriouslydifficulttodevelop,andthatthreadsthemselvesimposeanarchitecturalburdenonappservers.Node.js'sgoalistoprovideaneasywaytobuildscalablenetworkservers.

ThewholeNode.jsruntimeisdesignedaroundasynchronousexecution.

Page 33: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

JavaScriptwaschosenasthelanguagebecauseanonymousfunctionsandotherlanguageelementsprovideanexcellentbaseforimplementingasynchronouscomputation.

Page 34: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

WhothisbookisforWeassumethatyouhavesomeknowledgeofJavaScriptandpossiblyhaveexperiencewithserver-sidecodedevelopment,andthatyouarelookingforadifferentwayofdevelopingserver-sidecode.

Server-sideengineersmayfindtheconceptsbehindNode.jsrefreshing.Itoffersanewperspectiveonwebapplicationdevelopmentandadifferenttakeonserverarchitecturesfromthemonolithswedealwithinotherprogramminglanguages.JavaScriptisapowerfullanguageandNode.js'sasynchronousnatureplaystoitsstrengths.HavingJavaScriptonboththefrontendandthebackendgivesawholenewmeaning.

Developersexperiencedwithbrowser-sideJavaScriptwillfinditproductivetobringthatknowledgetoanewterritory.

Althoughourfocusisonwebapplicationdevelopment,Node.jsknowledgecanbeappliedinotherareasaswell.Assaidearlier,Node.jsiswidelyusedtodevelopmanytypesofapplications.

Page 35: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TogetthemostoutofthisbookThebasicrequirementistoinstallNode.jsandhaveaprogrammer-orientedtexteditor.Theeditorneednotbeanythingfancy,vi/vimwillevendoinapinch.Wewillshowyouhowtoinstalleverythingthat'sneeded.It'sallopensourcesoftwarethatcanbeeasilydownloadedfromwebsites.

Themostimportanttoolistheonebetweenyourears.

Somechaptersrequiredatabaseengines,suchasMySQLandMongoDB.

AlthoughNode.jsisacross-platformsoftwaredevelopmentplatform,somethird-partymodulesarewritteninC/C++andmustbecompiledduringinstallation.Todoso,native-codedevelopmenttoolssuchasC/C++compilersarerequired,andPythonisrequiredtorunthetool-chain.ThedetailsarecoveredinChapter2,SettingupNode.js.MicrosoftisinvolvedwiththeNode.jsprojectandtoensuredeveloperproductivitywithNode.jsonWindows.

Page 36: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

DownloadtheexamplecodefilesYoucandownloadtheexamplecodefilesforthisbookfromyouraccountatwww.packtpub.com.Ifyoupurchasedthisbookelsewhere,youcanvisitwww.packtpub.com/supportandregistertohavethefilesemaileddirectlytoyou.

Youcandownloadthecodefilesbyfollowingthesesteps:

1. Loginorregisteratwww.packtpub.com.2. SelecttheSUPPORTtab.3. ClickonCodeDownloads&Errata.4. EnterthenameofthebookintheSearchboxandfollowthe

onscreeninstructions.

Oncethefileisdownloaded,pleasemakesurethatyouunziporextractthefolderusingthelatestversionof:

WinRAR/7-ZipforWindows

Zipeg/iZip/UnRarXforMac

7-Zip/PeaZipforLinux

ThecodebundleforthebookisalsohostedonGitHubathttps://github.com/PacktPublishing/Node.js-Web-Development-Fourth-Edition.Wealsohaveothercodebundlesfromourrichcatalogofbooksandvideosavailableathttps://github.com/PacktPublishing/.Checkthemout!

Page 37: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you
Page 38: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ConventionsusedThereareanumberoftextconventionsusedthroughoutthisbook.

CodeInText:Indicatescodewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandles.Hereisanexample:"ThehttpobjectencapsulatestheHTTPprotocol,anditshttp.createServermethodcreatesawholewebserver,listeningontheportspecifiedinthelistenmethod."

Ablockofcodeissetasfollows:

varhttp=require('http');

http.createServer(function(req,res){

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

res.end('HelloWorld\n');

}).listen(8124,"127.0.0.1");

console.log('Serverrunningathttp://127.0.0.1:8124/');

Whenwewishtodrawyourattentiontoaparticularpartofacodeblock,therelevantlinesoritemsaresetinbold:

if(urlP.query['n']){

fibonacciAsync(urlP.query['n'],fibo=>{

res.end('Fibonacci'+urlP.query['n']+'='+fibo);

});

}else{

Anycommand-lineinputoroutputiswrittenasfollows:

$node--version

v8.9.1

Bold:Indicatesanewterm,animportantword,orwordsthatyousee

Page 39: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

onscreen.Forexample,wordsinmenusordialogboxesappearinthetextlikethis.Hereisanexample:"IntheStartmenu,enterPowerShellintheapplicationssearchbox."

Warningsorimportantnotesappearlikethis.

Tipsandtricksappearlikethis.

Page 40: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

GetintouchFeedbackfromourreadersisalwayswelcome.

Generalfeedback:Emailfeedback@packtpub.comandmentionthebooktitleinthesubjectofyourmessage.Ifyouhavequestionsaboutanyaspectofthisbook,[email protected].

Errata:Althoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyouhavefoundamistakeinthisbook,wewouldbegratefulifyouwouldreportthistous.Pleasevisitwww.packtpub.com/submit-errata,selectingyourbook,clickingontheErrataSubmissionFormlink,andenteringthedetails.

Piracy:IfyoucomeacrossanyillegalcopiesofourworksinanyformontheInternet,wewouldbegratefulifyouwouldprovideuswiththelocationaddressorwebsitename.Pleasecontactusatcopyright@packtpub.comwithalinktothematerial.

Ifyouareinterestedinbecominganauthor:Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,pleasevisitauthors.packtpub.com.

Page 41: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ReviewsPleaseleaveareview.Onceyouhavereadandusedthisbook,whynotleaveareviewonthesitethatyoupurchaseditfrom?Potentialreaderscanthenseeanduseyourunbiasedopiniontomakepurchasedecisions,weatPacktcanunderstandwhatyouthinkaboutourproducts,andourauthorscanseeyourfeedbackontheirbook.Thankyou!

FormoreinformationaboutPackt,pleasevisitpacktpub.com.

Page 42: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

AboutNode.jsNode.jsisanexcitingnewplatformfordevelopingwebapplications,applicationservers,anysortofnetworkserverorclient,andgeneralpurposeprogramming.Itisdesignedforextremescalabilityinnetworkedapplicationsthroughaningeniouscombinationofserver-sideJavaScript,asynchronousI/O,andasynchronousprogramming.ItisbuiltaroundJavaScriptanonymousfunctions,andasingleexecutionthreadevent-drivenarchitecture.

Whileonlyafewyearsold,Node.jshasquicklygrowninprominenceandit'snowplayingasignificantrole.Companies,bothsmallandlarge,areusingitforlarge-scaleandsmall-scaleprojects.PayPal,forexample,hasconvertedmanyservicesfromJavatoNode.js.

TheNode.jsarchitecturedepartsfromatypicalchoicemadebyotherapplicationplatforms.WherethreadsarewidelyusedtoscaleanapplicationtofilltheCPU,Node.jseschewsthreadsbecauseoftheirinherentcomplexity.It'sclaimedthatwithsingle-threadevent-drivenarchitectures,memoryfootprintislow,throughputishigh,thelatencyprofileunderloadisbetter,andtheprogrammingmodelissimpler.TheNode.jsplatformisinaphaseofrapidgrowth,andmanyareseeingitasacompellingalternativetothetraditionalwebapplicationarchitecturesusingJava,PHP,Python,orRubyonRails.

Atitsheart,itisastandaloneJavaScriptenginewithextensionsmakingitsuitableforgeneralpurposeprogrammingandwithaclearfocusonapplicationserverdevelopment.Eventhoughwe'recomparingNode.jstoapplicationserverplatforms,itisnotanapplicationserver.Instead,Node.jsisaprogrammingrun-timeakintoPython,Go,orJavaSE.WhiletherearewebapplicationframeworksandapplicationserverswritteninNode.js,itissimplyasystemtoexecuteJavaScriptprograms.

Page 43: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Itisimplementedaroundanon-blockingI/OeventloopandalayeroffileandnetworkI/Olibraries,allbuiltontopoftheV8JavaScriptengine(fromtheChromewebbrowser).TherapidperformanceandfeatureimprovementsimplementedinChromequicklyflowthroughtotheNode.jsplatform.Additionally,ateamoffolksareworkingonaNode.jsimplementationthatrunsontopofMicrosoft'sChakraCoreJavaScriptengine(fromtheEdgewebbrowser).ThatwouldgivetheNode.jscommunitygreaterflexibilitybynotbeingreliantononeJavaScriptengineprovider.Visithttps://github.com/nodejs/node-chakracoretotakealookattheproject.

TheNode.jsI/OlibraryisgeneralenoughtoimplementanysortofserverexecutinganyTCPorUDPprotocol,whetherit'sdomainnamesystem(DNS),HTTP,internetrelaychat(IRC),orFTP.Whileitsupportsdevelopinginternetserversorclients,itsbiggestusecaseisinregularwebsites,inplaceoftechnologysuchasanApache/PHPorRailsstack,ortocomplementexistingwebsites.Forexample,addingreal-timechatormonitoringexistingwebsitescanbeeasilydonewiththeSocket.IOlibraryforNode.js.Itslightweight,high-performancenatureoftenseesNode.jsusedasaglueservice.

AparticularlyintriguingcombinationisdeployingsmallservicesusingDockerintocloudhostinginfrastructure.Alargeapplicationcanbedividedintowhat'snowcalledmicroservicesthatareeasilydeployedatscaleusingDocker.TheresultfitsagileprojectmanagementmethodssinceeachmicroservicecanbeeasilymanagedbyasmallteamthatcollaboratesattheboundaryoftheirindividualAPI.

ThisbookwillgiveyouanintroductiontoNode.js.Wepresumethefollowing:

Youalreadyknowhowtowritesoftware

YouarefamiliarwithJavaScript

Youknowsomethingaboutdevelopingwebapplicationsinotherlanguages

Page 44: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Wewillcoverthefollowingtopicsinthischapter:

AnintroductiontoNode.js

WhyyoushoulduseNode.js

ThearchitectureofNode.js

Performance,utilization,andscalabilitywithNode.js

Node.js,microservicearchitecture,andtesting

ImplementingtheTwelve-FactorAppmodelwithNode.js

Wewilldiverightintodevelopingworkingapplicationsandrecognizethatoftenthebestwaytolearnisbyrummagingaroundinworkingcode.

Page 45: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ThecapabilitiesofNode.jsNode.jsisaplatformforwritingJavaScriptapplicationsoutsidewebbrowsers.ThisisnottheJavaScriptwearefamiliarwithinwebbrowsers!Forexample,thereisnoDOMbuiltintoNode.js,noranyotherbrowsercapability.

BeyonditsnativeabilitytoexecuteJavaScript,thebundledmodulesprovidecapabilitiesofthissort:

Command-linetools(inshellscriptstyle)

Aninteractive-terminalstyleofprogramthatisRead-Eval-PrintLoop(REPL)

Excellentprocesscontrolfunctionstooverseechildprocesses

Abufferobjecttodealwithbinarydata

TCPorUDPsocketswithcomprehensiveevent-drivencallbacks

DNSlookup

AnHTTP,HTTPSandHTTP/2client/serverlayeredontopoftheTCPlibraryfilesystemaccess

Built-inrudimentaryunittestingsupportthroughassertions

ThenetworklayerofNode.jsislowlevelwhilebeingsimpletouse.Forexample,theHTTPmodulesallowyoutowriteanHTTPserver(orclient)usingafewlinesofcode.Thisispowerful,butitputsyou,theprogrammer,veryclosetotheprotocolrequestsandmakesyouimplementpreciselythoseHTTPheadersthatyoushouldreturninrequestresponses.

Page 46: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Typicalwebapplicationdevelopersdon'tneedtoworkatalowleveloftheHTTPorotherprotocols.Instead,wetendtobemoreproductive,workingwithhigher-levelinterfaces.Forexample,PHPcodersassumethatApache(orotherHTTPservers)isalreadythereprovidingtheHTTPprotocol,andthattheydon'thavetoimplementtheHTTPserverportionofthestack.Bycontrast,aNode.jsprogrammerdoesimplementanHTTPservertowhichtheirapplicationcodeisattached.

Tosimplifythesituation,theNode.jscommunityhasseveralwebapplicationframeworks,suchasExpress,providingthehigher-levelinterfacesrequiredbytypicalprogrammers.YoucanquicklyconfigureanHTTPserverwithbaked-incapabilitiessuchassessions,cookies,servingstaticfiles,andlogging,lettingdevelopersfocusontheirbusinesslogic.OtherframeworksprovideOAuth2support,orfocusonRESTAPIs,andsoon.

Node.jsisnotlimitedtowebserviceapplicationdevelopment.ThecommunityaroundNode.jshastakenitinmanyotherdirections,

Buildtools:Node.jshasbecomeapopularchoicefordevelopingcommand-linetoolsusedinsoftwaredevelopment,orcommunicatingwithserviceinfrastructure.GruntandGulparewidelyusedbyfrontenddeveloperstobuildassetsforwebsites.BabeliswidelyusedfortranspilingmodernES-2016codetorunonolderbrowsers.PopularCSSoptimizersandprocessors,suchasPostCSS,arewritteninNode.js.StaticwebsitegenerationsystemssuchasMetalsmith,Punch,andAkashaCMS,runatthecommandlineandgeneratewebsitecontentthatyouuploadtoawebserver.

WebUItesting:Puppeteergivesyoucontroloveraheadless-Chromewebbrowserinstance.Withit,youcandevelopNode.jsscriptscontrollingamodernfull-featuredwebbrowser.Typicalusecasesinvolvewebscrapingandtestingwebapplications.

Desktopapplications:BothElectronandnode-webkit(NW.js)areframeworksfordevelopingdesktopapplicationsforWindows,macOS,andLinux.TheseframeworksutilizealargechunkofChrome,wrapped

Page 47: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

byNode.jslibraries,todevelopdesktopapplicationsusingwebUItechnologies.ApplicationsarewrittenwithmodernHTML5,CSS3,andJavaScript,andcanutilizeleading-edgewebframeworks,suchasBootstrap,React,orAngularJS.ManypopularapplicationshavebeenbuiltusingElectron,includingtheSlackdesktopclientapplication,theAtomandMicrosoftVisualCodeprogrammingeditors,thePostmanRESTclient,theGitKrakenGITclient,andEtcher,whichmakesitincrediblyeasytoburnOSimagestoflashdrivestorunonsingle-boardcomputers.

Mobileapplications:TheNode.jsforMobileSystemsprojectletsyoudevelopsmartphoneortabletcomputerapplicationsusingNode.js,forbothiOSandAndroid.Apple'sAppStorerulesprecludeincorporatingaJavaScriptenginewithJITcapabilities,meaningthatnormalNode.jscannotbeusedinaniOSapplication.ForiOSapplicationdevelopment,theprojectusesNode.js-on-ChakraCoretoskirtaroundtheAppStorerules.ForAndroidapplicationdevelopmenttheprojectusesregularNode.jsonAndroid.Atthetimeofwriting,theprojectisinanearlystageofdevelopment,butitlookspromising.

InternetofThings(IoT):Reportedly,itisaverypopularlanguageforInternet-of-Thingsprojects,andNode.jsdoesrunonmostARM-basedsingle-boardcomputers.TheclearestexampleistheNodeREDproject.Itoffersagraphicalprogrammingenvironment,lettingyoudrawprogramsbyconnectingblockstogether.Itfeatureshardware-orientedinputandoutputmechanisms,forexample,tointeractwithGeneralPurposeI/O(GPIO)pinsonRaspberryPiorBeaglebonesingle-boardcomputers.

Page 48: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Server-sideJavaScriptQuitscratchingyourheadalready!Ofcourseyou'redoingit,scratchingyourheadandmumblingtoyourself,"What'sabrowserlanguagedoingontheserver?"Intruth,JavaScripthasalongandlargelyunknownhistoryoutsidethebrowser.JavaScriptisaprogramminglanguage,justlikeanyotherlanguage,andthebetterquestiontoaskis"WhyshouldJavaScriptremaintrappedinsidebrowsers?".

Backinthedawnofthewebage,thetoolsforwritingwebapplicationswereatafledglingstage.SomewereexperimentingwithPerlorTCLtowriteCGIscripts,andthePHPandJavalanguageshadjustbeendeveloped.Eventhen,JavaScriptsawuseontheserverside.OneearlywebapplicationserverwasNetscape'sLiveWireserver,whichusedJavaScript.SomeversionsofMicrosoft'sASPusedJScript,theirversionofJavaScript.Amorerecentserver-sideJavaScriptprojectistheRingoJSapplicationframeworkintheJavauniverse.Java6andJava7werebothshippedwiththeRhinoJavaScriptengine.InJava8,RhinowasdroppedinfavorofthenewerNashornJavaScriptengine.

Inotherwords,JavaScriptoutsidethebrowserisnotanewthing,evenifitisuncommon.

Page 49: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

WhyshouldyouuseNode.js?Amongthemanyavailablewebapplicationdevelopmentplatforms,whyshouldyouchooseNode.js?Therearemanystackstochoosefrom;whatisitaboutNode.jsthatmakesitriseabovetheothers?Wewillseeinthefollowingsections.

Page 50: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

PopularityNode.jsisquicklybecomingapopulardevelopmentplatformwithadoptionbyplentyofbigandsmallplayers.OneofthoseisPayPal,whoarereplacingtheirincumbentJava-basedsystemwithonewritteninNode.js.ForPayPal'sblogpostaboutthis,visithttps://www.paypal-engineering.com/2013/11/22/node-js-at-paypal/.OtherlargeNode.jsadoptersincludeWalmart'sonlinee-commerceplatform,LinkedIn,andeBay.

AccordingtoNodeSource,Node.jsusageisgrowingrapidly(visithttps://nodesource.com/node-by-numbers).ThemeasuresincludeincreasingbandwidthfordownloadingNode.jsreleases,increasingactivityinNode.js-relatedGitHubprojects,andmore.

It'sbesttonotjustfollowthecrowdbecausethecrowdclaimstheirsoftwareplatformdoescoolthings.Node.jsdoessomecoolthings,butmoreimportantisitstechnicalmerit.

Page 51: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

JavaScriptatalllevelsofthestackHavingthesameprogramminglanguageontheserverandclienthasbeenalong-timedreamontheweb.ThisdreamdatesbacktotheearlydaysofJava,whereJavaappletsweretobethefrontendtoserverapplicationswritteninJava,andJavaScriptwasoriginallyenvisionedasalightweightscriptinglanguageforthoseapplets.Javaneverfulfilleditshypeasaclient-sideprogramminglanguage,forvariousreasons.WeendedupwithJavaScriptastheprinciplein-browser,client-sidelanguage,ratherthanJava.Typically,thefrontendJavaScriptdeveloperswereinadifferentlanguageuniversethantheserver-sideteam,whowaslikelytobecodinginPHP,Java,Ruby,orPython.

Overtime,in-browserJavaScriptenginesbecameincrediblypowerful,lettinguswriteever-morecomplexbrowser-sideapplications.WithNode.js,wemayfinallybeabletoimplementapplicationswiththesameprogramminglanguageontheclientandserverbyhavingJavaScriptatbothendsoftheweb,inthebrowserandserver.

Acommonlanguageforfrontendandbackendoffersseveralpotentialbenefits:

Thesameprogrammingstaffcanworkonbothendsofthewire

Codecanbemigratedbetweenserverandclientmoreeasily

Commondataformats(JSON)existbetweenserverandclient

Commonsoftwaretoolsexistforserverandclient

Commontestingorqualityreportingtoolsforserverandclient

Page 52: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Whenwritingwebapplications,viewtemplatescanbeusedonbothsides

TheJavaScriptlanguageisverypopularduetoitsubiquityinwebbrowsers.Itcomparesfavorablyagainstotherlanguageswhilehavingmanymodern,advancedlanguageconcepts.Thankstoitspopularity,thereisadeeptalentpoolofexperiencedJavaScriptprogrammersoutthere.

Page 53: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

LeveragingGoogle'sinvestmentinV8TomakeChromeapopularandexcellentwebbrowser,GoogleinvestedinmakingV8asuper-fastJavaScriptengine.Google,therefore,hasahugemotivationtokeeponimprovingV8.V8istheJavaScriptengineforChrome,anditcanalsobeexecutedstandalone.Node.jsisbuiltontopoftheV8JavaScriptengine.

AsNode.jsbecomesmoreimportanttotheV8team,there'sapotentialsynergyoffasterV8performancewinsasmorepeoplefocusonV8improvements.

Page 54: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Leaner,asynchronous,event-drivenmodelWe'llgetintothislater.TheNode.jsarchitecture,asingleexecutionthread,aningeniousevent-orientedasynchronous-programmingmodel,andafastJavaScriptengine,haslessoverheadthanthread-basedarchitectures.

Page 55: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

MicroservicearchitectureAnewsensationinsoftwaredevelopmentisthemicroserviceidea.Microservicesarefocusedonsplittingalargewebapplicationintosmall,tightly-focusedservicesthatcanbeeasilydevelopedbysmallteams.Whiletheyaren'texactlyanewidea,they'remoreofareframingofoldclient-servercomputingmodels,themicroservicepatternfitswellwithagileprojectmanagementtechniques,andgivesusmoregranularapplicationdeployment.

Node.jsisanexcellentplatformforimplementingmicroservices.We'llgetintothislater.

Page 56: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Node.jsisstrongerforhavingsurvivedamajorschismandhostileforkDuring2014and2015,theNode.jscommunityfacedamajorsplitoverpolicy,direction,andcontrol.Theio.jsprojectwasahostileforkdrivenbyagroupwhowantedtoincorporateseveralfeaturesandchangewho'sinthedecision-makingprocess.TheendresultwasamergeoftheNode.jsandio.jsrepositories,anindependentNode.jsfoundationtoruntheshow,andthecommunityisworkingtogethertomoveforwardinacommondirection.

AconcreteresultofhealingthatriftistherapidadoptionofnewECMAScriptlanguagefeatures.TheV8engineisadoptingthosenewfeaturesquicklytoadvancethestateofwebdevelopment.TheNode.jsteam,inturn,isadoptingthosefeaturesasquicklyastheyshowupinV8,meaningthatPromisesandasyncfunctionsarequicklybecomingarealityforNode.jsprogrammers.

ThebottomlineisthattheNode.jscommunitynotonlysurvivedtheio.jsfork,butthecommunityandtheplatformitnurturesgrewstrongerasaresult.

Page 57: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Threadedversusevent-drivenarchitectureNode.js'sblisteringperformanceissaidtobebecauseofitsasynchronousevent-drivenarchitecture,anditsuseoftheV8JavaScriptengine.That'sanicethingtosay,butwhat'stherationaleforthestatement?

TheV8JavaScriptengineisamongthefastestJavaScriptimplementations.Asaresult,Chromeiswidelyusednotjusttoviewwebsitecontent,buttoruncomplexapplications.ExamplesincludeGmail,theGoogleGSuiteapplications(Docs,Slides,andsoon),imageeditorssuchasPixlr,anddrawingapplicationssuchasdraw.ioandCanva.BothAtomandMicrosoft'sVisualStudioCodeareexcellentIDE'sthatjusthappentobeimplementedinNode.jsandChromeusingElectron.ThattheseapplicationsexistandarehappilyusedbyalargenumberofpeopleistestamenttoV8'sperformance.Node.jsbenefitsfromV8performanceimprovements.

ThenormalapplicationservermodelusesblockingI/Otoretrievedata,anditusesthreadsforconcurrency.BlockingI/Ocausesthreadstowaitonresults.Thatcausesachurnbetweenthreadsastheapplicationserverstartsandstopsthethreadstohandlerequests.Eachsuspendedthread(typicallywaitingonanI/Ooperationtofinish)consumesafullstacktraceofmemory,increasingmemoryconsumptionoverhead.Threadsaddcomplexitytotheapplicationserveraswellasserveroverhead.

Node.jshasasingleexecutionthreadwithnowaitingonI/Oorcontextswitching.Instead,thereisaneventlooplookingforeventsanddispatchingthemtohandlerfunctions.Theparadigmisthatanyoperationthatwouldblockorotherwisetaketimetocompletemustusetheasynchronousmodel.Thesefunctionsaretobegivenananonymousfunctiontoactasahandlercallback,orelse(withtheadventofES2015

Page 58: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

promises),thefunctionwouldreturnaPromise.Thehandlerfunction,orPromise,isinvokedwhentheoperationiscomplete.Inthemeantime,controlreturnstotheeventloop,whichcontinuesdispatchingevents.

AttheNode.jsinteractiveconferencein2017,IBM'sChrisBaileymadeacaseforNode.jsbeinganexcellentchoiceforhighlyscalablemicroservices.KeyperformancecharacteristicsareI/Operformance,measuredintransactionspersecond,startuptime,becausethatlimitshowquicklyyourservicecanscaleuptomeetdemand,andmemoryfootprint,becausethatdetermineshowmanyapplicationinstancescanbedeployedperserver.Node.jsexcelsonallthosemeasures;witheverysubsequentreleaseeach,iseitherimprovingorremainingfairlysteady.BaileypresentedfigurescomparingNode.jstoasimilarbenchmarkwritteninSpringBootshowingNode.jstoperformmuchbetter.Toviewhistalk,seehttps://www.youtube.com/watch?v=Fbhhc4jtGW4.

Tohelpuswrapourheadsaroundwhythiswouldbe,let'sreturntoRyanDahl,thecreatorofNode.js,andthekeyinspirationleadinghimtocreateNode.js.InhisCincodeNodeJSpresentationinMay2010,https://www.youtube.com/watch?v=M-sc73Y-zQA,Dahlaskeduswhathappenswhileexecutingalineofcodesuchasthis:

result=query('SELECT*fromdb');

//operateontheresult

Ofcourse,theprogrampausesatthatpointwhilethedatabaselayersendsthequerytothedatabase,whichdeterminestheresultandreturnsthedata.Dependingonthequery,thatpausecanbequitelong;well,afewmilliseconds,whichisaneonincomputertime.Thispauseisbadbecausethatexecutionthreadcandonothingwhilewaitingfortheresulttoarrive.Ifyoursoftwareisrunningonasingle-threadedplatform,theentireserverwouldbeblockedandunresponsive.Ifinstead,yourapplicationisrunningonathread-basedserverplatform,athreadcontextswitchisrequiredtosatisfyanyotherrequeststhatarrive.Thegreaterthenumberofoutstandingconnectionstotheserver,thegreaterthenumberofthreadcontextswitches.ContextswitchingisnotfreebecausemorethreadsrequiremorememoryperthreadstateandmoretimefortheCPUtospend

Page 59: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

onthreadmanagementoverhead.

Simplyusinganasynchronous,event-drivenI/O,Node.jsremovesmostofthisoverheadwhileintroducingverylittleofitsown.

Usingthreadstoimplementconcurrencyoftencomeswithadmonitionssuchasthese:expensiveanderror-prone,theerror-pronesynchronizationprimitivesofJava,ordesigningconcurrentsoftwarecanbecomplexanderrorprone.Thecomplexitycomesfromtheaccesstosharedvariablesandvariousstrategiestoavoiddeadlockandcompetitionbetweenthreads.ThesynchronizationprimitivesofJavaareanexampleofsuchastrategy,andobviouslymanyprogrammersfindthemdifficulttouse.There'sthetendencytocreateframeworkssuchasjava.util.concurrenttotamethecomplexityofthreadedconcurrency,butsomemightarguethatpaperingovercomplexitydoesnotmakethingssimpler.

Node.jsasksustothinkdifferentlyaboutconcurrency.Callbacksfiredasynchronouslyfromaneventloopareamuchsimplerconcurrencymodel—simplertounderstand,simplertoimplement,simplertoreasonabout,andsimplertodebugandmaintain.

RyanDahlpointstotherelativeaccesstimeofobjectstounderstandtheneedforasynchronousI/O.Objectsinmemoryaremorequicklyaccessed(intheorderofnanoseconds)thanobjectsondiskorobjectsretrievedoverthenetwork(millisecondsorseconds).Thelongeraccesstimeforexternalobjectsismeasuredinzillionsofclockcycles,whichcanbeaneternitywhenyourcustomerissittingattheirwebbrowserreadytomoveonifittakeslongerthantwosecondstoloadthepage.

InNode.js,thequerydiscussedpreviouslywillreadasfollows:

query('SELECT*fromdb',function(err,result){

if(err)throwerr;//handleerrors

//operateonresult

});

Theprogrammersuppliesafunctionthatiscalled(hencethe

Page 60: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

namecallbackfunction)whentheresult(orerror)isavailable.Insteadofathreadcontextswitch,thiscodereturnsalmostimmediatelytotheeventloop.Thateventloopisfreetohandleotherrequests.TheNode.jsruntimekeepstrackofthestackcontextleadingtothiscallbackfunction,andeventuallyaneventwillfirecausingthiscallbackfunctiontobecalled.

AdvancesintheJavaScriptlanguagearegivingusnewoptionstoimplementthisidea.TheequivalentcodelookslikesowhenusedwithES2015Promise's:

query('SELECT*fromdb')

.then(result=>{

//operateonresult

})

.catch(err=>{

//handleerrors

});

ThefollowingwithanES-2017asyncfunction:

try{

varresult=awaitquery('SELECT*fromdb');

//operateonresult

}catch(err){

//handleerrors

}

Allthreeofthesecodesnippetsperformthesamequerywrittenearlier.Thedifferenceisthatthequerydoesnotblocktheexecutionthread,becausecontrolpassesbacktotheeventloop.Byreturningalmostimmediatelytotheeventloop,itisfreetoserviceotherrequests.Eventually,oneofthoseeventswillbetheresponsetothequeryshownpreviously,whichwillinvokethecallbackfunction.

WiththecallbackorPromiseapproach,theresultisnotreturnedastheresultofthefunctioncall,butisprovidedtoacallbackfunctionthatwillbecalledlater.Theorderofexecutionisnotonelineafteranother,asitisinsynchronousprogramminglanguages.Instead,theorderofexecutionisdeterminedbytheorderofthecallbackfunctionexecution.

Page 61: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Whenusinganasyncfunction,thecodingstyleLOOKSliketheoriginalsynchronouscodeexample.Theresultisreturnedastheresultofthefunctioncall,anderrorsarehandledinanaturalmannerusingtry/catch.Theawaitkeywordintegratesasynchronousresultshandlingwithoutblockingtheexecutionthread.Alotisburiedunderthecoversoftheasync/awaitfeature,andwe'llbecoveringthismodelextensivelythroughoutthebook.

Commonly,webpagesbringtogetherdatafromdozensofsources.Eachonehasaqueryandresponseasdiscussedearlier.Usingasynchronousqueries,eachquerycanhappeninparallel,wherethepageconstructionfunctioncanfireoffdozensofqueries—nowaiting,eachwiththeirowncallback—andthengobacktotheeventloop,invokingthecallbacksaseachisdone.Becauseit'sinparallel,thedatacanbecollectedmuchmorequicklythanifthesequeriesweredonesynchronouslyoneatatime.Now,thereaderonthewebbrowserishappierbecausethepageloadsmorequickly.

Page 62: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

PerformanceandutilizationSomeoftheexcitementoverNode.jsisduetoitsthroughput(therequestsperseconditcanserve).Comparativebenchmarksofsimilarapplications,forexample,Apache,showthatNode.jshastremendousperformancegains.

OnebenchmarkgoingaroundisthissimpleHTTPserver(borrowedfromhttps://nodejs.org/en/),whichsimplyreturnsaHelloWorldmessagedirectlyfrommemory:

varhttp=require('http');

http.createServer(function(req,res){

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

res.end('HelloWorld\n');

}).listen(8124,"127.0.0.1");

console.log('Serverrunningathttp://127.0.0.1:8124/');

ThisisoneofthesimplerwebserversthatyoucanbuildwithNode.js.ThehttpobjectencapsulatestheHTTPprotocol,anditshttp.createServermethodcreatesawholewebserver,listeningontheportspecifiedinthelistenmethod.Everyrequest(whetheraGETorPOSTonanyURL)onthatwebservercallstheprovidedfunction.Itisverysimpleandlightweight.Inthiscase,regardlessoftheURL,itreturnsasimpletext/plainthatistheHelloWorldresponse.

RyanDahlshowedasimplebenchmark(https://www.youtube.com/watch?v=M-sc73Y-zQA)thatreturneda1-megabytebinarybuffer;Node.jsgave822req/sec,whileNginxgave708req/sec,fora15%improvementoverNginx.HealsonotedthatNginxpeakedatfourmegabytesmemory,whileNode.jspeakedat64megabytes.

ThekeyobservationwasthatNode.js,runninganinterpretedJIT-compiledhigh-levellanguage,wasaboutasfastasNginx,builtofhighlyoptimized

Page 63: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Ccode,whilerunningsimilartasks.ThatpresentationwasinMay2010,andNode.jshasimprovedhugelysincethen,asshowninChrisBailey'stalkthatwereferencedearlier.

Yahoo!searchengineerFabianFrankpublishedaperformancecasestudyofareal-worldsearchquerysuggestionwidgetimplementedwithApache/PHPandtwovariantsofNode.jsstacks(http://www.slideshare.net/FabianFrankDe/nodejs-performance-case-study).Theapplicationisapop-uppanelshowingsearchsuggestionsastheusertypesinphrases,usingaJSON-basedHTTPquery.TheNode.jsversioncouldhandleeighttimesthenumberofrequestspersecondwiththesamerequestlatency.FabianFranksaidbothNode.jsstacksscaledlinearlyuntilCPUusagehit100%.Inanotherpresentation(http://www.slideshare.net/FabianFrankDe/yahoo-scale-nodejs),hediscussedhowYahoo!AxisisrunningonManhattan+Mojitoandthevalueofbeingabletousethesamelanguage(JavaScript)andframework(YUI/YQL)onbothfrontendandbackend.

LinkedIndidamassiveoverhauloftheirmobileappusingNode.jsfortheserver-sidetoreplaceanoldRubyonRailsapp.Theswitchletthemmovefrom30serversdowntothree,andallowedthemtomergethefrontendandbackendteambecauseeverythingwaswritteninJavaScript.BeforechoosingNode.js,they'devaluatedRailswithEventMachine,PythonwithTwisted,andNode.js,choosingNode.jsforthereasonsthatwejustdiscussed.ForalookatwhatLinkedIndid,seehttp://arstechnica.com/information-technology/2012/10/a-behind-the-scenes-look-at-

linkedins-mobile-engineering/.

MostexistingadviceonNode.jsperformancetipstendstohavebeenwrittenforolderV8versionsthatusedtheCrankShaftoptimizer.TheV8teamhascompletelydumpedCrankShaft,andithasanewoptimizercalledTurboFan.Forexample,underCrankShaft,itwasslowertousetry/catch,let/const,generatorfunctions,andsoon.Therefore,commonwisdomsaidtonotusethosefeatures,whichisdepressingbecausewewanttousethenewJavaScriptfeaturesbecauseofhowmuchithasimprovedtheJavaScriptlanguage.PeterMarshall,anEngineerontheV8teamatGoogle,gaveatalkatNode.jsInteractive2017claimingthat,

Page 64: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

underTurboFan,youshouldjustwritenaturalJavaScript.WithTurboFan,thegoalisforacross-the-boardperformanceimprovementsinV8.Toviewthepresentation,seehttps://www.youtube.com/watch?v=YqOhBezMx1o.

AtruismaboutJavaScriptisthatit'snogoodforheavycomputationwork,becauseofthenatureofJavaScript.We'llgooversomeideasrelatedtothisinthenextsection.AtalkbyMikolaLysenkoatNode.jsInteractive2016wentoversomeissueswithnumericalcomputinginJavaScript,andsomepossiblesolutions.CommonnumericalcomputinginvolveslargenumericalarraysprocessedbynumericalalgorithmsthatyoumighthavelearnedinCalculusorLinearAlgebraclasses.WhatJavaScriptlacksismulti-dimensionalarrays,andaccesstocertainCPUinstructions.Thesolutionhepresentedisalibrarytoimplementmulti-dimensionalarraysinJavaScript,alongwithanotherlibraryfullofnumericalcomputingalgorithms.Toviewthepresentation,seehttps://www.youtube.com/watch?v=1ORaKEzlnys.

ThebottomlineisthatNode.jsexcelsatevent-drivenI/Othroughput.WhetheraNode.jsprogramcanexcelatcomputationalprogramsdependsonyouringenuityinworkingaroundsomelimitationsintheJavaScriptlanguage.Abigproblemwithcomputationalprogrammingisthatitpreventstheeventloopfromexecutingand,aswewillseeinthenextsection,thatcanmakeNode.jslooklikeapoorcandidateforanything.

Page 65: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

IsNode.jsacancerousscalabilitydisaster?InOctober2011,softwaredeveloperandbloggerTedDziubawroteablogpost(sincepulledfromhisblog)titledNode.jsisacancer,callingitascalabilitydisaster.TheexampleheshowedforproofisaCPU-boundimplementationoftheFibonaccisequencealgorithm.Whilehisargumentwasflawed,heraisedavalidpointthatNode.jsapplicationdevelopershavetoconsiderthefollowing:wheredoyouputtheheavycomputationaltasks?

AkeytomaintaininghighthroughputofNode.jsapplicationsisensuringthateventsarehandledquickly.Becauseitusesasingleexecutionthread,ifthatthreadisboggeddownwithabigcalculation,Node.jscannothandleevents,andeventthroughputwillsuffer.

TheFibonaccisequence,servingasastand-inforheavycomputationaltasks,quicklybecomescomputationallyexpensivetocalculate,especiallyforanaïveimplementationsuchasthis:

constfibonacci=exports.fibonacci=function(n){

if(n===1||n===2)return1;

elsereturnfibonacci(n-1)+fibonacci(n-2);

}

Yes,therearemanywaystocalculatefibonaccinumbersmorequickly.WeareshowingthisasageneralexampleofwhathappenstoNode.jswheneventhandlersareslow,andnottodebatethebestwaystocalculatemathematicsfunctions.Considerthisserver:

consthttp=require('http');

consturl=require('url');

Page 66: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

constfibonacci=//asabove

http.createServer(function(req,res){

consturlP=url.parse(req.url,true);

letfibo;

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

if(urlP.query['n']){

fibo=fibonacci(urlP.query['n']);

res.end('Fibonacci'+urlP.query['n']+'='+fibo);

}else{

res.end('USAGE:http://127.0.0.1:8124?n=##where##istheFibonacci

numberdesired');

}

}).listen(8124,'127.0.0.1');

console.log('Serverrunningathttp://127.0.0.1:8124');

Forsufficientlylargevaluesofn(forexample,40),theserverbecomescompletelyunresponsivebecausetheeventloopisnotrunning,andinsteadthisfunctionisblockingeventprocessingbecauseitisgrindingthroughthecalculation.

DoesthismeanthatNode.jsisaflawedplatform?No,itjustmeansthattheprogrammermusttakecaretoidentifycodewithlong-runningcomputationsanddevelopsolutions.Theseincluderewritingthealgorithmtoworkwiththeeventloop,orrewritingthealgorithmforefficiency,orintegratinganativecodelibrary,orfoistingcomputationallyexpensivecalculationsontoabackendserver.

Asimplerewritedispatchesthecomputationsthroughtheeventloop,lettingtheservercontinuetohandlerequestsontheeventloop.Usingcallbacksandclosures(anonymousfunctions),we'reabletomaintainasynchronousI/Oandconcurrencypromises:

constfibonacciAsync=function(n,done){

if(n===0)return0;

elseif(n===1||n===2)done(1);

elseif(n===3)return2;

else{

process.nextTick(function(){

fibonacciAsync(n-1,function(val1){

process.nextTick(function(){

fibonacciAsync(n-2,function(val2){

Page 67: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

done(val1+val2);});

});

});

});

}

}

Becausethisisanasynchronousfunction,itnecessitatesasmallrefactoringoftheserver:

consthttp=require('http');

consturl=require('url');

constfibonacciAsync=//asabove

http.createServer(function(req,res){

leturlP=url.parse(req.url,true);

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

if(urlP.query['n']){

fibonacciAsync(urlP.query['n'],fibo=>{

res.end('Fibonacci'+urlP.query['n']+'='+fibo);

});

}else{

res.end('USAGE:http://127.0.0.1:8124?n=##where##istheFibonacci

numberdesired');

}

}).listen(8124,'127.0.0.1');console.log('Serverrunningat

http://127.0.0.1:8124');

Dziuba'svalidpointwasn'texpressedwellinhisblogpost,anditwassomewhatlostintheflamesfollowingthatpost.Namely,thatwhileNode.jsisagreatplatformforI/O-boundapplications,itisn'tagoodplatformforcomputationallyintensiveones.

Laterinthisbook,we'llexplorethisexamplealittlemoredeeply.

Page 68: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Serverutilization,thebusinessbottomline,andgreenwebhostingThestrivingforoptimalefficiency(handlingmorerequestspersecond)isnotjustaboutthegeekysatisfactionthatcomesfromoptimization.Therearerealbusinessandenvironmentalbenefits.Handlingmorerequestspersecond,asNode.jsserverscando,meansthedifferencebetweenbuyinglotsofserversandbuyingonlyafewservers.Node.jspotentiallyletsyourorganizationdomorewithless.

Roughlyspeaking,themoreserversyoubuy,thegreaterthecost,andthegreatertheenvironmentalimpactofhavingthoseservers.There'sawholefieldofexpertisearoundreducingcostsandtheenvironmentalimpactofrunningwebserverfacilities,towhichthatroughguidelinedoesn'tdojustice.Thegoalisfairlyobvious—fewerservers,lowercosts,andareducedenvironmentalimpactthroughutilizingmoreefficientsoftware.

Intel'spaper,IncreasingDataCenterEfficiencywithServerPowerMeasurements(https://www.intel.com/content/dam/doc/white-paper/intel-it-data-center-efficiency-server-power-paper.pdf),givesanobjectiveframeworkforunderstandingefficiencyanddatacentercosts.Therearemanyfactors,suchasbuildings,coolingsystems,andcomputersystemdesigns.Efficientbuildingdesign,efficientcoolingsystems,andefficientcomputersystems(datacenterefficiency,datacenterdensity,andstoragedensity)canlowercostsandenvironmentalimpact.Butyoucandestroythosegainsbydeployinganinefficientsoftwarestackcompellingyoutobuymoreserversthanyouwouldifyouhadanefficientsoftwarestack.Alternatively,youcanamplifygainsfromdatacenterefficiencywithanefficientsoftwarestackthatletsyoudecreasethenumberofserversrequired.

Page 69: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Thistalkaboutefficientsoftwarestacksisn'tjustforaltruisticenvironmentalpurposes.Thisisoneofthosecaseswherebeinggreencanhelpyourbusinessbottomline.

Page 70: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

EmbracingadvancesintheJavaScriptlanguageThelastcoupleofyearshavebeenanexcitingtimeforJavaScriptprogrammers.TheTC-39committeethatoverseestheECMAScriptstandardhasaddedmanynewfeatures,someofwhicharesyntacticsugar,butseveralofwhichhavepropelledusintoawholeneweraofJavaScriptprogramming.Byitself,theasync/awaitfeaturepromisesusawayoutofwhat'scalledCallbackHell,orthesituationwefindourselvesinwhennestingcallbackswithincallbacks.It'ssuchanimportantfeaturethatitshouldnecessitateabroadrethinkingoftheprevailingcallback-orientedparadigminNode.jsandtherestoftheJavaScriptecosystem.

Referbackafewpagestothis:

query('SELECT*fromdb',function(err,result){

if(err)throwerr;//handleerrors

//operateonresult

});

ThiswasanimportantinsightonRyanDahl'spart,andiswhatpropelledNode.js'spopularity.Certainactionstakealongtimetorun,suchasdatabasequeries,andshouldnotbetreatedthesameasoperationsthatquicklyretrievedatafrommemory.BecauseofthenatureoftheJavaScriptlanguage,Node.jshadtoexpressthisasynchronouscodingconstructinanunnaturalway.Theresultsdonotappearatthenextlineofcode,butinsteadappearwithinthiscallbackfunction.Further,errorshavetobehandledinanunnaturalway,insidethatcallbackfunction.

TheconventioninNode.jsisthatthefirstparametertoacallbackfunctionisanerrorindicator,andthesubsequentparametersaretheresults.Thisisausefulconventionthatyou'llfindallacrosstheNode.jslandscape.

Page 71: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

However,itcomplicatesworkingwithresultsanderrorsbecausebothlandinaninconvenientlocation—thatcallbackfunction.Thenaturalplaceforerrorsandresultstolandisonthesubsequentline(s)ofcode.

Wedescendfurtherintocallbackhellwitheachlayerofcallbackfunctionnesting.Theseventhlayerofcallbacknestingismorecomplexthanthesixthlayerofcallbacknesting.Why?Ifnothingelse,it'sthatthespecialconsiderationsforerrorhandlingbecomeevermorecomplexascallbacksarenestedmoredeeply.

varresults=awaitquery('SELECT*fromdb');

Instead,ES2017asyncfunctionsreturnustothisverynaturalexpressionofprogrammingintent.Resultsanderrorslandinthecorrectlocation,whilepreservingtheexcellentevent-drivenasynchronousprogrammingmodelthatmadeNode.jsgreat.We'llseelaterinthebookhowthisworks.

TheTC-39committeeaddedmanymorenewfeaturestoJavaScript,suchas:

AnimprovedsyntaxforClassdeclarationsmakingobjectinheritanceandgetter/setterfunctionsverynatural.

AnewmoduleformatthatisstandardizedacrossbrowsersandNode.js.

Newmethodsforstrings,suchasthetemplatestringnotation.

Newmethodsforcollectionsandarrays—forexample,operationsformap/reduce/filter.

Theconstkeywordtodefinevariablesthatcannotbechanged,andtheletkeywordtodefinevariableswhosescopeislimitedtotheblockinwhichthey'redeclared,ratherthanhoistedtothefrontofthefunction.

Page 72: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Newloopingconstructs,andaniterationprotocolthatworkswiththosenewloops.

Anewkindoffunction,thearrowfunction,whichislighterweightmeaninglessmemoryandexecutiontimeimpact

ThePromiseobjectrepresentsaresultthatispromisedtobedeliveredinthefuture.Bythemselves,Promisescanmitigatethecallbackhellproblem,andtheyformpartofthebasisforasyncfunctions.

Generatorfunctionsareanintriguingwaytorepresentasynchronousiterationoverasetofvalues.Moreimportantly,theyformtheotherhalfofthebasisforasyncfunctions.

YoumayseethenewJavaScriptdescribedasES6orES2017.What'sthepreferrednametodescribetheversionofJavaScriptthatisbeingused?

ES1throughES5markedvariousphasesofJavaScript'sdevelopment.ES5wasreleasedin2009,andiswidelyimplementedinmodernbrowsers.StartingwithES6,theTC-39committeedecidedtochangethenamingconventionbecauseoftheirintentiontoaddnewlanguagefeatureseveryyear.Therefore,thelanguageversionnamenowincludestheyear,henceES2015wasreleasedin2015,ES2016wasreleasedin2016,andES2017wasreleasedin2017.

Page 73: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

DeployingES2015/2016/2017/2018JavaScriptcodeThepinkelephantintheroomisthat,becauseofhowJavaScriptisdeliveredtotheworld,wecannotjuststartusingthelatestES2017features.InfrontendJavaScript,wearelimitedbythefactthatoldbrowsersarestillinuse.InternetExplorerversion6hasfortunatelybeenalmostcompletelyretired,buttherearestillplentyofoldbrowsersinstalledonoldercomputersthatarestillservingavalidrolefortheirowners.OldbrowsersmeanoldJavaScriptimplementations,andifwewantourcodetowork,weneedittobecompatiblewitholdbrowsers.

UsingcoderewritingtoolssuchasBabel,someofthenewfeaturescanberetrofittedtofunctiononsomeoftheolderbrowsers.FrontendJavaScriptprogrammerscanadopt(someof)thenewfeaturesatthecostofamorecomplexbuildtoolchain,andtheriskofbugsintroducedbythecoderewritingprocess.Somemaywishtodothat,whileotherswillprefertowaitawhile.

TheNode.jsworlddoesn'thavethisproblem.Node.jshasrapidlyadoptedES2015/2016/2017featuresasquicklyastheywereimplementedintheV8engine.WithNode.js8,wecannowuseasyncfunctionsasanativefeature,andmostoftheES2015/2016featuresbecameavailablewithNode.jsversion6.ThenewmoduleformatisnowsupportedinNode.jsversion10.

Inotherwords,whilefrontendJavaScriptprogrammerscanarguethattheymustwaitacoupleofyearsbeforeadoptingES2015/2016/2017features,Node.jsprogrammershavenoneedtowait.Wecansimplyusethenewfeatureswithoutneedinganycoderewritingtools.

Page 74: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Node.js,themicroservicearchitecture,andeasilytestablesystemsNewcapabilities,suchasclouddeploymentsystemsandDocker,makeitpossibletoimplementanewkindofservicearchitecture.Dockermakesitpossibletodefineserverprocessconfigurationinarepeatablecontainerthat'seasytodeploybythemillionsintoacloudhostingsystem.Itlendsitselfbesttosmallsingle-purposeserviceinstancesthatcanbeconnectedtogethertomakeacompletesystem.Dockerisn'ttheonlytooltohelpsimplifyclouddeployments;however,itsfeaturesarewellattunedtomodernapplicationdeploymentneeds.

Somehavepopularizedthemicroserviceconceptasawaytodescribethiskindofsystem.Accordingtothemicroservices.iowebsite,amicroserviceconsistsofasetofnarrowlyfocused,independentlydeployableservices.Theycontrastthiswiththemonolithicapplicationdeploymentpatternwhereeveryaspectofthesystemisintegratedintoonebundle(suchasasingleWARfileforaJavaEEappserver).Themicroservicemodelgivesdevelopersmuchneededflexibility.

Someadvantagesofmicroservicesareasfollows:

Eachmicroservicecanbemanagedbyasmallteam

Eachteamcanworkonitsownschedule,solongastheserviceAPIcompatibilityismaintained

Microservicescanbedeployedindependently,suchasforeasiertesting

Page 75: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

It'seasiertoswitchtechnologystackchoices

WheredoesNode.jsfitinwiththis?Itsdesignfitsthemicroservicemodellikeaglove:

Node.jsencouragessmall,tightlyfocused,single-purposemodules

Thesemodulesarecomposedintoanapplicationbytheexcellentnpmpackagemanagementsystem

Publishingmodulesisincrediblysimple,whetherviatheNPMrepositoryoraGitURL

Page 76: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Node.jsandtheTwelve-FactorappmodelThroughoutthisbook,we'llcalloutaspectsoftheTwelve-FactorAppmodel,andwaystoimplementthoseideasinNode.js.Thismodelispublishedonhttp://12factor.net,andisasetofguidelinesforapplicationdeploymentinthemoderncloudcomputingera.It'snotthattheTwelve-FactorAppmodelisthebe-allandend-allofapplicationarchitectureparadigms.It'sasetofusefulideas,clearlybirthedaftermanylatenightsspentdebuggingcomplexapplications,whichofferusefulideasthatcouldsaveusallalotofeffortbyhavingeasier-to-maintainandmorereliablesystems.

Theguidelinesarestraightforward,andonceyoureadthem,theywillseemlikepurecommonsense.Asabestpractice,theTwelve-FactorAppmodelisacompellingstrategyfordeliveringthekindoffluidself-containedcloud-deployedapplicationscalledforbyourcurrentcomputingenvironment.

Page 77: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SummaryYoulearnedalotinthischapter.Specifically,yousawthatJavaScripthasalifeoutsidewebbrowsersandyoulearnedaboutthedifferencebetweenasynchronousandblockingI/O.WethencoveredtheattributesofNode.jsandwhereitfitsintheoverallwebapplicationplatformmarketandthreadedversusasynchronoussoftware.Lastly,wesawtheadvantagesoffastevent-drivenasynchronousI/O,coupledwithalanguagewithgreatsupportforanonymousclosures.

Ourfocusinthisbookisreal-worldconsiderationsofdevelopinganddeployingNode.jsapplications.We'llcoverasmanyaspectsaswecanofdeveloping,refining,testing,anddeployingNode.jsapplications.

Nowthatwe'vehadthisintroductiontoNode.js,we'rereadytodiveinandstartusingit.InChapter2,SettingupNode.js,we'llgooversettingupaNode.jsenvironment,solet'sgetstarted.

Page 78: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SettingupNode.jsBeforegettingstartedwithusingNode.js,youmustsetupyourdevelopmentenvironment.Inthefollowingchapters,we'llusethisfordevelopmentandfornon-productiondeployment.

Inthischapter,wewillcoverthefollowingtopics:

HowtoinstallNode.jsfromsourceandprepackagedbinariesonLinux,macOS,orWindows

HowtoinstallNodePackageManager(NPM)andsomepopulartools

TheNode.jsmodulesystem

Node.jsandJavaScriptlanguageimprovementsfromtheECMAScriptcommittee

Solet'sgetonwithit.

Page 79: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SystemrequirementsNode.jsrunsonPOSIX-likeoperatingsystems,variousUNIXderivatives(Solaris,forexample)orworkalikes(Linux,macOS,andsoon),aswellasonMicrosoftWindows.Itcanrunonmachinesbothlargeandsmall,includingthetinyARMdevicessuchastheRaspberryPimicroscaleembeddablecomputerforDIYsoftware/hardwareprojects.

Node.jsisnowavailableviapackagemanagementsystems,limitingtheneedtocompileandinstallfromsource.

BecausemanyNode.jspackagesarewritteninCorC++,youmusthaveaCcompiler(suchasGCC),Python2.7(orlater),andthenode-gyppackage.Ifyouplantouseencryptioninyournetworkingcode,youwillalsoneedtheOpenSSLcryptographiclibrary.ThemodernUNIXderivativesalmostcertainlycomewiththese,andNode.js'sconfigurescript,usedwheninstallingfromsource,willdetecttheirpresence.Ifyouneedtoinstallthem,Pythonisavailableathttp://python.organdOpenSSLisavailableathttp://openssl.org.

Page 80: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

InstallingNode.jsusingpackagemanagersThepreferredmethodforinstallingNode.js,now,istousetheversionsavailableinpackagemanagers,suchasapt-get,orMacPorts.Packagemanagerssimplifyyourlifebyhelpingtomaintainthecurrentversionofthesoftwareonyourcomputer,ensuringtoupdatedependentpackagesasnecessary,allbytypingasimplecommandsuchasapt-getupdate.Let'sgooverthisfirst.

Page 81: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

InstallingonmacOSwithMacPortsTheMacPortsproject(http://www.macports.org/)hasforyearsbeenpackagingalonglistofopensourcesoftwarepackagesformacOS,andtheyhavepackagedNode.js.AfteryouhaveinstalledMacPortsusingtheinstallerontheirwebsite,installingNode.jsisprettymuchthissimple:

$portsearchnodejsnpm

...

[email protected](devel,net)

EventedI/OforV8JavaScript

[email protected](devel,net)

EventedI/OforV8JavaScript

[email protected](devel,net)

EventedI/OforV8JavaScript

[email protected](devel,net)

EventedI/OforV8JavaScript

Found6ports.

--

[email protected](devel)

nodepackagemanager

[email protected](devel)

nodepackagemanager

Found4ports.

$sudoportinstallnodejs8npm5

..longlogofdownloadingandinstallingprerequisitesandNode

$whichnode

Page 82: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

optlocal/bin/node

$node--version

v8.9.1

Page 83: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

<strong>$brewupdate</strong><br/><strong>...longwaitandlotsofoutput</strong><br/><strong>$brewsearchnode</strong><br/><strong>==>Searchinglocaltaps...</strong><br/><strong>node<imgsrc="Images/902ea8ae-c8f4-4de3-a41e-a21d10704fd1.png"style="width:1.67em;height:1.67em;"width="150"height="150">libbitcoin-nodenode-buildnode@6nodeenv<strong><br/><strong>leafnodellnodenode@4nodebrewnodenv</strong><br/><strong>==>SearchingtapsonGitHub...</strong><br/><strong>caskroom/cask/node-profiler</strong><br/><strong>==>Searchingblacklisted,migratedanddeletedformulae...</strong>

<strong>$brewinstallnode</strong><br/><strong>...</strong><br/><strong>==>Installingnode</strong><br/><strong>==>Downloadinghttps://homebrew.bintray.com/bottles/node-8.9.1.el_capitan.bottle.tar.gz</strong><br/><strong>########################################################################100.0%</strong><br/><strong>==>Pouringnode-8.9.1.el_capitan.bottle.tar.gz</strong><br/><strong>==>Caveats</strong><br/><strong>Bashcompletionhasbeeninstalledto:</strong><br/><strong>usrlocal/etc/bash_completion.d</strong><br/><strong>==>Summary</strong><br/><strong><imgsrc="Images/062f1f44-c05b-4b0b-881f-b7ad78f50bc9.png"style="width:1.33em;height:1.33em;"width="150"height="150"/>usrlocal/Cellar/node/8.9.1:5,012files,49.6MB</strong>

<strong>$node--versionv8.9.1</strong>

Page 84: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

<strong>#curl-sLhttps://deb.nodesource.com/setup_10.x|sudo-Ebash-

#sudoapt-getinstall-ynodejs#sudoapt-getinstall-ybuild-essential</strong>

TodownloadotherNode.jsversions(thisexampleshowsversion10.x),modifytheURLtosuit.

Page 85: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

InstallingNode.jsintheWindowsSubsystemforLinux(WSL)TheWindowsSubsystemforLinux(WSL)letsyouinstallUbuntu,openSUSE,orSUSELinuxEnterpriseonWindows.AllthreeareavailableviatheStorebuiltintoWindows10.YoumayneedtoupdateyourWindowsfortheinstallationtowork.

Onceinstalled,theLinux-specificinstructionswillinstallNode.jswithintheLinuxsubsystem.

ToinstalltheWSL,seehttps://msdn.microsoft.com/en-us/commandline/wsl/install-win10.

Page 86: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Openinganadministrator-privilegedPowerShellonWindowsSomeofthecommandsyou'llrunwhileinstallingtoolsonWindowsaretobeexecutedinaPowerShellwindowwithelevatedprivileges.WementionthisbecausetheprocessofenablingtheWSLincludesacommandtoberuninsuchaPowerShellwindow.

Theprocessissimple:

1. IntheStartmenu,enterPowerShellintheapplicationssearchbox.2. TheresultantmenuwilllistPowerShell.3. Right-clickthePowerShellentry.4. ThecontextmenuthatcomesupwillhaveanentryRunas

Administrator.Clickonthat.

Theresultantcommandwindowwillhaveadministratorprivileges,andthetitlebarwillsayAdministrator:WindowsPowerShell.

Page 87: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

InstallingtheNode.jsdistributionfromnodejs.orgThehttps://nodejs.org/en/websiteoffersbuilt-inbinariesforWindows,macOS,Linux,andSolaris.Wecansimplygotothewebsite,clickontheInstallbutton,andruntheinstaller.Forsystemswithpackagemanagers,suchastheoneswe'vejustdiscussed,it'spreferabletousethepackagemanagementsystem.That'sbecauseyou'llfinditeasiertostayup-to-datewiththelatestversion.But,thatdoesn'tserveallpeoplebecause:

Somewillprefertoinstallabinaryratherthandealwiththepackagemanager

Theirchosensystemdoesn'thaveapackagemanagementsystem

TheNode.jsimplementationintheirpackagemanagementsystemisout-of-date

SimplygototheNode.jswebsiteandyou'llseesomethinglikethefollowingscreenshot.ThepagedoesitsbesttodetermineyourOSandsupplytheappropriatedownload.Ifyouneedsomethingdifferent,clickontheDOWNLOADSlinkintheheaderforallpossibledownloads:

Page 88: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

FormacOS,theinstallerisaPKGfilegivingthetypicalinstallationprocess.ForWindows,theinstallersimplytakesyouthroughthetypicalInstallWizardprocess.

Oncefinishedwiththeinstaller,youhavecommand-linetools,suchasnodeandnpm,withwhichyoucanrunNode.jsprograms.OnWindows,you'resuppliedwithaversionoftheWindowscommandshellpreconfiguredtoworknicelywithNode.js.

Page 89: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

InstallingfromsourceonPOSIX-likesystemsInstallingtheprepackagedNode.jsdistributionsisthepreferredinstallationmethod.However,installingNode.jsfromsourceisdesirableinafewsituations:

Itcanletyouoptimizethecompilersettingsasdesired

Itcanletyoucross-compile,say,foranembeddedARMsystem

YoumightneedtokeepmultipleNode.jsbuildsfortesting

YoumightbeworkingonNode.jsitself

Nowthatyouhavethehigh-levelview,let'sgetourhandsdirtymuckingaroundinsomebuildscripts.Thegeneralprocessfollowstheusualconfigure,make,andmakeinstallroutinethatyoumayalreadyhaveperformedwithotheropensourcesoftwarepackages.Ifnot,don'tworry,we'llguideyouthroughtheprocess.

TheofficialinstallationinstructionsareintheREADME.mdcontainedwithinthesourcedistributionathttps://github.com/nodejs/node/blob/master/README.md.

Page 90: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

InstallingprerequisitesTherearethreeprerequisites:aCcompiler,Python,andtheOpenSSLlibraries.TheNode.jscompilationprocesschecksfortheirpresenceandwillfailiftheCcompilerorPythonisnotpresent.Thespecificmethodofinstallingtheseisdependentonyouroperatingsystem.

Thesesortsofcommandswillcheckfortheirpresence:$cc--versionAppleLLVMversion7.0.2(clang-700.1.81)Target:x86_64-apple-darwin15.3.0Threadmodel:posix$pythonPython2.7.11(default,Jan82016,22:23:13)[GCC4.2.1CompatibleAppleLLVM7.0.2(clang-700.1.81)]ondarwinType"help","copyright","credits"or"license"formoreinformation.>>>

Seethisfordetails:https://github.com/nodejs/node/blob/master/BUILDING.md.

TheNode.jsbuildtoolsdonotsupportPython3.x.

Page 91: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

InstallingdevelopertoolsonmacOSDevelopertools(suchasGCC)areanoptionalinstallationonmacOS.Fortunately,they'reeasytoacquire.

YoustartwithXcode,whichisavailableforfreethroughtheMacAppStore.SimplysearchforXcodeandclickontheGetbutton.OnceyouhaveXcodeinstalled,openaTerminalwindowandtypethefollowing:$xcode-select--install

ThisinstallstheXcodecommand-linetools:

Page 92: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Foradditionalinformation,visithttp://osxdaily.com/2014/02/12/install-command-line-tools-mac-os-x/.

Page 93: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

InstallingfromsourceforallPOSIX-likesystemsCompilingNode.jsfromsourcefollowsthisprocess:

1. Downloadthesourcefromhttp://nodejs.org/download.

2. Configurethesourceforbuildingusing./configure.3. Runmake,thenmakeinstall.

Thesourcebundlecanbedownloadedwithyourbrowser,orasfollows,substitutingyourpreferredversion:

$mkdirsrc

$cdsrc

$wgethttps://nodejs.org/dist/v10.0.0/node-v10.0.0.tar.gz

$tarxvfznode-v10.0.0.tar.gz

$cdnode-v10.0.0

Nowweconfigurethesourcesothatitcanbebuilt.Thisisjustlikemanyotheropensourcepackages,andtherearealonglistofoptionstocustomizethebuild:

$./configure--help

Tocausetheinstallationtolandinyourhomedirectory,runitthisway:

$./configure--prefix=$HOME/node/10.0.0

..outputfromconfigure

Page 94: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Ifyou'regoingtoinstallmultipleNode.jsversionssidebyside,it'susefultoputtheversionnumberinthepathlikethis.Thatway,eachversionwillsitinaseparatedirectory.It'sasimplematterofswitchingbetweenNode.jsversionsbychangingthePATHvariableappropriately:

#Onbashshell:

$exportPATH=${HOME}/node/VERSION-NUMBER/bin:${PATH}

#Oncsh

$setenvPATH${HOME}/node/VERSION-NUMBER/bin:${PATH}

AsimplerwaytoinstallmultipleNode.jsversionsisthenvmscriptdescribedlater.

IfyouwanttoinstallNode.jsinasystem-widedirectory,simplyleaveoffthe--prefixoptionanditwilldefaulttoinstallinginusrlocal.

Afteramoment,it'llstopandwilllikelyhavesuccessfullyconfiguredthesourcetreeforinstallationinyourchosendirectory.Ifthisdoesn'tsucceed,theerrormessagesthatareprintedwilldescribewhatneedstobefixed.Oncetheconfigurescriptissatisfied,youcangoontothenextstep.

Withtheconfigurescriptsatisfied,youcompilethesoftware:

$make

..alonglogofcompileroutputisprinted

$makeinstall

Ifyouareinstallingintoasystem-widedirectory,dothelaststepthiswayinstead:

$make

$sudomakeinstall

Onceinstalled,youshouldmakesurethatyouaddtheinstallationdirectorytoyourPATHvariableasfollows:

$echo'exportPATH=$HOME/node/10.0.0/bin:${PATH}'>>~/.bashrc

Page 95: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

$.~/.bashrc

Alternatively,forcshusers,usethissyntaxtomakeanexportedenvironmentvariable:

$echo'setenvPATH$HOME/node/10.0.0/bin:${PATH}'>>~/.cshrc

$source~/.cshrc

Thisshouldresultinsomedirectories,asfollows:

$ls~/node/10.0.0/

binincludelibshare

$ls~/node/10.0.0/bin

Page 96: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

InstallingfromsourceonWindowsTheBUILDING.mddocumentreferencedpreviouslyhasinstructions.OneusesthebuildtoolsfromVisualStudio,orelsethefullVisualStudio2017product:

VisualStudio2017:https://www.visualstudio.com/downloads/

Buildtools:https://www.visualstudio.com/downloads/#build-tools-for-visual-studio-2017

Threeadditionaltoolsarerequired:

GitforWindows:http://git-scm.com/download/win

Python:https://www.python.org/

OpenSSL:https://www.openssl.org/source/andhttps://wiki.openssl.org/index.php/Binaries

Then,runtheincluded.\vcbuildscripttoperformthebuild.

Page 97: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

InstallingmultipleNode.jsinstanceswithnvmNormally,youwon'tinstallmultipleversionsofNode.jsanddoingsoaddscomplexitytoyoursystem.ButifyouarehackingonNode.jsitself,oraretestingyoursoftwareagainstdifferentNode.jsreleases,youmaywanttohavemultipleNode.jsinstallations.Themethodtodosoisasimplevariationonwhatwe'vealreadydiscussed.

Earlier,whilediscussingbuildingNode.jsfromsource,wenotedthatonecaninstallmultipleNode.jsinstancesinseparatedirectories.It'sonlynecessarytobuildfromsourceifyouneedacustomizedNode.jsbuild,andmostfolkswillbesatisfiedwithpre-builtNode.jsbinaries.They,too,canbeinstalledintoseparatedirectories.

ToswitchbetweenNode.jsversionsissimplyamatterofchangingthePATHvariable(onPOSIXsystems),asfollows,usingthedirectorywhereyouinstalledNode.js:

$exportPATH=usrlocal/node/VERSION-NUMBER/bin:${PATH}

Itstartstobealittletedioustomaintainthisafterawhile.Foreachrelease,youhavetosetupNode.js,NPM,andanythird-partymodulesyoudesireinyourNode.jsinstallation.Also,thecommandshowntochangeyourPATHisnotquiteoptimal.InventiveprogrammershavecreatedseveralversionmanagerstosimplifymanagingmultipleNode.js/NPMreleasesandprovidingcommandstochangeyourPATHthesmartway:

Nodeversionmanager:https://github.com/tj/n

Nodeversionmanager:https://github.com/creationix/nvm

Page 98: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

BothmaintainmultiplesimultaneousversionsofNodeandletyoueasilyswitchbetweenversions.Installationinstructionsareavailableontheirrespectivewebsites.

Forexample,withnvm,youcanruncommandslikethese:

$nvmls

...

v6.0.0

v6.1.0

v6.2.2

v6.3.1

v6.4.0

...

v6.11.2

v7.0.0

v7.1.0

v7.10.0

v8.0.0

v8.1.3

v8.2.1

v8.5.0

v8.9.1

v8.9.3

v9.2.0

v9.4.0

v9.5.0

v9.10.1

v9.11.1

->v10.0.0

->system

node->stable(->v8.9.1)(default)

stable->8.9(->v8.9.1)(default)

iojs->N/A(default)

$nvmuse10

Nowusingnodev10.0.0(npmv5.6.0)

$node--version

v10.0.0

$nvmusev4.2

Nowusingnodev4.2.0(npmv2.14.7)

$node--version

v4.2.0

$nvminstall9

Downloadinghttps://nodejs.org/dist/v9.2.0/node-v9.2.0-darwin-x64.tar.xz...

########################################################################

100.0%

WARNING:checksumsarecurrentlydisabledfornode.jsv4.0andlater

Page 99: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Nowusingnodev9.2.0(npmv5.5.1)

$node--version

v9.2.0

$whichnode

/Users/david/.nvm/versions/node/v9.2.0/bin/node

$usrlocal/bin/node--version

v8.9.1

$optlocal/bin/node--version

v8.9.1

Thisdemonstratesthatyoucanhaveasystem-wideNode.jsinstalled,keepmultipleprivateNode.jsversionsmanagedbynvm,andswitchbetweenthemasneeded.WhennewNode.jsversionsarereleased,theyaresimpletoinstallwithnvmeveniftheofficialpackagedversionforyourOSdoesn'timmediatelyupdate.

Page 100: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

InstallingnvmonWindowsUnfortunately,nvmdoesnotsupportWindows.Fortunately,acoupleofWindows-specificclonesofthenvmconceptexist:

https://github.com/coreybutler/nvm-windows

https://github.com/marcelklehr/nodist

AnotherrouteistousetheWSL.BecauseinWSLyou'reinteractingwithaLinuxcommandline,youcanusenvmitself.

Manyoftheexamplesinthisbookweretestedusingthenvm-windowsapplication.Thereareslightbehaviordifferences,butitactslargelythesameasnvmforLinuxandmacOS.Thebiggestchangeistheversionnumberspecifierinthenvmuseandnvminstallcommands.

WithnvmforLinuxandmacOSonecantypeasimpleversionnumber,likenvmuse8,anditwillautomaticallysubstitutethelatestreleaseofthenamedNode.jsversion.Withnvm-windowsthesamecommandactsasifyoutyped"nvmuse8.0.0".Inotherwords,withnvm-windowsyoumustusetheexactversionnumber.Fortunately,thelistofsupportedversionsiseasilyavailableusingthe"nvmlistavailable"command.

Page 101: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Nativecodemodulesandnode-gypWhilewewon'tdiscussnativecodemoduledevelopmentinthisbook,wedoneedtomakesurethattheycanbebuilt.SomemodulesintheNPMrepositoryarenativecode,andtheymustbecompiledwithaCorC++compilertobuildthecorresponding.nodefiles(the.nodeextensionisusedforbinarynative-codemodules).

Themodulewilloftendescribeitselfasawrapperforsomeotherlibrary.Forexample,thelibxsltandlibxmljsmodulesarewrappersaroundtheC/C++librariesofthesamename.ThemoduleincludestheC/C++sourcecode,andwheninstalled,ascriptisautomaticallyruntodothecompilationwithnode-gyp.

Thenode-gyptoolisacross-platformcommand-linetoolwritteninNode.jsforcompilingnativeadd-onmodulesforNode.js.We'vementionednativecodemodulesseveraltimes,anditisthistoolthatcompilesthemforusewithNode.js.

Youcaneasilyseethisinactionbyrunningthesecommands:

$mkdirtemp

$cdtemp

$npminstalllibxmljslibxslt

Thisisdoneinatemporarydirectory,soyoucandeleteitafterward.Ifyoursystemdoesnothavethetoolsinstalledtocompilenativecodemodules,you'llseeerrormessages.Otherwise,you'llseeintheoutputanode-gypexecution,followedbymanylinesoftextobviouslyrelatedtocompilingC/C++files.

Page 102: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Thenode-gyptoolhasprerequisitessimilartothoseforcompilingNode.jsfromsource.Namely,aC/C++compiler,aPythonenvironment,andotherbuildtoolssuchasGit.ForUnix/macOS/Linuxsystemsthoseareeasytocomeby.ForWindows,youshouldinstall:

VisualStudioBuildTools:https://www.visualstudio.com/downloads/#build-tools-for-visual-studio-2017

GitforWindows:http://git-scm.com/download/win

PythonforWindows:https://www.python.org/

Normally,youwon'tneedtoworryaboutinstallingnode-gyp.That'sbecauseitisinstalledbehindthescenesaspartofNPM.That'sdonesothatNPMcanautomaticallybuildnativecodemodules.

ItsGitHubrepositorycontainsdocumentationathttps://github.com/nodejs/node-gyp.

Readingthenode-gypdocumentation,initsrepository,willgiveyouaclearerunderstandingofthecompilationprerequisitesdiscussedpreviously,aswellasofdevelopingnativecodemodules.

Page 103: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Node.jsversionspolicyandwhattouseWejustthrewaroundsomanydifferentNode.jsversionnumbersintheprevioussectionthatyoumayhavebecomeconfusedoverwhichversiontouse.ThisbookistargetingNode.jsversion10.x,andit'sexpectedthateverythingwe'llcoveriscompatiblewithNode.js10.xandanysubsequentrelease.

StartingwithNode.js4.x,theNode.jsteamisfollowingadual-trackapproach.Theeven-numberedreleases(4.x,6.x,8.x,andsoon)arewhatthey'recallingLongTermSupport(LTS),whiletheodd-numberedreleases(5.x,7.x,9.x,andsoon)arewherecurrentnewfeaturedevelopmentoccurs.Whilethedevelopmentbranchiskeptstable,theLTSreleasesarepositionedasbeingforproductionuseandwillreceiveupdatesforseveralyears.

Atthetimeofwriting,Node.js8.xisthecurrentLTSrelease;Node.js9.xwasjustreleasedandwilleventuallybecomeNode.js10.x,whichinturnwilleventuallybecometheLTSrelease.Forcompletedetailsaboutthereleaseschedule,refertohttps://github.com/nodejs/LTS/.

AmajorimpactofeachnewNode.jsrelease,beyondtheusualperformanceimprovementsandbugfixes,isbringinginthelatestV8JavaScriptenginerelease.Inturn,thismeansbringinginmoreoftheES-2015/2016/2017featuresastheV8teamimplementsthosefeatures.InNode.js8.x,async/awaitfunctionsarrived,andinNode.js10.xsupportforthestandardES6moduleformathasarrived.

ApracticalconsiderationiswhetheranewNode.jsreleasewillbreakyourcode.NewlanguagefeaturesarealwaysbeingaddedasV8catchesupwithECMAScript,andtheNode.jsteamsometimesmakesbreaking

Page 104: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

changesintheNode.jsAPI.Ifyou'vetestedononeNode.jsversion,willitworkonanearlierversion?WillaNode.jschangebreaksomeassumptionswemade?

TheNPMPackageManagerhelpsusensurethatourpackagesexecuteonthecorrectNode.jsversion.Thismeansthatwecanspecifyinthepackage.jsonfile,whichwe'llexploreinChapter3,Node.jsModules,thecompatibleNode.jsversionsforapackage.

Wecanaddanentrytopackage.jsonasfollows:

engines:{

"node":">=6.x"

}

Thismeansexactlywhatitimplies—thatthegivenpackageiscompatiblewithNode.jsversion6.xorlater.

Ofcourse,yourdevelopmentmachine(s)couldhaveseveralNode.jsversionsinstalled.You'llneedtheversionyoursoftwareisdeclaredtosupport,plusanylaterversionsyouwishtoevaluate.

Page 105: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

EditorsanddebuggersSinceNode.jscodeisJavaScript,anyJavaScript-awareeditorwillbeuseful.UnlikesomeotherlanguagesthataresocomplexthatanIDEwithcodecompletionisanecessity,asimpleprogrammingeditorisperfectlysufficientforNode.jsdevelopment.

TwoeditorsareworthcallingoutbecausetheyarewritteninNode.js:AtomandMicrosoftVisualStudioCode.

Atom(https://atom.io/)billsitselfasahackableeditorforthe21stcentury.ItisextendablebywritingNode.jsmodulesusingtheAtomAPI,andtheconfigurationfilesareeasilyeditable.Inotherwords,it'shackableinthesamewayplentyofothereditorshavebeen,goingbacktoEmacs,meaningonewritesasoftwaremoduletoaddcapabilitiestotheeditor.TheElectronframeworkwasinventedinordertobuildAtom,andElectronisasupereasywaytobuilddesktopapplicationsusingNode.js.

MicrosoftVisualStudioCode(https://code.visualstudio.com/)isalsoahackableeditor—well,thehomepagesaysextensibleandcustomizable,whichmeansthesamething—thatisalsoopensource,andisalsoimplementedinElectron.Butit'snotahollowme-tooeditor,apingAtomwhileaddingnothingofitsown.Instead,VisualStudioCodeisasolidprogrammerseditorinitsownright,bringinginterestingfunctionalitytothetable.

Asfordebuggers,thereareseveralinterestingchoices.StartingwithNode.js6.3,theinspectorprotocolmadeitpossibletousetheGoogleChromedebugger.VisualStudioCodehasabuilt-indebuggerthatalsousestheinspectorprotocol.

Forafulllistofdebuggingoptionsandtools,seehttps://nodejs.org/en/docs/guides/debugging-getting-started/.

Page 106: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

RunningandtestingcommandsNowthatyou'veinstalledNode.js,wewanttodotwothings—verifythattheinstallationwassuccessful,andfamiliarizeyouwiththecommand-linetools.

Page 107: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Node.js'scommand-linetoolsThebasicinstallationofNode.jsincludestwocommands,nodeandnpm.We'vealreadyseenthenodecommandinaction.It'susedeitherforrunningcommand-linescriptsorserverprocesses.Theother,npm,isapackagemanagerforNode.js.

TheeasiestwaytoverifythatyourNode.jsinstallationworksisalsothebestwaytogethelpwithNode.js.Typethefollowingcommand:

$node--help

Usage:node[options][-escript|script.js|-][arguments]

nodeinspectscript.js[arguments]

Options:

-v,--versionprintNode.jsversion

-e,--evalscriptevaluatescript

-p,--printevaluatescriptandprintresult

-c,--checksyntaxcheckscriptwithoutexecuting

-i,--interactivealwaysentertheREPLevenifstdin

doesnotappeartobeaterminal

-r,--requiremoduletopreload(optioncanberepeated)

-scriptreadfromstdin(default;interactivemodeifatty)

--inspect[=[host:]port]activateinspectoronhost:port

(default:127.0.0.1:9229)

--inspect-brk[=[host:]port]

activateinspectoronhost:port

andbreakatstartofuserscript

--inspect-port=[host:]port

sethost:portforinspector

...manymoreoptions

Environmentvariables:

NODE_DEBUG','-separatedlistofcoremodules

thatshouldprintdebuginformation

NODE_DISABLE_COLORSsetto1todisablecolorsintheREPL

NODE_EXTRA_CA_CERTSpathtoadditionalCAcertificates

file

NODE_ICU_DATAdatapathforICU(Intlobject)data

(willextendlinked-indata)

Page 108: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

NODE_NO_WARNINGSsetto1tosilenceprocesswarnings

NODE_NO_HTTP2setto1tosuppressthehttp2module

NODE_OPTIONSsetCLIoptionsintheenvironment

viaaspace-separatedlist

NODE_PATH':'-separatedlistofdirectories

prefixedtothemodulesearchpath

NODE_PENDING_DEPRECATIONsetto1toemitpendingdeprecation

warnings

NODE_REPL_HISTORYpathtothepersistentREPLhistory

file

NODE_REDIRECT_WARNINGSwritewarningstopathinsteadof

stderr

OPENSSL_CONFloadOpenSSLconfigurationfromfile

Documentationcanbefoundathttps://nodejs.org/

NotethatthereareoptionsforbothNode.jsandV8(notshowninthepreviouscommandline).RememberthatNode.jsisbuiltontopofV8;ithasitsownuniverseofoptionsthatlargelyfocusondetailsofbytecodecompilationorgarbagecollectionandheapalgorithms.Enternode--v8-optionstoseethefulllistofthem.

Onthecommandline,youcanspecifyoptions,asinglescriptfile,andalistofargumentstothatscript.We'lldiscussscriptargumentsfurtherinthenextsection,RunningasimplescriptwithNode.js.

RunningNode.jswithnoargumentsplopsyouintoaninteractiveJavaScriptshell:

$node

>console.log('Hello,world!');

Hello,world!

undefined

AnycodeyoucanwriteinaNode.jsscriptcanbewrittenhere.ThecommandinterpretergivesagoodTerminal-orienteduserexperienceandisusefulforinteractivelyplayingwithyourcode.Youdoplaywithyourcode,don'tyou?Good!

Page 109: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

RunningasimplescriptwithNode.jsNow,let'sseehowtorunscriptswithNode.js.It'squitesimple;let'sstartbyreferringtothehelpmessageshownpreviously.Thecommand-linepatternisjustascriptfilenameandsomescriptarguments,whichshouldbefamiliartoanyonewhohaswrittenscriptsinotherlanguages.

CreatingandeditingNode.jsscriptscanbedonewithanytexteditorthatdealswithplaintextfiles,suchasVI/VIM,Emacs,Notepad++,Atom,VisualStudioCode,Jedit,BBEdit,TextMate,orKomodo.It'shelpfulifit'saprogrammer-orientededitor,ifonlyforthesyntaxcoloring.

Forthisandotherexamplesinthisbook,itdoesn'ttrulymatterwhereyouputthefiles.However,forthesakeofneatness,youcanstartbymakingadirectorynamednode-web-devinthehomedirectoryofyourcomputer,andinsidethatcreatingonedirectoryperchapter(forexample,chap02andchap03).

First,createatextfilenamedls.jswiththefollowingcontent:

constfs=require('fs');

constutil=require('util');

constfs_readdir=util.promisify(fs.readdir);

(async()=>{

constfiles=awaitfs_readdir('.');

for(letfnoffiles){

console.log(fn);

}

})().catch(err=>{console.error(err);});

Next,runitbytypingthefollowingcommand:

$nodels.js

Page 110: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ls.js

ThisisapalecheapimitationoftheUnixlscommand(asifyoucouldn'tfigurethatoutfromthename).ThereaddirfunctionisacloseanalogtotheUnixreaddirsystemcall(typeman3readdirinaTerminalwindowtolearnmore)andisusedtolistthefilesinadirectory.

Wehavewrittenthisusinganinlineasyncfunction,theawaitkeyword,andanES2015for..ofloop.Usingutil.promisify,wecanconvertanycallback-orientedfunctionsoitreturnsaPromise,sothatthePromiseplayswellwiththeawaitkeyword.

Bydefaultfsmodulefunctionsusethecallbackparadigm,asdoesmostNode.jsmodules.Butwithinasyncfunctionsitismoreconvenientiffunctionsinsteadreturnpromises.Usingutil.promisifywecanmakeitso.

Thisscriptishardcodedtolistfilesinthecurrentdirectory.Thereallscommandtakesadirectoryname,solet'smodifythescriptalittle.

Command-lineargumentslandinaglobalarraynamedprocess.argv.Thereforewecanmodifyls.js,copyingitasls2.js,asfollowstoseehowthisarrayworks:

constfs=require('fs');

constutil=require('util');

constfs_readdir=util.promisify(fs.readdir);

(async()=>{

vardir='.';

if(process.argv[2])dir=process.argv[2];

constfiles=awaitfs_readdir(dir);

for(letfnoffiles){

console.log(fn);

}

})().catch(err=>{console.error(err);});

Youcanrunitasfollows:

$pwd

Page 111: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

/Users/David/chap02

$nodels2..

chap01

chap02

$nodels2

app.js

ls.js

ls2.js

Wesimplycheckedifacommand-lineargumentwaspresent,if(process.argv[2]).Ifitwas,weoverrodethevalueofthedirvariable,dir=process.argv[2],andwethenusedthatasthereaddirargument.

Ifyougiveitanonexistentdirectorypathname,anerrorwillbethrownandprintedusingthecatchclause.Thatlookslikeso:

$nodels2.js/nonexistent

{Error:ENOENT:nosuchfileordirectory,scandir'/nonexistent'

errno:-2,

code:'ENOENT',

syscall:'scandir',

path:'/nonexistent'}

Page 112: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ConversiontoasyncfunctionsandthePromiseparadigmIntheprevioussectionwediscussedtheutil.promisifyanditsabilitytoconvertacallback-orientedfunctionintoonethatreturnsaPromise.ThelatterplaywellwithinasyncfunctionsandthereforeitispreferableforfunctionstoreturnaPromise.

Tobemoreprecise,util.promisifyistobegivenafunctionthatusestheerror-first-callbackparadigm.Thelastargumentofsuchfunctionsisacallbackfunctionwhosefirstargumentisinterpretedasanerrorindicator,hencethephraseerror-first-callback.Whatutil.promisifyreturnsisanotherfunctionthatreturnsaPromise.

ThePromiseservesthesamepurposeastheerror-first-callback.Ifanerrorisindicated,thePromiseresolvestotherejectedstatus,whileifsuccessisindicatedthePromiseresolvestoasuccessstatus.Asweseeintheseexamples,withinanasyncfunctionthePromiseishandledverynicely.

TheNode.jsecosystemhasalargebodyoffunctionsusingtheerror-first-callback.ThecommunityhasbegunaconversionprocesswherefunctionswillreturnaPromise,andpossiblyalsotakeanerror-first-callbackforAPIcompatibility.

OneofthenewfeaturesinNode.js10isanexampleofsuchaconversion.Withinthefsmoduleisasubmodule,namedfs.promises,withthesameAPIbutproducingPromiseobjects.Wecouldrewritethepreviousexampleasso:constfs=require('fs').promises;(async()=>{vardir='.';if(process.argv[2])dir=process.argv[2];constfiles=awaitfs.readdir(dir);

Page 113: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

for(letfnoffiles){console.log(fn);}})().catch(err=>{console.error(err);});

Asyoucansee,thefunctionsinthefs.promisesmodulereturnsaPromisewithoutrequiringacallbackfunction.Thenewprogram,whichyoucansaveasls2-promises.js,isrunasso:

$nodels2-promises.js

(node:40329)ExperimentalWarning:Thefs.promisesAPIisexperimental

app.js

ls.js

ls2-promises.js

ls2.js

TheAPIiscurrentlyinanexperimentalstateandthereforewe'reshownthiswarning.

Anotherchoiceisa3rdpartymodule,fs-extra.ThismodulehasanextendedAPIbeyondthestandardfsmodule.OntheonehanditsfunctionsreturnaPromiseifnocallbackfunctionisprovided,orelseinvokesthecallback.Inadditionitincludesseveralusefulfunctions.

Intherestofthisbookwewillbeusingfs-extrabecauseofthoseadditionalfunctions.Fordocumentationofthemodule,see:https://www.npmjs.com/package/fs-extra.

Page 114: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

LaunchingaserverwithNode.jsManyscriptsthatyou'llrunareserverprocesses.We'llberunninglotsofthesescriptslateron.Sincewe'restillinthedualmodeofverifyingtheinstallationandfamiliarizingyouwithusingNode.js,wewanttorunasimpleHTTPserver.Let'sborrowthesimpleserverscriptontheNode.jshomepage(http://nodejs.org).

Createafilenamedapp.jscontainingthefollowing:

consthttp=require('http');

http.createServer(function(req,res){

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

res.end('Hello,World!\n');

}).listen(8124,'127.0.0.1');

console.log('Serverrunningathttp://127.0.0.1:8124');

Runitasfollows:

$nodeapp.js

Serverrunningathttp://127.0.0.1:8124

ThisisthesimplestofwebserversyoucanbuildwithNode.js.Ifyou'reinterestedinhowitworks,flipforwardtoChapter4,HTTPServersandClients;Chapter5,YourFirstExpressApplication;andChapter6,ImplementingtheMobile-FirstParadigm.Forthemoment,justvisithttp://127.0.0.1:8124inyourbrowsertoseetheHello,World!message:

Page 115: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Aquestiontoponderiswhythisscriptdidnotexitwhenls.jsdidexit.Inbothcases,executionofthescriptreachestheendofthescript;theNode.jsprocessdoesnotexitinapp.js,whileinls.jsitdoes.

Thereasonisthepresenceofactiveeventlisteners.Node.jsalwaysstartsupaneventloop,andinapp.js,thelistenfunctioncreatesaneventlistenerthatimplementstheHTTPprotocol.Thiseventlistenerkeepsapp.jsrunninguntilyoudosomethingsuchastypingCtrl+CintheTerminalwindow.Inls.js,thereisnothingthatcreatesalong-runningeventlistener,sowhenls.jsreachestheendofitsscript,thenodeprocesswillexit.

Page 116: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

NPM–theNode.jspackagemanagerNode.jsbyitselfisaprettybasicsystem,beingaJavaScriptinterpreterwithafewinterestingasynchronousI/Olibraries.OneofthethingsthatmakesNode.jsinterestingistherapidlygrowingecosystemofthird-partymodulesforNode.js.

AtthecenterofthatecosystemisNPM.WhileNode.jsmodulescanbedownloadedassourceandassembledmanuallyforusewithNode.jsprograms,that'stediousandit'sdifficulttoimplementarepeatablebuildprocess.NPMgivesusasimplerway;NPMisthedefactostandardpackagemanagerforNode.jsanditgreatlysimplifiesdownloadingandusingthesemodules.WewilltalkaboutNPMatlengthinthenextchapter.

Thesharp-eyedwillhavenoticedthatnpmisalreadyinstalledviaalltheinstallationmethodsdiscussedpreviously.Inthepast,npmwasinstalledseparately,buttodayitisbundledwithNode.js.

Nowthatwehavenpminstalled,let'stakeitforaquickspin.Thehexyprogramisautilityforprintinghexdumpsoffiles.That'savery1970thingtodo,butisstillextremelyuseful.Itservesourpurposerightnowingivingussomethingtoquicklyinstallandtryout:

$npminstall-ghexy

optlocal/bin/hexy->optlocal/lib/node_modules/hexy/bin/hexy_cmd.js

[email protected]

added1packagein1.107s

Addingthe-gflagmakesthemoduleavailableglobally,irrespectiveofthepresent-working-directoryofyourcommandshell.Aglobalinstallismostusefulwhenthemoduleprovidesacommand-lineinterface.Whena

Page 117: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

packageprovidesacommand-linescript,npmsetsthatup.Foraglobalinstall,thecommandisinstalledcorrectlyforusebyallusersofthecomputer.

DependingonhowNode.jsisinstalledforyou,thatmayneedtoberunwithsudo:

$sudonpminstall-ghexy

Onceitisinstalled,you'llbeabletorunthenewly–installedprogramthisway:

$hexy--width12ls.js

00000000:636f6e7374206673203d2072const.fs.=.r

0000000c:657175697265282766732729equire('fs')

00000018:3b0a636f6e7374207574696c;.const.util

00000024:203d20726571756972652827.=.require('

00000030:7574696c27293b0a636f6e73util');.cons

0000003c:742066735f72656164646972t.fs_readdir

00000048:203d207574696c2e70726f6d.=.util.prom

00000054:69736966792866732e726561isify(fs.rea

00000060:64646972293b0a0a28617379ddir);..(asy

0000006c:6e63202829203d3e207b0a20nc.().=>.{..

00000078:20636f6e73742066696c6573.const.files

00000084:203d2061776169742066735f.=.await.fs_

00000090:7265616464697228272e2729readdir('.')

0000009c:3b0a2020666f722028666e20;...for.(fn.

000000a8:6f662066696c657329207b0aof.files).{.

000000b4:20202020636f6e736f6c652e....console.

000000c0:6c6f6728666e293b0a20207dlog(fn);...}

000000cc:0a7d2928292e636174636828.})().catch(

000000d8:657272203d3e207b20636f6eerr.=>.{.con

000000e4:736f6c652e6572726f722865sole.error(e

000000f0:7272293b207d293brr);.});

Again,we'llbedoingadeepdiveintoNPMinthenextchapter.ThehexyutilityisbothaNode.jslibraryandascriptforprintingouttheseold-stylehexdumps.

Page 118: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Node.js,ECMAScript2015/2016/2017,andbeyondIn2015,theECMAScriptcommitteereleasedalong-awaitedmajorupdateoftheJavaScriptlanguage.TheupdatebroughtinmanynewfeaturestoJavaScript,suchasPromises,arrowfunctions,andClassobjects.Thelanguageupdatesetthestageforimprovements.sincethatshoulddramaticallyimproveourabilitytowriteclean,understandableJavaScriptcode.

Thebrowsermakersareaddingthosemuch-neededfeatures,meaningtheV8engineisaddingthosefeaturesaswell.ThesefeaturesaremakingtheirwayintoNode.jsstartingwithversion4.x.

TolearnaboutthecurrentstatusofES-2015inNode.js,visithttps://nodejs.org/en/docs/es6/.

Bydefault,onlytheES-2015/2016/2017featuresthatV8considersstableareenabledbyNode.js.Furtherfeaturescanbeenabledwithcommand-lineoptions.Thealmost-completefeaturesareenabledwiththe--es_stagingoption.Thewebsitedocumentationgivesmoreinformation.

TheNodegreenwebsite(http://node.green/)hasatablelistingthestatusofalonglistoffeaturesinNode.jsversions.

TheES2017languagespecispublishedat:https://www.ecma-international.org/publications/standards/Ecma-262.htm.

TheTC-39committeedoesitsworkonGitHubhttps://github.com/tc39.

TheES-2015featuresmakeabigimprovementintheJavaScriptlanguage.Onefeature,thePromiseclass,shouldmeanafundamentalrethinkingofcommonidiomsinNode.jsprogramming.InES-2017,apairofnew

Page 119: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

keywords,asyncandawait,willsimplifywritingasynchronouscodeinNode.js,anditshouldencouragetheNode.jscommunitytofurtherrethinkthecommonidiomsoftheplatform.

There'salonglistofnewJavaScriptfeatures,butlet'squicklygoovertwoofthemthatwe'lluseextensively.

Thefirstisalighter-weightfunctionsyntaxcalledthearrowfunction:

fs.readFile('file.txt','utf8',(err,data)=>{

if(err)...;//dosomethingwiththeerror

else...;//dosomethingwiththedata

});

Thisismorethanthesyntacticsugarofreplacingthefunctionkeywordwiththefatarrow.Arrowfunctionsarelighter-weightaswellasbeingeasiertoread.Thelighterweightcomesatthecostofchangingthevalueofthisinsidethearrowfunction.Inregularfunctions,thishasauniquevalueinsidethefunction.Inanarrowfunction,thishasthesamevalueasthescopecontainingthearrowfunction.Thismeansthat,whenusinganarrowfunction,wedon'thavetojumpthroughhoopstobringthisintothecallbackfunctionbecausethisisthesameatbothlevelsofthecode.

ThenextfeatureisthePromiseclass,whichisusedfordeferredandasynchronouscomputations.DeferredcodeexecutiontoimplementasynchronousbehaviorisakeyparadigmforNode.js,anditrequirestwoidiomaticconventions:

Thelastargumenttoanasynchronousfunctionisacallbackfunction,whichiscalledwhenanasynchronousexecutionistobeperformed

Thefirstargumenttothecallbackfunctionisanerrorindicator

Whileconvenient,theseconventionsresultedinmultilayercodepyramidsthatcanbedifficulttounderstandandmaintain:

Page 120: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

doThis(arg1,arg2,(err,result1,result2)=>{

if(err)...;

else{

//dosomework

doThat(arg2,arg3,(err2,results)=>{

if(err2)...;

else{

doSomethingElse(arg5,err=>{

if(err)..;

else..;

});

}

});

}

});

Dependingonhowmanystepsarerequiredforaspecifictask,acodepyramidcangetquitedeep.Promiseswillletusunravelthecodepyramidandimprovereliability,becauseerrorhandlingismorestraightforwardandeasilycapturesallerrors.

APromiseclassiscreatedasfollows:

functiondoThis(arg1,arg2){

returnnewPromise((resolve,reject)=>{

//executesomeasynchronouscode

if(errorIsDetected)returnreject(errorObject);

//Whentheprocessisfinishedcallthis:

resolve(result1,result2);

});

}

Ratherthanpassinginacallbackfunction,thecallerreceivesaPromiseobject.Whenproperlyutilized,theprecedingpyramidcanbecodedasfollows:

doThis(arg1,arg2)

.then(result=>{

//Thiscanreceiveonlyonevalue,henceto

//receivemultiplevaluesrequiresanobjectorarray

returndoThat(arg2,arg3);

})

Page 121: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

.then((results)=>{

returndoSomethingElse(arg5);

})

.then(()=>{

//doafinalsomething

})

.catch(err=>{

//errorslandhere

});

ThisworksbecausethePromiseclasssupportschainingifathenfunctionreturnsaPromiseobject.

Theasync/awaitfeatureimplementsthepromiseofthePromiseclasstosimplifyasynchronouscoding.Thisfeaturebecomesactivewithinanasyncfunction:

asyncfunctionmumble(){

//asyncmagichappenshere

}

Anasyncarrowfunctionisasfollows:

constmumble=async()=>{

//asyncmagichappenshere

};

It'susedasso:

asyncfunctiondoSomething(arg1,arg2,arg3,arg4,arg5){

var{result1,result2}=awaitdoThis(arg1,arg2);

varresults=awaitdoThat(arg2,arg3);

awaitdoSomethingElse(arg5);

//doafinalsomething

returnfinalResult;

}

Isn'tthisabreathoffreshaircomparedtothenestedstructurewestartedwith?

Page 122: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TheawaitkeywordisusedwithaPromise.ItautomaticallywaitsforthePromisetoresolve.IfthePromiseresolvessuccessfullythenthevalueisreturned,andifitresolveswithanerrorthenthaterroristhrown.Bothhandlingresultsandthrowingerrorsarehandledinthenaturalmanner.

ThisexamplealsoshowsanotherES2015feature:destructuring.Thefieldsofanobjectcanbeextractedusingthefollowing:

var{value1,value2}={

value1:"Value1",value2:"Value2",value3:"Value3"

};

Wehaveanobjectwiththreefields,butextractonlytwoofthefields.

Page 123: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

UsingBabeltouseexperimentalJavaScriptfeaturesTheBabeltranspiler(http://babeljs.io/)isagreatwaytousecutting-edgeJavaScriptfeaturesonolderimplementations.ThewordtranspilemeansBabelrewritesJavaScriptcodeintootherJavaScriptcode,specificallytorewriteES-2015orES-2016featurestoolderJavaScriptcode.BabelconvertsJavaScriptsourcetoanabstractsyntaxtree,thenmanipulatesthattreetorewritethecodeusingolderJavaScriptfeatures,andthenwritesthattreetoaJavaScriptsourcecodefile.

Putanotherway,BabelrewritesJavaScriptcodeintoJavaScriptcode,applyingdesiredtransformationssuchasconvertingES2015/2016featuresintoES5codethatcanruninawebbrowser.

ManyuseBabeltoexperimentwithnewJavaScriptfeatureproposalsworkingtheirwaythroughtheTC-39committee.OthersuseBabeltousenewJavaScriptfeaturesinprojectsonJavaScriptenginesthatdonotsupportthosefeatures.

TheNodeGreenwebsitemakesitclearthatNode.jssupportsprettymuchalloftheES2015/2016/2017features.Therefore,asapracticalmatter,wenolongerneedtouseBabelforNode.jsprojects.

Forwebbrowsers,thereisamuchlongertimelagbetweenasetofECMAScriptfeaturesandwhenwecanreliablyusethosefeaturesinbrowser-sidecode.It'snotthatthewebbrowsermakersareslowinadoptingnewfeatures,becausetheGoogle,Mozilla,andMicrosoftteamsareproactiveaboutadoptingthelatestfeatures.Apple'sSafariteamseemsslowtoadoptnewfeatures,unfortunately.What'sslower,however,isthe

Page 124: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

penetrationofnewbrowsersintothefleetofcomputersinthefield.

Therefore,modernJavaScriptprogrammersneedtofamiliarizethemselveswithBabel.

We'renotreadytoshowexamplecodeforthesefeatures,butwecangoaheadanddocumentthesetupoftheBabeltool.Forfurtherinformationonsetupdocumentation,visithttp://babeljs.io/docs/setup/,andthenclickontheCLIbutton.

TogetabriefintroductiontoBabel,we'lluseittotranspilethescriptswesawearliertorunonNode.js6.x.Inthosescriptsweusedasyncfunctions,whicharenotsupportedinNode.js6.x.

Inthedirectorycontainingls.jsandls2.js,typethesecommands:

$npminstallbabel-cli\

babel-plugin-transform-es2015-modules-commonjs\

babel-plugin-transform-async-to-generator

ThisinstallstheBabelsoftware,alongwithacoupleoftransformationplugins.Babelhasapluginsystemsothatyouenablethetransformationsrequiredbyyourproject.OurprimarygoalinthisexampleisconvertingtheasyncfunctionsshownearlierintoGeneratorfunctions.GeneratorsareanewsortoffunctionintroducedwithES2015,whichformthefoundationforimplementationofasyncfunctions.

BecauseNode.js6.xdoesnothaveutil.promisify,weneedtomakeonesubstitution:

//constfs_readdir=util.promisify(fs.readdir);

constfs_readdir=dir=>{

returnnewPromise((resolve,reject)=>{

fs.readdir(dir,(err,fileList)=>{

if(err)reject(err);

elseresolve(fileList);

});

});

};

Thisstructureismoreorlesswhattheutil.promisifyfunctiondoes.

Page 125: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Next,createafilenamed.babelrccontainingthefollowing:

{

"plugins":[

"transform-es2015-modules-commonjs",

"transform-async-to-generator"

]

}

ThisfileinstructsBabeltousethenamedtransformationpluginsthatweinstalledearlier.

Becauseweinstalledbabel-cli,ababelcommandisinstalledsuchthatwecantypethefollowing:

$./node_modules/.bin/babel-help

Totranspileyourcode,runthefollowingcommand:

$./node_modules/.bin/babells2.js-ols2-babel.js

Thiscommandtranspilesthenamedfile,producinganewfile.Thenewfileisasfollows:

'usestrict';

function_asyncToGenerator(fn){returnfunction(){vargen=

fn.apply(this,arguments);returnnewPromise(function(resolve,reject){

functionstep(key,arg){try{varinfo=gen[key](arg);varvalue=

info.value;}catch(error){reject(error);return;}if(info.done){

resolve(value);}else{returnPromise.resolve(value).then(function(value)

{step("next",value);},function(err){step("throw",err);});}}return

step("next");});};}

constfs=require('fs');

constutil=require('util');

//constfs_readdir=util.promisify(fs.readdir);

constfs_readdir=dir=>{

returnnewPromise((resolve,reject)=>{

Page 126: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

fs.readdir(dir,(err,fileList)=>{

if(err)reject(err);

elseresolve(fileList);

});

});

};

_asyncToGenerator(function*(){

vardir='.';

if(process.argv[2])dir=process.argv[2];

constfiles=yieldfs_readdir(dir);

for(letfnoffiles){

console.log(fn);

}

})().catch(err=>{

console.error(err);

});

Thiscodeisn'tmeanttobeeasytoreadbyhumans.Instead,it'smeantthatyouedittheoriginalsourcefile,andthenconvertitforyourtargetJavaScriptengine.ThemainthingtonoticeisthatthetranspiledcodeusesaGeneratorfunctioninplaceoftheasyncfunction,andtheyieldkeywordinplaceoftheawaitkeyword.The_asyncToGeneratorfunctionimplementsfunctionalitysimilartoasyncfunctions.

Thetranspiledscriptisrunasfollows:

$nodels2-babel

.babelrc

app.js

babel

ls.js

ls2-babel.js

ls2.js

node_modules

Inotherwords,itrunsthesameastheasyncversion,butonanolderNode.jsrelease.

Page 127: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SummaryYoulearnedalotinthischapteraboutinstallingNode.js,usingitscommand-linetools,andrunningaNode.jsserver.Wealsobreezedpastalotofdetailsthatwillbecoveredlaterinthebook,sobepatient.

Specifically,wecovereddownloadingandcompilingtheNode.jssourcecode,installingNode.jseitherfordevelopmentuseinyourhomedirectoryorfordeploymentinsystemdirectoriesandinstallingNPM—thedefactostandardpackagemanagerusedwithNode.js.WealsosawhowtorunNode.jsscriptsorNode.jsservers.WethentookalookatthenewfeaturesinES-2015/2016/2017.Finally,wesawhowtouseBabeltoimplementthosefeaturesinyourcode.

Nowthatwe'veseenhowtosetupthebasicsystem,we'rereadytostartworkingonimplementingapplicationswithNode.js.First,youmustlearnthebasicbuildingblocksofNode.jsapplicationsandmodules,whichwewillcoverinthenextchapter.

Page 128: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Node.jsModulesBeforewritingNode.jsapplications,youmustlearnaboutNode.jsmodulesandpackages.Modulesandpackagesarethebuildingblocksforbreakingdownyourapplicationintosmallerpieces.

Inthischapter,wewillcoverthefollowingtopics:

Definingamodule

TheCommonJSandES2015modulespecifications

UsingES2015/2016/2017codingpracticesinNode.js

UsingtheES6moduleformatinNode.jscode

UnderstandinghowNode.jsfindsmodules

Thenpmpackagemanagementsystem

So,let'sgetonwithit.

Page 129: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

DefiningamoduleModulesarethebasicbuildingblocksforconstructingNode.jsapplications.ANode.jsmoduleencapsulatesfunctions,hidingdetailsinsideawell-protectedcontainer,andexposinganexplicitly-declaredlistoffunctions.

Therearetwomoduleformatsthatwemustconsider:

ThetraditionalNode.jsformatbasedontheCommonJSstandardhasbeenusedsinceNode.jswascreated.

WithES2015/2016anewformat,ES6Modules,hasbeendefinedwithanewimportkeyword.ES6moduleswillbe(oris)supportedinallJavaScriptimplementations.

BecauseES6modulesarenowthestandardmoduleformat,theNode.jsTechnicalSteeringCommittee(TSC)iscommittedtofirst-classsupportforES6modules.

Wehavealreadyseenmodulesinactioninthepreviouschapter.EveryJavaScriptfileweuseinNode.jsisitselfamodule.It'stimetoseewhattheyareandhowtheywork.We'llstartwithCommonJSmodulesandthenquicklybringinES6modules.

Inthels.jsexampleinChapter2,SettingupNode.js,wewrotethefollowingcodetopullinthefsmodule,givingusaccesstoitsfunctions:

constfs=require('fs');

Therequirefunctionsearchesforthenamedmodule,loadingthemodule

Page 130: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

definitionintotheNode.jsruntime,andmakingitsfunctionsavailable.Inthiscase,thefsobjectcontainsthecode(anddata)exportedbythefsmodule.ThefsmoduleispartoftheNode.jscoreandprovidesfilesystemfunctions.

Bydeclaringfsasconst,wehavealittlebitofassuranceagainstmakingcodingmistakesthatwouldmodifytheobjectholdingthemodulereference.

IneveryNode.jsmodule,theexportsobjectwithinthemoduleistheinterfaceexportedtoothercode.Anythingassignedtoafieldoftheexportsobjectisavailabletootherpiecesofcode,andeverythingelseishidden.Bytheway,thisobjectisactuallymodule.exports.Theexportsobjectisanaliasformodule.exports.

Therequirefunctionandmodule.exportsobjectsbothcomefromtheCommonJSspecification.ES6moduleshavesimilarconcepts,butadifferentimplementation.

Let'slookatabriefexampleofthisbeforedivingintothedetails.Ponderoverthesimple.jsmodule:

varcount=0;

exports.next=function(){return++count;};

exports.hello=function(){

return"Hello,world!";

};

Wehaveonevariable,count,whichisnotattachedtotheexportsobject,andafunction,next,whichisattached.Now,let'suseit:

$node

>consts=require('./simple');

undefined

>s.hello();

'Hello,world!'

>s.next();

1

>s.next();

Page 131: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

2

>s.next();

3

>console.log(s.count);

undefined

undefined

>

Theexportsobjectinthemoduleistheobjectthatisreturnedbyrequire('./simple').Therefore,eachcalltos.nextcallsthenextfunctioninsimple.js.Eachreturns(andincrements)thevalueofthelocalvariable,count.Anattempttoaccesstheprivatefield,count,showsit'sunavailablefromoutsidethemodule.

Toreiteratetherule:

Anything(functionsorobjects)assignedasafieldofexports(asknownasmodule.exports)isavailabletoothercodeoutsidethemodule

Objectsnotassignedtoexportsarenotavailabletocodeoutsidethemodule,unlessthemoduleexportsthoseobjectsviaanothermechanism

ThisishowNode.jssolvestheglobalobjectproblemofbrowser-basedJavaScript.Thevariablesthatlooklikethey'reglobalvariablesareonlyglobaltothemodulecontainingthatvariable.Thesevariablesarenotvisibletoanyothercode.

Nowthatwe'vegotatasteformodules,let'stakeadeeperlook.

Page 132: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

CommonJSandES2015moduleformatsNode.js'smoduleimplementationisstronglyinspiredby,butnotidenticalto,theCommonJSmodulespecification.ThedifferencesbetweenthemmightonlybeimportantifyouneedtosharecodebetweenNodeandotherCommonJSsystems.

AmongthechangesinES2015isastandardmoduleformatmeantforuseeverywhere.Ithassomeinterestingfeatures,andbyexistingeverywhereitshouldadvancethestateofJavaScript.SinceitisincompatiblewiththeCommonJS/Node.jsmodulesystem,adoptingES2015modulesinNode.jsmeansreworkingourpracticesandacceptednorms.

Asapracticalmatter,Node.jsprogrammerswillbedealingwithbothmoduleformatsforsometimeduringatransitionperiod.Ourlong-termgoalshouldbetoadoptES2015modulesacrosstheboard.TheNode.jsplatformisslatedtobringinsupportforES2015modulesinNode.js10.AsofNode.js8.5,thefeatureisavailablebysettingacommand-lineflag.

Page 133: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

CommonJS/Node.jsmoduleformatWe'vealreadyseenacoupleofexamplesofthismoduleformat,withthesimple.jsexample,andtheprogramsweexaminedinChapter2,SettingupNode.js.Solet'stakeacloserlook.

CommonJSmodulesarestoredinfileswiththeextension.js.

LoadingaCommonJSmoduleisasynchronousoperation.Thatmeansthatwhentherequire('modulename')functioncallreturns,themodulehasbeenlocatedandcompletelyreadintomemoryandisreadytogo.Themoduleiscachedinmemorysothatsubsequentrequire('modulename')callsreturnimmediately,andallreturntheexactsameobject.

Node.jsmodulesprovideasimpleencapsulationmechanismtohideimplementationdetailswhileexposinganAPI.Modulesaretreatedasiftheywerewrittenasfollows:

(function(){...contentsofmodulefile...})();

Thus,everythingwithinthemoduleiscontainedwithinananonymousprivatenamespacecontext.Thisishowtheglobalobjectproblemisresolved;everythinginamodulethatlooksglobalisactuallycontainedwithinthisprivatecontext.

ObjectsandfunctionscanbeexposedfromaCommonJSmodulebymeansoftwofreevariablesNode.jsinsertsintothisprivatecontext:moduleandexports:

Themoduleobjectcontainsseveralfieldsthatyoumightfinduseful.

Page 134: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

RefertotheonlineNode.jsdocumentationfordetails.

Theexportsobjectisanaliasofthemodule.exportsfield.Thismeansthatthefollowingtwolinesofcodeareequivalent:

exports.funcName=function(arg,arg1){...};

module.exports.funcName=function(arg,arg2){..};

Yourcodecanbreakthealiasbetweenthetwoifyoudothis:

exports=function(arg,arg1){...};

Donotdothat,becauseexportswillnolongerbeequivalenttomodule.exports.Ifyourintentistoassignasingleobjectorfunctiontobewhat'sreturnedbyrequire,dothisinstead:

module.exports=function(arg,arg1){...};

Somemodulesdoexportasinglefunctionbecausethat'showthemoduleauthorenvisioneddeliveringthedesiredfunctionality.

TheNode.jspackageformatisderivedfromtheCommonJSmodulesystem(http://commonjs.org).Whendeveloped,theCommonJSteamaimedtofillagapintheJavaScriptecosystem.Atthattime,therewasnostandardmodulesystem,makingittrickiertopackageJavaScriptapplications.Therequirefunction,theexportsobject,andotheraspectsofNode.jsmodulescomedirectlyfromtheCommonJSModules/1.0spec.

Page 135: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ES6moduleformatES6modulesareanewmoduleformatdesignedforallJavaScriptenvironments.WhileNode.jshashadagoodmodulesystemforitswholeexistence,browser-sideJavaScripthasnot.Thatleftthebrowser-sidecommunitywitheitherrelyingonthe<script>tag,orusingnon-standardizedsolutions.Forthatmatter,traditionalNode.jsmoduleswereneverstandardized,outsideoftheCommonJSeffort.Therefore,ES6modulesstandtobeabigimprovementfortheentireJavaScriptworld,bygettingeveryoneonthesamepagewithacommonmoduleformatandmechanisms.

ThesideeffectisthattheNode.jscommunityneedstostartlookingat,learningabout,andadoptingtheES2015moduleformat.

ES6modulesarereferredtobyNode.jswiththeextension.mjs.Whenitcametoimplementingthenewmoduleformat,theNode.jsteamdeterminedthattheycouldnotsupportbothCommonJSandES6moduleswiththe.jsextension.The.mjsextensionwasdecidedasthesolution,andyoumayseetongue-in-cheekreferencestoMichaelJacksonScriptforthisfileextension.

OneinterestingdetailisthatES6modulesloadasynchronously.ThismaynothaveanimpactonNode.jsprogrammers,exceptthatthisispartoftherationalebehindrequiringthenew.mjsextension.

Createafilenamedsimple2.mjsinthesamedirectoryasthesimple.jsexamplethatwelookedatearlier:

varcount=0;

exportfunctionnext(){return++count;}

functionsquared(){returnMath.pow(count,2);}

exportfunctionhello(){

return"Hello,world!";

Page 136: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

}

exportdefaultfunction(){returncount;}

exportconstmeaning=42;

exportletnocount=-1;

export{squared};

ES6itemsexportedfromamodulearedeclaredwiththeexportkeyword.Thiskeywordcanbeputinfrontofanytop-leveldeclaration,suchasvariable,function,orclassdeclarations:

exportfunctionnext(){..}

Theeffectofthisissimilartothefollowing:

module.exports.next=function(){..}

Theintentofbothisessentiallythesame:tomakeafunction,orotherobject,availabletocodeoutsidethemodule.Astatementsuchasexportfunctionnext()isanamedexport,meaningtheexportedthinghasaname,andthatcodeoutsidethemoduleusesthatnametoaccesstheobject.Asweseehere,namedexportscanbefunctionsorobjects,andtheymayalsobeclassdefinitions.

Usingexportdefaultcanbedoneoncepermodule,andisthedefaultexportfromthemodule.Thedefaultexportiswhatcodeoutsidethemoduleaccesseswhenusingthemoduleobjectitself,ratherthanwhenusingoneoftheexportsfromthemodule.

Youcanalsodeclaresomething,suchasthesquaredfunction,andthenexportitlater.

Nowlet'sseehowtousethisES2015module.Createasimpledemo.mjsfilewiththefollowing:

import*assimple2from'./simple2.mjs';

console.log(simple2.hello());

Page 137: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

console.log(`${simple2.next()}${simple2.squared()}`);

console.log(`${simple2.next()}${simple2.squared()}`);

console.log(`${simple2.default()}${simple2.squared()}`);

console.log(`${simple2.next()}${simple2.squared()}`);

console.log(`${simple2.next()}${simple2.squared()}`);

console.log(`${simple2.next()}${simple2.squared()}`);

console.log(simple2.meaning);

Theimportstatementdoeswhatitmeans:itimportsobjectsexportedfromamodule.ThisversionoftheimportstatementismostsimilartoatraditionalNode.jsrequirestatement,meaningthatitcreatesanobjectthroughwhichyouaccesstheobjectsexportedfromthemodule.

Thisishowthecodeexecutes:

$node--experimental-modulessimpledemo.mjs

(node:63937)ExperimentalWarning:TheESMmoduleloaderisexperimental.

Hello,world!

11

24

24

39

416

525

42

AsofNode.js8.5,thenewmoduleformatisavailablebehindanoptionflagasshownhere.You'realsopresentedwiththisnicewarningthatit'sanexperimentalfeature.Accessingthedefaultexportisaccomplishedbyaccessingthefieldnameddefault.Accessinganexportedvalue,suchasthemeaningfield,isdonewithoutparenthesesbecauseitisavalueandnotafunction.

Nowtoseeadifferentwaytoimportobjectsfromamodule,createanotherfile,namedsimpledemo2.mjs,containingthefollowing:

import{

defaultassimple,hello,next

}from'./simple2.mjs';

console.log(hello());

console.log(next());

Page 138: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

console.log(next());

console.log(simple());

console.log(next());

console.log(next());

console.log(next());

Inthiscase,eachimportedobjectisitsownthingratherthanbeingattachedtoanotherobject.Insteadofwritingsimple2.next(),yousimplywritenext().Theasclauseisawaytodeclareanalias,ifnothingelsesoyoucanusethedefaultexport.Wealreadyusedanasclauseearlier,anditcanbeusedinotherinstanceswhereyouwishtoprovideanaliasforthevaluebeingexportedorimported.

Node.jsmodulescanbeusedfromES2015.mjscode.Createafilenamedls.mjs,containingthefollowing:

import_fsfrom'fs';

constfs=_fs.promises;

importutilfrom'util';

(async()=>{

constfiles=awaitfs.readdir('.');

for(letfnoffiles){

console.log(fn);

}

})().catch(err=>{console.error(err);});

Youcannot,however,requireanES2015moduleintoregularNode.jscode.ThelookupalgorithmforES2015modulesisdifferent,andaswementionedearlier,ES2015modulesareloadedasynchronously.

Anotherwrinkleishandlingthefs.promisessubmodule.Weareusingthatsubmoduleintheexample,buthow?Thisimportstatementdoesnotwork:

import{promisesasfs}from'fs';

Thisfailsasso:

Page 139: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

$node--experimental-modulesls.mjs

(node:45186)ExperimentalWarning:TheESMmoduleloaderisexperimental.

file:///Volumes/Extra/book-4th/chap03/ls.mjs:1

import{promisesasfs}from'fs';

^^^^^^^^

SyntaxError:Therequestedmodule'fs'doesnotprovideanexportnamed

'promises'

atModuleJob._instantiate(internal/modules/esm/module_job.js:89:21)

Thatleavesuswiththisconstruct:

import_fsfrom'fs';

constfs=_fs.promises;

Executingthescriptgivesthefollowing:

$node--experimental-modulesls.mjs

(node:65359)ExperimentalWarning:TheESMmoduleloaderisexperimental.

(node:37671)ExperimentalWarning:Thefs.promisesAPIisexperimental

ls.mjs

module1.js

module2.js

simple.js

simple2.mjs

simpledemo.mjs

simpledemo2.mjs

ThelastthingtonoteaboutES2015modulecodeisthatimportandexportstatementsmustbetop-levelcode.Evenputtinganexportinsideasimpleblocklikethis:

{

exportconstmeaning=42;

}

Resultsinanerror:

$node--experimental-modulesbadexport.mjs

(node:67984)ExperimentalWarning:TheESMmoduleloaderisexperimental.

SyntaxError:Unexpectedtokenexport

Page 140: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

atModuleJob.loaders.set[asmoduleProvider]

(internal/loader/ModuleRequest.js:32:13)

at<anonymous>

WhilethereareafewmoredetailsaboutES2015modules,thesearetheirmostimportantattributes.

Page 141: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

JSONmodulesNode.jssupportsusingrequire('pathto/file-name.json')toimportaJSONfile.Itisequivalenttothiscode:

constfs=require('fs');

module.exports=JSON.parse(

fs.readFileSync('pathto/file-name.json','utf8'));

Thatis,theJSONfileisreadsynchronously,andthetextisparsedasJSON.Theresultantobjectisavailableastheobjectexportedfromthemodule.Createafilenameddata.json,containingthefollowing:{"hello":"Hello,world!","meaning":42}

Nowcreateafilenamedshowdata.js,containingthefollowing:

constutil=require('util');

constdata=require('./data');

console.log(util.inspect(data));

Itwillexecuteasfollows:

$nodeshowdata.js

{hello:'Hello,world!',meaning:42}

Theutil.inspectfunctionisausefulwaytopresentanobjectinaneasy-to-readfashion.

Page 142: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SupportingES6modulesonolderNode.jsversionsWhilesupportforES6modulesarrivedasanexperimentalfeatureinNode.js8.5,therearetwowaystousethesemodulesonearlierNode.jsimplementations.

OnemethodistousetheBabeltranspilertorewriteES6codesoitcanexecuteonolderNode.jsversions.Foranexample,seehttps://blog.revillweb.com/using-es2015-es6-modules-with-babel-6-3ffc0870095b.

ThebettermethodistheesmpackageintheNode.jsregistry.Simplydothefollowing:

$nvminstall6

Downloadingandinstallingnodev6.14.1...

Downloadinghttps://nodejs.org/dist/v6.14.1/node-v6.14.1-darwin-x64.tar.xz...

########################################################################

100.0%

Computingchecksumwithshasum-a256

Checksumsmatched!

Nowusingnodev6.14.1(npmv3.10.10)

$nvmuse6

Nowusingnodev6.14.1(npmv3.10.10)

$npminstallesm

...npmoutput

$node--requireesmsimpledemo.mjs

Hello,world!

11

24

24

39

416

525

42

Tousethismodule,onesimplyinvokesrequire('esm')once,andES6

Page 143: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

modulesareretrofittedintoNode.js.The--requireflagautomaticallyloadsthenamedmodule.Withoutrewritingthecode,wecanselectivelyusetheesmmodulewiththisthecommand-lineoption.

ThisexampledemonstratesretrofittingES6modulesintoolderNode.jsreleases.Tosuccessfullyexecutethels.mjsexamplewemusthavesupportforasync/awaitfunctions,andarrowfunctions.SinceNode.js6.xdoesnotsupporteither,thels.mjsexamplewillfail,andwillnecessitaterewritingsuchcode:

$node--version

v6.14.1

$node-resmls.mjs

UsersDavid/chap03/ls.mjs:5

(async()=>{

^

SyntaxError:Unexpectedtoken(

atexports.runInThisContext(vm.js:53:16)

atModule._compile(module.js:373:25)

Formoreinformation,see:https://medium.com/web-on-the-edge/es-modules-in-node-today-32cff914e4b.Thatarticledescribesanolderreleaseoftheesmmodule,atthetimenamed@std/esm.

Page 144: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Demonstratingmodule-levelencapsulationAkeyattributeofmodulesisencapsulation.Theobjectsthatarenotexportedfromthemoduleareprivatetothemodule,andcannotbeaccessedfromcodeoutsidethemodule.Toreiterate,modulesaretreatedasiftheywerewrittenasfollows:

(function(){...contentsofmodulefile...})();

ThisJavaScriptidiomdefinesananonymousprivatescope.Anythingdeclaredwithinthatscopecannotbeaccessedbycodeoutsidethescope.Thatis,unlesssomecodemakesobjectreferencesavailabletoothercodeoutsidethisprivatescope.That'swhatthemodule.exportsobjectdoes:itisamechanismforthemoduleauthortoexposeobjectreferencesfromthemodule.Othercodecanthenaccessresourcesinsidethemoduleinacontrolledfashion.

Thetop-levelvariablesinsideamodulelookliketheyexistintheglobalscope.InsteadofbeingtrulyGlobal,they'resafelyprivatetothemoduleandarecompletelyinaccessibletoothercode.

Let'stakealookatapracticaldemonstrationofthatencapsulation.Createafilenamedmodule1.js,containingthefollowing:

constA="valueA";

constB="valueB";

exports.values=function(){

return{A:A,B:B};

}

Then,createafilenamedmodule2.js,containingthefollowing:

Page 145: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

constutil=require('util');

constA="adifferentvalueA";

constB="adifferentvalueB";

constm1=require('./module1');

console.log(`A=${A}B=${B}values=${util.inspect(m1.values())}`);

console.log(`${m1.A}${m1.B}`);

constvals=m1.values();

vals.B="somethingcompletelydifferent";

console.log(util.inspect(vals));

console.log(util.inspect(m1.values()));

Then,runitasfollows(youmusthaveNode.jsalreadyinstalled):

$nodemodule2.js

A=adifferentvalueAB=adifferentvalueBvalues={A:'valueA',B:'value

B'}

undefinedundefined

{A:'valueA',B:'somethingcompletelydifferent'}

{A:'valueA',B:'valueB'}

Thisartificialexampledemonstratesencapsulationofthevaluesinmodule1.jsfromthoseinmodule2.js.TheAandBvaluesinmodule1.jsdon'toverwriteAandBinmodule2.jsbecausethey'reencapsulatedwithinmodule1.js.Thevaluesfunctioninmodule1.jsdoesallowcodeinmodule2.jsaccesstothevalues;however,module2.jscannotdirectlyaccessthosevalues.Wecanmodifytheobjectmodule2.jsreceivedfrommodule1.js.Butdoingsodoesnotchangethevalueswithinmodule1.js.

Page 146: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

FindingandloadingCommonJSandJSONmodulesusingrequireWehavetalkedaboutseveraltypesofmodules:CommonJS,JSON,ES2015,andnativecodemodules.AllbuttheES2015modulesareloadedusingtherequirefunction.Thatfunctionhasaverypowerfulandflexiblealgorithmforlocatingmoduleswithinadirectoryhierarchy.Thisalgorithm,coupledwiththenpmpackagemanagementsystem,givestheNode.jsplatformalotofpowerandflexibility.

Page 147: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

FilemodulesTheCommonJSandES2015moduleswe'vejustlookedatarewhattheNode.jsdocumentationdescribesasafilemodule.Suchmodulesarecontainedwithinasinglefile,whosefilenameendswith.js,.mjs,.json,or.node.ThelatterarecompiledfromCorC++sourcecode,orevenotherlanguagessuchasRust,whiletheformerareofcoursewritteninJavaScriptorJSON.

We'vealreadylookedatseveralexamplesofusingthesemodules,aswellasthedifferencebetweentheCommonJSformattraditionallyusedinNode.js,andthenewES2015modulesthatarenowsupported.

Page 148: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ModulesbakedintoNode.jsbinarySomemodulesarepre-compiledintotheNode.jsbinary.ThesearethecoreNode.jsmodulesdocumentedontheNode.jswebsiteathttps://nodejs.org/api/index.html.

TheystartoutassourcecodewithintheNode.jsbuildtree.Thebuildprocesscompilesthemintothebinarysothatthemodulesarealwaysavailable.

Page 149: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

DirectoriesasmodulesAmodulecancontainawholedirectorystructurefullofstuff.Stuffhereisatechnicaltermreferringtointernalfilemodules,datafiles,templatefiles,documentation,tests,assets,andmore.Oncestoredwithinaproperlyconstructeddirectorystructure,Node.jswilltreattheseasamodulethatsatisfiesarequire('moduleName')call.

Thismaybealittleconfusingbecausethewordmoduleisbeingoverloadedwithtwomeanings.Insomecases,amoduleisafile,andinothercases,amoduleisadirectorycontainingoneormorefilemodules.

Inmostcases,adirectory-as-modulecontainsapackage.jsonfile.Thisfilecontainsdataaboutthemodule(knownaspackage)thatNode.jsuseswhileloadingthemodule.TheNode.jsruntimerecognizesthesetwofields:{name:"myAwesomeLibrary",main:"./lib/awesome.js"}

Ifthispackage.jsonfileisinadirectorynamedawesomelib,thenrequire('./awesomelib')willloadthefilemodulein./awesomelib/lib/awesome.js.

Ifthereisnopackage.json,thenNode.jswilllookforeitherindex.jsorindex.node.Insuchacase,require('./awesomelib')willloadthefilemodulein./awesomelib/index.js.

Ineithercase,thedirectorymodulecaneasilycontainotherfilemodules.Themodulethat'sinitiallyloadedwouldsimplyuserequire('./anotherModule')oneormoretimestoloadother,privatemodules.

Thenpmpackagemanagementsystemcanrecognizealotmoredatainthepackage.jsonfile.Thatincludesthepackagename,itsauthor,thehomepageURL,theissue-queueURL,packagedependencies,andmore.We'llgooverthislater.

Page 150: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ModuleidentifiersandpathnamesGenerallyspeaking,themodulenameisapathname,butwiththefileextensionremoved.Earlier,whenwewroterequire('./simple'),Node.jsknewtoadd.jstothefilenameandloadinsimple.js.Similarly,Node.jswouldrecognizesimple.jsonorsimple.nodeasthefilenamelegitimatelysatisfyingrequire('./simple').

Therearethreetypesofmoduleidentifiers:relative,absolute,andtop-level:

Relativemoduleidentifiers:Thesebeginwith./or../andabsoluteidentifiersbeginwith/.ThemodulenameisidenticalwithPOSIXfilesystemsemantics.Theresultantpathnameisinterpretedrelativetothelocationofthefilebeingexecuted.Thatis,amoduleidentifierbeginningwith./islookedforinthecurrentdirectory,whereasonestartingwith../islookedforintheparentdirectory.

Absolutemoduleidentifiers:Thesebeginwith/andare,ofcourse,lookedforintherootofthefilesystem,butthisisnotarecommendedpractice.

Top-levelmoduleidentifiers:Thesebeginwithnoneofthosestringsandarejustthemodulename,orelsemodule-name/path/to/module.Thesemustbestoredinanode_modulesdirectory,andtheNode.jsruntimehasanicelyflexiblealgorithmforlocatingthecorrectnode_modulesdirectory:

Page 151: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Inthecaseofmodule-name/path/to/modulespecifiers,whatwillbeloadedisamodulepath/to/modulewithinthetop-levelmodulenamedmodule-name

Thebaked-inmodulesarespecifiedusingtop-levelmodulenames

Thesearchbeginsinthedirectorycontainingthefilecallingrequire().Ifthatdirectorycontainsanode_modulesdirectory,whichthencontainseitheramatchingdirectorymoduleoramatchingfilemodule,thenthesearchissatisfied.Ifthelocalnode_modulesdirectorydoesnotcontainasuitablemodule,ittriesagainintheparentdirectory,anditwillcontinueupwardinthefilesystemuntiliteitherfindsasuitablemoduleoritreachestherootdirectory.

Thatis,witharequirecallinhomedavid/projects/notes/foo.js,thefollowingdirectorieswillbeconsulted:

homedavid/projects/notes/node_modules

homedavid/projects/node_modules

homedavid/node_modules

homenode_modules

/node_modules

Ifthemoduleisnotfoundthroughthissearch,thereareglobalfoldersinwhichmodulescanbelocated.ThefirstisspecifiedintheNODE_PATHenvironmentvariable.Thisisinterpretedasacolon-delimitedlistofabsolutepathssimilartothePATHenvironmentvariable.OnWindows,theelementsofNODE_PATHareofcourseseparatedbysemicolons.Node.jswillsearchthosedirectoriesforamatchingmodule.

Page 152: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TheNODE_PATHapproachisnotrecommended,becauseofsurprisingbehaviorwhichcanhappenifpeopleareunawarethatthisvariablemustbeset.IfaspecificmodulelocatedinaspecificdirectoryreferencedinNODE_PATHisrequiredforproperfunction,andthevariableisnotset,theapplicationwilllikelyfail.AstheTwelve-FactorApplicationmodelsuggests,itisbestforalldependenciestobeexplicitlydeclared,andwithNode.jsthatmeanslistingalldependenciesinthepackage.jsonsothatnpmoryarncanmanagethedependencies.

Thisvariablewasimplementedbeforethemoduleresolutionalgorithmjustdescribedwasfinalized.Becauseofthatalgorithm,NODE_PATHislargelyunnecessary.

Therearethreeadditionallocationsthatcanholdmodules:

$HOME/.node_modules

$HOME/.node_libraries

$PREFIX/lib/node

Inthiscase,$HOMEiswhatyouexpect,theuser'shomedirectory,and$PREFIXisthedirectorywhereNode.jsisinstalled.

Somearebeginningtorecommendagainstusingglobalmodules.Therationaleisthedesireforrepeatabilityanddeployability.Ifyou'vetestedanapp,andallitscodeisconvenientlylocatedwithinadirectorytree,youcancopythattreefordeploymenttoothermachines.But,whatiftheappdependedonsomeotherfilethatwasmagicallyinstalledelsewhereonthesystem?Willyouremembertodeploysuchfiles?

Page 153: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

AnexampleofapplicationdirectorystructureLet'stakealookatthefilesystemstructureofatypicalNode.jsExpressapplication:

ThisisanExpressapplication(we'llstartusingExpressinChapter5,YourFirstExpressApplication)containingafewmodulesinstalledinthenode_modulesdirectory.Oneofthose,Express,hasitsownnode_modulesdirectorycontainingacoupleofmodules.

Page 154: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Forapp.jstoloadmodels-sequelize/notes.js,itusesthefollowingrequirecall:

constnotesModel=require('./models-sequelize/notes');

Thisisarelativemoduleidentifier,wherethepathnameisresolvedrelativetothedirectorycontainingthefilemakingthereference.

Usethefollowingcodetodothereverseinmodels-sequelize/notes.js:

constapp=require('../app');

Again,thisisarelativemoduleidentifier,thistimeresolvedrelativetothesubdirectorycontainingmodels-sequelize/notes.js.

Anyreferencetoatop-levelmoduleidentifierwillfirstlookinthenode_modulesdirectoryshownhere.Thisdirectoryispopulatedfromthedependencieslistedinthepackage.json,aswe'llseeinafewpages:

constexpress=require('express');

constfavicon=require('serve-favicon');

constlogger=require('morgan');

constcookieParser=require('cookie-parser');

constbodyParser=require('body-parser');

AllofthesearetypicalmodulesincludedinanExpressapplication.Mostofthemarereadilyvisibleinthescreenshotshownearlier.What'sloadedisthemainfileinthecorrespondingsubdirectoryofnode_modules,forexample,node_modules/express/index.js.

ButtheapplicationcannotdirectlyreferencethedependenciesoftheExpressmodulethatareinitsinternalnode_modulesdirectory.Themodulesearchalgorithmonlymovesupwardinthefilesystem;itdoesnotdescendintosubsidiarydirectorytrees.

Onesideeffectoftheupwardsearchdirectionisthehandlingofconflictingdependencies.

Page 155: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Supposetwomodules(modulesAandB)listedadependencyonthesamemodule(C)?Inthenormalcase,thetwodependenciesonmoduleCcouldbehandledbythesameinstanceofthatmodule.Aswe'llseeinafewpages,npm'sdependencylistinpackage.jsoncanuselooseorpreciseversionnumberreferences.DependingonthecurrentversionnumberformoduleC,modulesAandBmay,ormaynot,beinagreementastowhichversiontouse.Iftheydonotagree,npmcanarrangethemoduleinstallationsuchthatbothmoduleAandBgettheversionofmoduleCtheydependon,withouteithersteppingontheother.IfbothareagreeablewiththesamemoduleCinstance,onlyonecopywillbeinstalled,butiftheydisagreethennpmwillinstalltwocopies.ThetwocopieswillbelocatedsuchthatthemodulesearchalgorithmwillcauseeachmoduletofindthecorrectversionofmoduleC.

Let'stryaconcreteexampletoclarifywhatwasjustsaid.Inthescreenshotearlier,youseetwoinstancesofthecookiemodule.Wecanusenpmtoqueryforallreferencestothismodule:

$npmlscookie

[email protected]/chap05/notes

├─┬[email protected]

│└──[email protected]

└─┬[email protected]

└──[email protected]

Thissaysthecookie-parsermoduledependsonversion0.1.3ofcookie,whileExpressdependsonversion0.1.5.Howdoesnpmavoidproblemswiththesetwoconflictingversions?Byputtingoneinsidethenode_modulesdirectoryinsidetheexpressmodule.Thisway,whenExpressreferstothismodule,itwillusethe0.1.5instanceinitsownnode_modulesdirectory,whilethecookie-parsermodulewillusethe0.1.3instanceinthetop-levelnode_modulesdirectory.

Page 156: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

FindingandloadingES6modulesusingimportTheimportstatementisusedtoloadES6modules,anditonlyworksinsideanES6module.BecauseES6modulesareloadedasynchronously,therequire()statementcannotloadES6modules.Aswesaidearlier,ES6modulesarerecognizedbyNode.jsbythe.mjsextension.TheECMAScriptTC-39committeehas(orplansto)officiallyregisterthatfileextensionwiththerecognizedauthoritiessothatregulartoolswillrecognizebothfileextensionsasJavaScript.

ThemodulespecifieronehandstotheimportstatementisinterpretedasaURL.Forthetimebeing,Node.jswillonlyacceptfile:URLbecauseofthesecurityimplicationsofloadingmodulesovertheInternet.Becauseit'saURL,somecharacterssuchas:,?,#,or%mustreceivespecialtreatment.Forexample:

import'./foo?search';

import'./foo#hash';

Thesearevalidmodulespecifierswhere?searchand#hashhavethesortofmeaningyou'dexpectinaURL.SolongasNode.jsonlysupportsfile:URLforimportstatements,wecannotmakeuseofthatfeature,butwehavetokeepitinmindandavoidusingthesestringsinmoduleURL.

OnecaninstallcustommoduleloaderhooksthatcouldconceivablyusethoseURLpartsforsomepurpose.

Themodulesearchalgorithmissimilartowhatwedescribedforrequire.Ifthespecifierbeginswith./,../,or/,thespecifierisinterpretedasapathname.Otherwise,itisinterpretedasatop-levelmodulesimilartotherequirestatement,withonebigdifference.Theimportstatementwillnot

Page 157: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

searchforaglobalmodule.Thisisfrownedon,butifonemustuseaglobalmodule,thatcanbeaccomplishedwithasymboliclink.

Fordocumentation,seehttps://nodejs.org/api/esm.html.

Page 158: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

HybridCommonJS/Node.js/ES6modulescenariosWe'vegoneovertheformatforCommonJS/Node.jsmodules,theformatforES6modules,andthealgorithmforlocatingandimportingboth.Thelastthingtocoveristhosehybridsituationswhereourcodewillusebothmoduleformatsatthesametime.

Asapracticalmatter,ES6modulesareverynewtotheNode.jsplatform,andthereforewehavealargebodyofexistingcodewrittenasCommonJS/Node.jsmodules.ManytoolsintheNode.jsmarkethaveimplementationdependenciesontheCommonJSformat.Thismeanswe'llbefacingsituationswhereES6moduleswillneedtouseCommonJSmodules,andviceversa:

CommonJSmoduleloadsotherCommonJSmoduleswithrequire()

CommonJSmodulecannotloadES6modules—exceptfortwomethods:

Dynamicimport,alsoknownasimport(),canloadanES6moduleasanasynchronousoperation

The@std/esmpackagesuppliesarequire()functionwithonethatcanloadES6modulesasanasynchronousoperation

ES6modulesloadotherES6moduleswithimport,withthefull

Page 159: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

semanticsoftheimportstatement

ES6modulesloadCommonJSmodulesusingimport

Therefore,outofthebox,threeofthescenariosaredirectlysupported.Thefourthissupportedwithaworkaroundmodule.

WhenanES6moduleloadsaCommonJSmodule,itsmodule.exportsobjectisexposedasthedefaultexportofthemodule.Thismeansyourcodeusesthispattern:

importcjsModulefrom'common-js-module';

...

cjsModule.functionName();

ThisisextremelysimilartousingaCommonJSmoduleinanotherCommonJSmodule.Youaresimplytransliteratingtherequire()callintoanimportstatement.

Page 160: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Dynamicimportswithimport()ES6modulesdonotcoveralltherequirementstofullyreplaceNode.js/CommonJSmodules.OneofthemissingcapabilitiesisbeingaddressedwiththeDynamicImportfeaturecurrentlyonitswaythroughtheTC-39committee.

SupportfordynamicimportslandedinNode.js9.7.Seethedocumentationat:https://github.com/tc39/proposal-dynamic-import.

We'llusedynamicimportstosolveanissueinChapter7,DataStorageandRetrieval,aboutdynamicallychoosingthemoduletoload.Innormalusageoftherequire()statement,canuseasimplestringliteraltospecifythemodulename.Butitisalsopossibletouseastringliteraltocomputethemodulename,likeso:

//Node.jsdynamicallydeterminedmoduleloading

constmoduleName=require(`../models/${process.env.MODEL_NAME}`);

WeusedthistechniqueinearliereditionsofthisbooktodynamicallychoosebetweenseveralimplementationsofthesamemodelAPI.TheES6importstatementdoesnotsupportanythingbutasimplestringliteral,andthereforecannotcomputethemodulespecifierlikethisexample.

Withdynamicimports,wehaveanimport()functionwherethemodulespecifierisaregularstring,lettingusmakeasimilardynamicchoiceofmodule.Unliketherequire()function,whichissynchronous,import()isasynchronous,andreturnsaPromise.Hence,it'snotadirectreplacementforrequire()inthatit'snotterriblyusefulasatop-levelfunction.You'llseehowtouseitinChapter7,DataStorageandRetrieval.

PerhapsthemostimportantfeatureitbringsisthatCommonJSmodules

Page 161: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

canuseimport()toloadanES6module.

Page 162: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Theimport.metafeatureAnothernewfeature,import.meta,ismakingitswaythroughtheTC-39committee,andisbeingimplementedforNode.js10.x.ItisanobjectexistingwithinthescopeofanES6moduleprovidingsomemetadataaboutthemodule.Seehttps://github.com/tc39/proposal-import-meta.

Apartialimplementation,supportingjustimport.meta.url,haslandedintheNode.jssource.Itsuserequiresthe--harmony-import-metacommand-lineflag.Thecontentofimport.meta.urlisafullyqualifiedfile:URLforthecurrentmodule,suchasfile:///Users/david/chap10/notes/app.mjs.

WherethisbecomesimportantisthatES6modulesdonotsupportthe__dirname,__filename,andotherglobalvariablesusedhistoricallyinNode.jsmodules.The__dirnamevariableisroutinelyusedtoreadinresourcedatafromfilessittinginthepackagedirectory.Itisintendedthatforsuchcases,oneparsesthedirectorynameoutofimport.meta.url.

Page 163: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

npm-theNode.jspackagemanagementsystemAsdescribedinChapter2,SettingupNode.js,npmisapackagemanagementanddistributionsystemforNode.js.Ithasbecomethedefactostandardfordistributingmodules(packages)forusewithNode.js.Conceptually,it'ssimilartotoolssuchasapt-get(Debian),rpm/yum(RedHat/Fedora),MacPorts(macOS),CPAN(Perl),orPEAR(PHP).ItspurposeispublishinganddistributingNode.jspackagesovertheInternetusingasimplecommand-lineinterface.Withnpm,youcanquicklyfindpackagestoservespecificpurposes,downloadthem,installthem,andmanagepackagesyou'vealreadyinstalled.

ThenpmapplicationextendsonthepackageformatforNode.js,whichinturnislargelybasedontheCommonJSpackagespecification.Itusesthesamepackage.jsonfilethat'ssupportednativelybyNode.js,butwithadditionalfieldstobuildinadditionalfunctionality.

Page 164: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ThenpmpackageformatAnnpmpackageisadirectorystructurewithapackage.jsonfiledescribingthepackage.Thisisexactlywhatwasreferredtoearlierasadirectorymodule,exceptthatnpmrecognizesmanymorepackage.jsontagsthanNode.jsdoes.Thestartingpointfornpm'spackage.jsonaretheCommonJSPackages/1.0specification.Thedocumentationfornpm'spackage.jsonimplementationisaccessedusingthefollowingcommand:

$npmhelpjson

Abasicpackage.jsonfileisasfollows:

{"name":"packageName",

"version":"1.0",

"main":"mainModuleName",

"modules":{

"mod1":"lib/mod1",

"mod2":"lib/mod2"

}

}

ThefileisinJSONformat,which,asaJavaScriptprogrammer,youshouldbefamiliarwith.

Themostimportanttagsarenameandversion.ThenamewillappearinURLsandcommandnames,sochooseonethat'ssafeforboth.Ifyoudesiretopublishapackageinthepublicnpmrepository,it'shelpfultocheckwhetheraparticularnameisalreadybeingusedathttp://npmjs.comorwiththefollowingcommand:

$npmsearchpackageName

Page 165: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Themaintagistreatedthesameaswediscussedintheprevioussectionondirectorymodules.Itdefineswhichfilemodulewillbereturnedwheninvokingrequire('packageName').Packagescancontainmanymoduleswithinthemselvesandtheycanbelistedinthemoduleslist.

Packagescanbebundledastar-gziparchives(tarballs),especiallytosendthemovertheinternet.

Apackagecandeclaredependenciesonotherpackages.Thatway,npmcanautomaticallyinstallothermodulesrequiredbythemodulebeinginstalled.Dependenciesaredeclaredasfollows:

"dependencies":{

"foo":"1.0.0-2.x.x",

"bar":">=1.0.2<2.1.2"

}

Thedescriptionandkeywordfieldshelppeoplefindthepackagewhensearchinginannpmrepository(https://www.npmjs.com/).Ownershipofapackagecanbedocumentedinthehomepage,author,orcontributorsfields:

"description":"Mywonderfulpackagethatwalksdogs",

"homepage":"http://npm.dogs.org/dogwalker/",

"author":"[email protected]"

Somenpmpackagesprovideexecutableprogramsmeanttobeintheuser'sPATH.Thesearedeclaredusingthebintag.It'samapofcommandnamestothescriptthatimplementsthatcommand.Thecommandscriptsareinstalledintothedirectorycontainingthenodeexecutableusingthenamegiven:

bin:{

'nodeload.js':'./nodeload.js',

'nl.js':'./nl.js'

},

Thedirectoriestagdescribesthepackagedirectorystructure.Thelib

Page 166: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

directoryisautomaticallyscannedformodulestoload.Thereareotherdirectorytagsforbinaries,manuals,anddocumentation:

directories:{lib:'./lib',bin:'./bin'},

Thescripttagsarescriptcommandsrunatvariouseventsinthelifecycleofthepackage.Theseeventsincludeinstall,activate,uninstall,update,andmore.Formoreinformationaboutscriptcommands,usethefollowingcommand:

$npmhelpscripts

We'vealreadyusedthescriptsfeaturewhenshowinghowtosetupBabel.We'llusetheselaterforautomatingthebuild,test,andexecutionprocesses.

Thiswasonlyatasteofthenpmpackageformat;seethedocumentation(npmhelpjson)formore.

Page 167: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

FindingnpmpackagesBydefault,npmmodulesareretrievedovertheinternetfromthepublicpackageregistrymaintainedonhttp://npmjs.com.Ifyouknowthemodulename,itcanbeinstalledsimplybytypingthefollowing:

$npminstallmoduleName

Butwhatifyoudon'tknowthemodulename?Howdoyoudiscovertheinterestingmodules?Thewebsitehttp://npmjs.compublishesasearchableindexofthemodulesinthatregistry.

Thenpmpackagealsohasacommand-linesearchfunctiontoconsultthesameindex:

Ofcourse,uponfindingamodule,it'sinstalledasfollows:

$npminstallacoustid

Page 168: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Afterinstallingamodule,youmaywanttoseethedocumentation,whichwouldbeonthemodule'swebsite.Thehomepagetaginpackage.jsonliststhatURL.Theeasiestwaytolookatthepackage.jsonfileiswiththenpmviewcommand,asfollows:

$npmviewakasharender

...

{name:'akasharender',

description:'RenderingsupportforgeneratingstaticHTMLwebsites

orEPUBeBooks',

'dist-tags':{latest:'0.6.15'},

versions:

['0.0.1',

...

author:'DavidHerron<[email protected]>

(http://davidherron.com)',

repository:{type:'git',url:

'git://github.com/akashacms/akasharender.git'},

homepage:'http://akashacms.com/akasharender/toc.html',

...

}

Youcanusenpmviewtoextractanytagfrompackage.json,likethefollowing,whichletsyouviewjustthehomepagetag:

$npmviewakasharenderhomepage

http://akashacms.org/akasharender/toc.html

Otherfieldsinthepackage.jsoncanbeviewedbysimplygivingthedesiredtagname.

Page 169: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

<strong>$npmhelp<command>Thehelptextwillbeshownonyourscreen.

Or,seethewebsite:http://docs.npmjs.com</strong>

Page 170: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

<strong>$npminstallexpress

homedavid/projects/notes/

[email protected]

...</strong>

Thenamedmoduleisinstalledinnode_modulesinthecurrentdirectory.Thespecificversioninstalleddependsonanyversionnumberlistedonthecommandline,asweseeinthenextsection.

Page 171: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

InstallingapackagebyversionnumberVersionnumbermatchinginnpmispowerfulandflexible.Thesamesortofversionspecifiersusedinpackage.jsondependenciescanalsobeusedwiththenpminstallcommand:

$npminstallpackage-name@tag

$npminstallpackage-name@version

$npminstallpackage-name@version-range

Thelasttwoarewhattheysoundlike.Youcanspecifyexpress@4.16.2totargetapreciseversion,orexpress@">4.1.0<5.0"totargetarangeofExpressV4versions.

Theversionmatchspecifiersincludethesechoices:

Exactversionmatch:1.2.3

AtleastversionN:>1.2.3

UptoversionN:<1.2.3

Betweentworeleases:>=1.2.3<1.3.0

The@tagattributeisasymbolicnamesuchas@latest,@stable,or@canary.Thepackageownerassignsthesesymbolicnamestospecificversionnumbers,andcanreassignthemasdesired.Theexceptionis@latest,whichisupdatedwheneveranewreleaseofthepackageispublished.

Formoredocumentation,runthesecommands:npmhelpjsonandnpmhelpnpm-dist-tag.

Page 172: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

GlobalpackageinstallsInsomeinstancesyouwanttoinstallamoduleglobally,sothatitcanbeusedfromanydirectory.Forexample,theGruntorGulpbuildtoolsarewidelyuseful,andconceivablyyouwillfinditusefulifthesetoolsareinstalledglobally.Simplyaddthe-goption:

$npminstall-ggrunt-cli

Ifyougetanerror,andyou'reonaUnix-likesystem(Linux/Mac),youmayneedtorunthiswithsudo:

$sudonpminstall-ggrunt-cli

Aglobalinstallismostimportantforthosepackageswhichinstallexecutablecommands.We'llgetintothisshortly.

Ifalocalpackageinstalllandsinnode_modules,wheredoesaglobalpackageinstallland?OnaUnix-likesystemitlandsinPREFIX/lib/node_modules,andonWindowsitlandsinPREFIX/node_modules.InthiscasePREFIXmeansthedirectorywhereNode.jsisinstalled.Youcaninspectthelocationofthisdirectorylikeso:

$npmconfiggetprefix

/Users/david/.nvm/versions/node/v8.9.1

ThealgorithmusedbyNode.jsfortherequirefunctionautomaticallysearchesthisdirectoryforpackagesifthepackageisnotfoundelsewhere.

RememberthatES6modulesdonotsupportglobalpackages.

Page 173: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

AvoidingglobalmoduleinstallationSomeintheNode.jscommunitynowfrownoninstallingapackageglobally.OnerationaleexistsintheTwelveFactormodel.Namely,asoftwareprojectismorereliableifallitsdependenciesareexplicitlydeclared.IfabuildtoolsuchasGruntisrequired,butisnotexplicitlydeclaredinpackage.json,theusersoftheapplicationwouldhavetoreceiveinstructionstoinstallGrunt,andtheywouldhavetofollowthoseinstructions.

Usersbeingusers,theymightskipovertheinstructions,failtoinstallthedependency,andthencomplaintheapplicationdoesn'twork.Surelymostofushavedonethatonceortwice.

It'srecommendedtoavoidthispotentialproblembyinstallingeverythinglocallyviaonemechanism—thenpminstallcommand.

Page 174: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

MaintainingpackagedependencieswithnpmAswementionedearlier,thenpminstallcommandbyitselfinstallsthepackageslistedinthedependenciessectionofpackage.json.Thisiseasyandconvenient.Simplybylistingallthedependencies,it'squickandeasytoinstallthedependenciesrequiredforusingthepackage.Whathappensisnpmlooksinpackage.jsonforthedependenciesordevDependenciesfield,anditwillautomaticallyinstallthementionedpackages.

Youcanmanagethedependenciesmanuallybyeditingpackage.json.Oryoucanusenpmtoassistyouwitheditingthedependencies.Youcanaddanewdependencylikeso:

$npminstallakasharender--save

Inresponse,npmwilladdadependenciestagtopackage.json:

"dependencies":{

"akasharender":"^0.6.15"

}

Now,whenyourapplicationisinstalled,npmwillautomaticallyalsoinstallthatpackagealongwithanydependencieslistedbythatpackage.

ThedevDependenciesaremodulesusedduringdevelopment.Thatfieldisinitializedthesameasabove,butwiththe--save-devflag.

Bydefault,whenannpminstallisrun,moduleslistedinbothdependenciesanddevDependenciesareinstalled.Ofcourse,thepurposeforhavingtwolistsistonotinstallthedevDependenciesinsomecases:

Page 175: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

$npminstall--production

ThisinstallsonlythemoduleslistedindependenciesandnoneofthedevDependenciesmodules.

IntheTwelve-Factorapplicationmodel,it'ssuggestedthatweexplicitlyidentifythedependenciesrequiredbytheapplication.Thiswaywecanreliablybuildourapplication,knowingthatwe'vetestedagainstaspecificsetofdependenciesthatwe'vecarefullyidentified.Byinstallingexactlythedependenciesagainstwhichtheapplicationhasbeentested,wehavemoreconfidenceintheapplication.OntheNode.jsplatform,npmgivesusthisdependenciessection,includingaflexiblemechanismtodeclarecompatiblepackageversionsbytheirversionnumber.

Page 176: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

<strong>$npmconfigsetsavefalse</strong>

Thenpmconfigcommandsupportsalonglistofsettableoptionsfortuningbehaviorofnpm.Seenpmhelpconfigforthedocumentation,andnpmhelp7configforthelistofoptions.

Page 177: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

FixingbugsbyupdatingpackagedependenciesBugsexistineverypieceofsoftware.AnupdatetotheNode.jsplatformmaybreakanexistingpackage,asmightanupgradetopackagesusedbytheapplication.Yourapplicationmaytriggerabuginapackageituses.Intheseandothercases,fixingtheproblemmightbeassimpleasupdatingapackagedependencytoalater(orearlier)version.

Firstidentifywhethertheproblemexistsinthepackageorinyourcode.Afterdeterminingit'saprobleminanotherpackage,investigatewhetherthepackagemaintainershavealreadyfixedthebug.IsthepackagehostedonGitHuboranotherservicewithapublicissuequeue?Lookforanopenissueonthisproblem.Thatinvestigationwilltellyouwhethertoupdatethepackagedependencytoalaterversion.Sometimes,itwilltellyoutoreverttoanearlierversion;forexample,ifthepackagemaintainerintroducedabugthatdoesn'texistinanearlierversion.

Sometimes,youwillfindthatthepackagemaintainersareunpreparedtoissueanewrelease.Insuchacase,youcanforktheirrepositoryandcreateapatchedversionoftheirpackage.

Oneapproachtofixingthisproblemispinningthepackageversionnumbertoonethat'sknowntowork.Youmightknowthatversion6.1.2wasthelastreleaseagainstwhichyourapplicationfunctioned,andthatstartingwithversion6.2.0yourapplicationbreaks.Hence,inpackage.json:"dependencies":{"module1":"6.1.2"}

Thisfreezesyourdependencytothespecificversionnumber.You'refree,then,totakeyourtimeupdatingyourcodetoworkagainstlaterreleasesof

Page 178: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

thatmodule.Onceyourcodeisupdated,ortheupstreamprojectisupdated,changethedependencyappropriately.

Anotherapproachistohostaversionofthepackagesomewhereoutsideofthenpmrepository.Thisiscoveredinalatersection.

Page 179: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

PackagesthatinstallcommandsSomepackagesinstallcommand-lineprograms.Asideeffectofinstallingsuchpackagesisanewcommandthatyoucantypeattheshellpromptoruseinshellscripts.AnexampleisthehexyprogramthatwebrieflyusedinChapter2,SettingupNode.js.AnotherexampleisthewidelyusedGruntorGulpbuildtools.

Thepackage.jsonfileinsuchpackagesspecifiesthecommand-linetoolsthatareinstalled.Thecommandcanbeinstalledtooneoftwoplaces:

GlobalInstall:Itisinstalledeithertoadirectorysuchasusrlocal,ortothebindirectorywhereNode.jswasinstalled.Thenpmbin-gcommandtellsyoutheabsolutepathnameforthisdirectory.

LocalInstall:Tonode_modules/.bininthepackagewherethemoduleisbeinginstalled.Thenpmbincommandtellsyoutheabsolutepathnameforthisdirectory.

Torunthecommand,simplytypethecommandnameatashellprompt.Exceptthere'salittlebitofconfigurationrequiredtomakethatsimple.

Page 180: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ConfiguringthePATHvariabletohandlecommandsinstalledbymodulesTypingthefullpathnameisnotauser-friendlyrequirementtoexecutethecommand.Wewanttousethecommandsinstalledbymodules,andwewantasimpleprocessfordoingso.Meaning,wemustaddanappropriatevalueinthePATHvariable,butwhatisit?

Forglobalpackageinstallations,theexecutablelandsinadirectorythatisprobablyalreadyinyourPATHvariable,likeusrbinorusrlocal/bin.Localpackageinstallationsarewhatrequirespecialhandling.Thefullpathforthenode_modules/.bindirectoryvariesforeachproject,andobviouslyitwon'tworktoaddthefullpathforeverynode_modules/.bindirectorytoyourPATH.

Adding./node_modules/.bintothePATHvariable(or,onWindows,.\node_modules\.bin)worksgreat.AnytimeyourshellisintherootofaNode.jsproject,itwillautomaticallyfindlocally-installedcommandsfromNode.jspackages.

Howwedothisdependsonthecommandshellyouuse,andyouroperatingsystem.

OnaUnix-likesystemthecommandshellsarebashandcsh.YourPATHvariablewouldbesetupinoneoftheseways:

$exportPATH=./node_modules/.bin:${PATH}#bash

$setenvPATH./node_modules/.bin:${PATH}#csh

Thenextstepisaddingthecommandtoyourloginscriptssothevariableisalwaysset.Onbash,addthecorrespondinglinetoyour~/.bashrc,andon

Page 181: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

cshaddittoyour~/.cshrc.

Page 182: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ConfiguringthePATHvariableonWindowsOnWindows,thistaskishandledthroughasystem-widesettingspanel:

Page 183: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ThispaneoftheSystemPropertiespanelisfoundbysearchingforPATHintheWindowsSettingsscreen.ClickontheEnvironmentVariablesbutton,thenselectthePathvariable,andfinallyclickontheEditbutton.InthescreenhereclicktheNewbuttontoaddanentrytothisvariable,andenter.\node_modules\.binasshown.You'llhavetorestartanyopencommandshellwindows.Onceyoudo,theeffectwillbeasshownpreviously.

Page 184: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

AvoidingmodificationstothePATHvariableWhatifyoudon'twanttoaddthesevariablestoyourPATHatalltimes?Thenpm-pathmodulemaybeofinterest.ThisisasmallprogramthatcomputesthecorrectPATHvariableforyourshellandoperatingsystem.Seethepackageathttps://www.npmjs.com/package/npm-path.

Page 185: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Updatingoutdatedpackagesyou'veinstalledThecodercodes,updatingtheirpackage,leavingyouintheirdustunlessyoukeepup.

Tofindoutifyourinstalledpackagesareoutofdate,usethefollowingcommand:$npmoutdated

Thereportshowsthecurrentnpmpackages,thecurrently-installedversion,aswellasthecurrentversioninthenpmrepository.Updatingtheoutdatedpackagesisverysimple:$npmupdateexpress$npmupdate

Page 186: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

InstallingpackagesfromoutsidethenpmrepositoryAsawesomeasthenpmrepositoryis,wedon'twanttopusheverythingwedothroughtheirservice.Thisisespeciallytrueforinternaldevelopmentteamswhocannotpublishtheircodeforalltheworldtosee.Whileyoucanrentorinstallaprivatenpmrepository,there'sanotherway.Packagescanbeinstalledfromotherlocations.Detailsaboutthisareinnpmhelppackage.jsoninthedependenciessection.Someexamplesare:

URL:YoucanspecifyanyURLthatdownloadsatarball,thatis,a.tar.gzfile.Forexample,GitHuborGitLabrepositoriescaneasilyexporttarballURL.SimplygototheReleasestabtofindthem.

GitURL:Similarly,anyGitrepositorycanbeaccessedwiththerightURL.Forexample:

$npminstallgit+ssh://user@hostname:project.git#tag--save

GitHubShortcut:ForGitHubrepositoriesyoucanlistjusttherepositoryspecifier,suchasexpressjs/express.Atagoracommitcanbereferencedusingexpressjs/express#tag-name.

Localfilesystem:YoucaninstallfromalocaldirectoryusingaURLlikethis:file:../../path/to/dir.

Page 187: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you
Page 188: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

InitializinganewnpmpackageIfyouwanttocreateanewpackage,youcancreatethepackage.jsonfilebyhandoryoucangetnpm'shelp.Thenpminitcommandleadsyouthroughalittledialogtogetstartingvaluesforthepackage.jsonfile.

Onceyougetthroughthequestions,thepackage.jsonfileiswrittentodisk.

Expecttohavetoeditthatfileconsiderablybeforepublishingtothenpmrepository.Afewfieldshelpgiveagoodimpressiontofolkslookingatthepackagelistingonnpmjs.com:

Linktothehomepage,andissuequeueURL

Keywords,soitcanbelinkedwithothersimilarpackages

Agooddescriptionthathelpsfolksunderstandthepurpose

AgoodREADME.mdfilesofolkscanreadsomedocumentationrightaway

Page 189: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

"engines":{

"node":">=8.x"

}

This,ofcourse,usesthesameversionnumbermatchingschemediscussedearlier.

Page 190: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

PublishingannpmpackageAllthosepackagesinthenpmrepositorycamefrompeoplelikeyouwithanideaofabetterwayofdoingsomething.Itisveryeasytogetstartedwithpublishingpackages.Onlinedocscanbefoundathttps://docs.npmjs.com/getting-started/publishing-npm-packages.

Youfirstusethenpmaddusercommandtoregisteryourselfwiththenpmrepository.Youcanalsosignupwiththewebsite.Next,youloginusingthenpmlogincommand.

Finally,whilesittinginthepackagerootdirectory,usethenpmpublishcommand.Then,standbacksothatyoudon'tgetstampededbythecrushofthrongingfans.Or,maybenot.Therearealmost600,000packagesintherepository,withalmost400packagesaddedeveryday.Togetyourstostandout,youwillrequiresomemarketingskill,whichisanothertopicbeyondthescopeofthisbook.

Page 191: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ExplicitlyspecifyingpackagedependencyversionnumbersOnefeatureoftheTwelve-Factormethodologyissteptwo,explicitlydeclaringyourdependencies.We'vealreadytouchedonthis,butit'sworthreiteratingandtoseeingnpmmakesthiseasytoaccomplish.

SteponeoftheTwelve-Factormethodologyisensuringthatyourapplicationcodeischeckedintoasourcecoderepository.Youprobablyalreadyknowthis,andevenhavethebestofintentionstoensurethateverythingischeckedin.WithNode.js,eachmoduleshouldhaveitsownrepositoryratherthanputtingeverysinglelastpieceofcodeinonerepository.

Eachmodulecanthenprogressonitsowntimeline.Abreakageinonemoduleiseasytobackoutbychangingtheversiondependencyinpackage.json.

ThisgetsustoTwelve-Factorsteptwo.Therearetwoaspectsofthisstep,oneofwhichisthepackageversioningthatwediscussedpreviously.Thenextisexplicitlydeclaringversionnumbers,whichcanbedeclaredindependenciesanddevDependenciessectionsofpackage.json.Thisensuresthateveryoneontheteamisonthesamepage,developingagainstthesameversionsofthesamemodules.Whenit'stimetodeploytotesting,staging,orproductionservers,andthedeploymentscriptrunsnpminstallornpmupdate,thecodewilluseaknownversionofthemodulethateveryonetestedagainst.

Thelazywayofdeclaringdependenciesisputting*intheversionfield.Thatusesthelatestversioninthenpmrepository.Maybethiswillwork,untilonedaythemaintainersofthatpackageintroduceabug.You'lltypenpmupdate,andallofasuddenyourcodedoesn'twork.You'llheadoverto

Page 192: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

theGitHubsiteforthepackage,lookintheissuequeue,andpossiblyseethatothershavealreadyreportedtheproblemyou'reseeing.Someofthemwillsaythatthey'vepinnedonthepreviousreleaseuntilthisbugisfixed.Whatthatmeansistheirpackage.jsonfiledoesnotdependon*forthelatestversion,butonaspecificversionnumberbeforethebugwascreated.

Don'tdothelazything,dothesmartthing.

Theotheraspectofexplicitlydeclaringdependenciesistonotimplicitlydependonglobalpackages.Earlier,wesaidthatsomeintheNode.jscommunitycautionagainstinstallingmodulesintheglobaldirectories.Thismightseemlikeaneasyshortcuttosharingcodebetweenapplications.Justinstallitglobally,andyoudon'thavetoinstallthecodeineachapplication.

But,doesn'tthatmakedeploymentharder?Willthenewteammemberbeinstructedonallthespecialfilestoinstallhereandtheretomaketheapplicationrun?Willyouremembertoinstallthatglobalmoduleonalldestinationmachines?

ForNode.js,thatmeanslistingallthemoduledependenciesinpackage.json,andthentheinstallationinstructionsaresimplynpminstall,followedperhapsbyeditingaconfigurationfile.

Page 193: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TheYarnpackagemanagementsystemAspowerfulasnpmis,itisnottheonlypackagemanagementsystemforNode.js.BecausetheNode.jscoreteamdoesnotdictateapackagemanagementsystem,theNode.jscommunityisfreetorolluptheirsleevesanddevelopanysystemtheyfeelbest.Thatthevastmajorityofususenpmisatestamenttoitsvalueandusefulness.Butthereisacompetitor.

Yarn(seehttps://yarnpkg.com/en/)isacollaborationbetweenengineersatFacebook,Google,andseveralothercompanies.TheyproclaimthatYarnisultrafast,ultra-secure(byusingchecksumsofeverything),andultrareliable(byusingayarn-lock.jsonfiletorecordprecisedependencies).

Insteadofrunningtheirownpackagerepository,Yarnrunsontopofnpm'spackagerepositoryatnpmjs.com.ThismeansthattheNode.jscommunityisnotforkedbyYarn,butenhancedbyhavinganimprovedpackagemanagementtool.

ThenpmteamrespondedtoYarninnpm@5(alsoknownasnpmversion5)byimprovingperformance,andbyintroducingapackage-lock.jsonfiletoimprovereliability.Thenpmteamhaveannouncedadditionalimprovementsinnpm@6.

Yarnhasbecomeverypopularandiswidelyrecommendedovernpm.Theyperformextremelysimilarfunctions,andtheperformanceisnotthatdifferenttonpm@5.Thecommand-lineoptionsarewordeddifferently.AnimportantbenefitYarnbringstotheNode.jscommunityisthatcompetitionbetweenYarnandnpmseemstobebreedingfasteradvancesinNode.jspackagemanagement.

Togetyoustarted,thesearethemostimportantcommands:

Page 194: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

yarnadd:Addsapackagetouseinyourcurrentpackage

yarninit:Initializesthedevelopmentofapackage

yarninstall:Installsallthedependenciesdefinedinapackage.jsonfile

yarnpublish:Publishesapackagetoapackagemanager

yarnremove:Removesanunusedpackagefromyourcurrentpackage

Runningyarnbyitselfdoestheyarninstallbehavior.ThereareseveralothercommandsinYarn,andyarnhelpwilllistthemall.

Page 195: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SummaryYoulearnedalotinthischapteraboutmodulesandpackagesforNode.js.

Specifically,wecoveredimplementingmodulesandpackagesforNode.js,managinginstalledmodulesandpackages,andsawhowNode.jslocatesmodules.

Nowthatyou'velearnedaboutmodulesandpackages,we'rereadytousethemtobuildapplications,whichisthetopicofthenextchapter.

Page 196: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

HTTPServersandClientsNowthatyou'velearnedaboutNode.jsmodules,it'stimetoputthisknowledgetoworkbybuildingasimpleNode.jswebapplication.Inthischapter,we'llkeeptoasimpleapplication,enablingustoexplorethreedifferentapplicationframeworksforNode.js.Inlaterchapters,we'llbuildsomemorecomplexapplications,butbeforewecanwalk,wemustlearntocrawl.

Wewillcoverthefollowingtopicsinthischapter:

EventEmitters

ListeningtoHTTPeventsandtheHTTPServerobject

HTTPrequestrouting

ES2015templatestrings

Buildingasimplewebapplicationwithnoframeworks

TheExpressapplicationframework

Expressmiddlewarefunctions

Howtodealwithcomputationallyintensivecode

TheHTTPClientobject

CreatingasimpleRESTservicewithExpress

Page 197: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you
Page 198: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SendingandreceivingeventswithEventEmittersEventEmittersareoneofthecoreidiomsofNode.js.IfNode.js'scoreideaisanevent-drivenarchitecture,emittingeventsfromanobjectisoneoftheprimarymechanismsofthatarchitecture.AnEventEmitterisanobjectthatgivesnotifications—events—atdifferentpointsinitslifecycle.Forexample,anHTTPServerobjectemitseventsconcerningeachstageofthestartup/shutdownoftheServerobject,andasHTTPrequestsaremadefromHTTPclients.

ManycoreNode.jsmodulesareEventEmitters,andEventEmittersareanexcellentskeletontoimplementasynchronousprogramming.EventEmittershavenothingtodowithwebapplicationdevelopment,buttheyaresomuchpartoftheNode.jswoodworkthatyoumayskipovertheirexistence.

Inthischapter,we'llworkwiththeHTTPServerandHTTPClientobjects.BotharesubclassesoftheEventEmitterclass,andrelyonittosendeventsforeachstepoftheHTTPprotocol.

Page 199: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

JavaScriptclassesandclassinheritanceBeforegettingstartedontheEventEmitterclass,weneedtotakealookatanotheroftheES2015features:classes.TheJavaScriptlanguagehasalwayshadobjects,andaconceptofaclasshierarchy,butnothingsoformalasinotherlanguages.TheES2015classobjectbuildsontheexistingprototype-basedinheritancemodel,butwithasyntaxlookingverymuchlikeclassdefinitionsinotherlanguages.

Forexample,considerthisclasswe'llbeusinglaterinthebook:

classNote{

constructor(key,title,body){

this._key=key;

this._title=title;

this._body=body;

}

getkey(){returnthis._key;}

gettitle(){returnthis._title;}

settitle(newTitle){returnthis._title=newTitle;}

getbody(){returnthis._body;}

setbody(newBody){returnthis._body=newBody;}

}

Onceyou'vedefinedtheclass,youcanexporttheclassdefinitiontoothermodules:

module.exports.Note=classNote{..}#inCommonJSmodules

exportclassNote{..}#inES6modules

Thefunctionsmarkedwithgetorsetkeywordsaregettersandsetters,usedlikeso:

Page 200: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

varaNote=newNote("key","TheRaininSpain","Fallsmainlyonthe

plain");

varkey=aNote.key;

vartitle=aNote.title;

aNote.title="TheRaininSpain,whichmademewanttocrywithjoy";

Newinstancesofaclassarecreatedwithnew.Youaccessagetterorsetterfunctionasifitisasimplefieldontheobject.Behindthescenes,thegetter/setterfunctionisinvoked.

Theprecedingimplementationisnotthebestbecausethe_titleand_bodyfieldsarepubliclyvisible,andthereisnodatahidingorencapsulation.We'llgooverabetterimplementationlater.

Onetestswhetheragivenobjectisofacertainclassbyusingtheinstanceofoperator:

if(anotherNoteinstanceofNote){

...it'saNote,soactonitasaNote

}

Finally,youdeclareasubclassusingtheextendsoperator,similartowhat'sdoneinotherlanguages:

classLoveNoteextendsNote{

constructor(key,title,body,heart){

super(key,title,body);

this._heart=heart;

}

getheart(){returnthis._heart;}

setheart(newHeart){returnthis._heart=newHeart;}

}

Inotherwords,theLoveNoteclasshasallthefieldsofNote,plusthisnewfieldnamedheart.

Page 201: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TheEventEmitterClassTheEventEmitterobjectisdefinedintheeventsmoduleofNode.js.DirectlyusingtheEventEmitterclassmeansperformingrequire('events').Inmostcases,you'llbeusinganexistingobjectthatusesEventEmitterinternallyandyouwon'trequirethismodule.ButtherearecaseswhereneedsdictateimplementinganEventEmittersubclass.

Createafilenamedpulser.jscontainingthefollowingcode:

constEventEmitter=require('events');

classPulserextendsEventEmitter{

start(){

setInterval(()=>{

console.log(`${newDate().toISOString()}>>>>pulse`);

this.emit('pulse');

console.log(`${newDate().toISOString()}<<<<pulse`);

},1000);

}

}

module.exports=Pulser;

ThisdefinesaPulserclass,whichinheritsfromEventEmitter.InolderNode.jsreleases,thiswouldrequireusingutil.inherits,butthenewclassobjectmakessubclassingmuchsimpler.

Anotherthingtoexamineishowthis.emitinthecallbackfunctionreferstothePulserobject.BeforetheES2015arrowfunction,whenourcallbacksusedaregularfunction,thiswouldnothavereferredtothePulserobject.Instead,itwouldhavereferredtosomeotherobjectrelatedtothesetIntervalfunction.Becauseitisanarrowfunction,thethisinsidethearrowfunctionisthesamethisasintheouterfunction.

Ifyouneededtouseafunctionratherthananarrowfunction,thistrick

Page 202: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

wouldwork:

classPulserextendsEventEmitter{

start(){

varself=this;

setInterval(function(){

self.emit(...);

});

}

}

What'sdifferentistheassignmentofthistoself.Thevalueofthisinsidethefunctionisdifferent,butthevalueofselfremainsthesameineveryenclosedscope.Thiswidely-usedtrickislessnecessarynowthatwehavearrowfunctions.

IfyouwantasimpleEventEmitter,butwithyourownclassname,thebodyoftheextendedclasscanbeempty:

classHeartBeatextendsEventEmitter{}

constbeatMaker=newHeartBeat();

ThepurposeofthePulserclassissendingatimedevent,onceasecond,toanylisteners.ThestartmethodusessetIntervaltokickoffrepeatedcallbackexecution,scheduledforeverysecond,callingemittosendthepulseeventstoanylisteners.

Now,let'sseehowtousethePulserobject.Createanewfile,calledpulsed.js,containing:

constPulser=require('./pulser');

//InstantiateaPulserobject

constpulser=newPulser();

//Handlerfunction

pulser.on('pulse',()=>{

console.log(`${newDate().toISOString()}pulsereceived`);

});

//Startitpulsing

pulser.start();

Page 203: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Here,wecreateaPulserobjectandconsumeitspulseevents.Callingpulser.on('pulse')setsupconnectionsforthepulseeventstoinvokethecallbackfunction.Itthencallsthestartmethodtogettheprocessgoing.

Enterthisintoafileandnamethefilepulsed.js.Whenyourunit,youshouldseethefollowingoutput:

$nodepulsed.js

2017-12-03T06:24:10.272Z>>>>pulse

2017-12-03T06:24:10.275Zpulsereceived

2017-12-03T06:24:10.276Z<<<<pulse

2017-12-03T06:24:11.279Z>>>>pulse

2017-12-03T06:24:11.279Zpulsereceived

2017-12-03T06:24:11.279Z<<<<pulse

2017-12-03T06:24:12.281Z>>>>pulse

2017-12-03T06:24:12.281Zpulsereceived

2017-12-03T06:24:12.282Z<<<<pulse

ThatgivesyoualittlepracticalknowledgeoftheEventEmitterclass.Let'snowlookatitsoperationaltheory.

Page 204: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TheEventEmittertheoryWiththeEventEmitterclass,yourcodeemitseventsthatothercodecanreceive.It'sawayofconnectingtwoseparatedsectionsofyourprogram,kindoflikehowquantumentanglementmeanstwoelectronscancommunicatewitheachotherfromanydistance.Seemssimpleenough.

Theeventnamecanbeanythingthatmakessensetoyou,andyoucandefineasmanyeventnamesasyoulike.Eventnamesaredefinedsimplybycalling.emitwiththeeventname.There'snothingformaltodoandnoregistryofeventnames.Simplymakingacallto.emitisenoughtodefineaneventname.

Byconvention,theeventnameerrorindicateserrors.

Anobjectsendseventsusingthe.emitfunction.Eventsaresenttoanylistenersthathaveregisteredtoreceiveeventsfromtheobject.Theprogramregisterstoreceiveaneventbycallingthatobject's.onmethod,givingtheeventnameandaneventhandlerfunction.

Thereisnocentraldistributionpointforallevents.Instead,eachinstanceofanEventEmitterobjectmanagesitsownsetoflistenersanddistributesitseventstothoselisteners.

Often,itisrequiredtosenddataalongwithanevent.Todoso,simplyaddthedataasargumentstothe.emitcall,asfollows:

this.emit('eventName',data1,data2,..);

Whentheprogramreceivesthatevent,thedataappearsasargumentstothecallbackfunction.Yourprogramwouldlistentosuchaneventasfollows:

Page 205: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

emitter.on('eventName',(data1,data2,...theArgs)=>{

//actonevent

});

Thereisnohandshakingbetweeneventreceiversandtheeventsender.Thatis,theeventsendersimplygoesonwithitsbusiness,anditgetsnonotificationsaboutanyeventsreceived,anyactiontaken,oranyerrorthatoccurred.

Inthisexample,weusedanotheroftheES2015features,therestoperator,shownhereas...theArgs.Therestoperatorcatchesanynumberofremainingfunctionparametersintoanarray.SinceEventEmittercanpassalonganynumberofparameters,andtherestoperatorcanautomaticallyreceiveanynumberofparameters,it'samatchmadeinheaven,orelseintheTC-39committee.

Page 206: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

HTTPserverapplicationsTheHTTPserverobjectisthefoundationofallNode.jswebapplications.TheobjectitselfisveryclosetotheHTTPprotocol,anditsuserequiresknowledgeofthatprotocol.Inmostcases,you'llbeabletouseanapplicationframeworksuchasExpressthathidestheHTTPprotocoldetails,allowingtheprogrammertofocusonbusinesslogic.

WealreadysawasimpleHTTPserverapplicationinChapter2,SettingupNode.js,whichisasfollows:

consthttp=require('http');

http.createServer((req,res)=>{

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

res.end('Hello,World!\n');

}).listen(8124,'127.0.0.1');

console.log('Serverrunningathttp://127.0.0.1:8124');

Thehttp.createServerfunctioncreatesanhttp.Serverobject.BecauseitisanEventEmitter,thiscanbewritteninanotherwaytomakethatfactexplicit:

consthttp=require('http');

constserver=http.createServer();

server.on('request',(req,res)=>{

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

res.end('Hello,World!\n');

});

server.listen(8124,'127.0.0.1');

console.log('Serverrunningathttp://127.0.0.1:8124');

Therequesteventtakesafunction,whichreceivesrequestandresponseobjects.Therequestobjecthasdatafromthewebbrowser,whiletheresponseobjectisusedtogatherthedatatobesentintheresponse.Thelistenfunctioncausestheservertostartlisteningandarrangingtodispatchaneventforeveryrequestarrivingfromawebbrowser.

Page 207: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Now,let'slookatsomethingmoreinterestingwithdifferentactionsbasedontheURL.

Createanewfile,namedserver.js,containingthefollowingcode:

consthttp=require('http');

constutil=require('util');

consturl=require('url');

constos=require('os');

constserver=http.createServer();

server.on('request',(req,res)=>{

varrequrl=url.parse(req.url,true);

if(requrl.pathname===''){

res.writeHead(200,{'Content-Type':'texthtml'});

res.end(

`<html><head><title>Hello,world!</title></head>

<body><h1>Hello,world!</h1>

<p><ahref='osinfo'>OSInfo<a></p>

</body></html>`);

}elseif(requrl.pathname==="osinfo"){

res.writeHead(200,{'Content-Type':'texthtml'});

res.end(

`<html><head><title>OperatingSystemInfo</title></head>

<body><h1>OperatingSystemInfo</h1>

<table>

<tr><th>TMPDir</th><td>${os.tmpdir()}</td></tr>

<tr><th>HostName</th><td>${os.hostname()}</td></tr>

<tr><th>OSType</th><td>${os.type()}${os.platform()}${os.arch()}

${os.release()}</td></tr>

<tr><th>Uptime</th><td>${os.uptime()}${util.inspect(os.loadavg())}</td></tr>

<tr><th>Memory</th><td>total:${os.totalmem()}free:${os.freemem()}</td>

</tr>

<tr><th>CPU's</th><td><pre>${util.inspect(os.cpus())}</pre></td></tr>

<tr><th>Network</th><td><pre>${util.inspect(os.networkInterfaces())}</pre>

</td></tr>

</table>

</body></html>`);

}else{

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

res.end("badURL"+req.url);

}

});

server.listen(8124);

console.log('listeningtohttp://localhost:8124');

Page 208: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Torunit,typethefollowingcommand:

$nodeserver.js

listeningtohttp://localhost:8124

ThisapplicationismeanttobesimilartoPHP'ssysinfofunction.Node'sosmoduleisconsultedtoprovideinformationabouttheserver.Thisexamplecaneasilybeextendedtogatherotherpiecesofdataabouttheserver:

Page 209: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Acentralpartofanywebapplicationisthemethodofroutingrequeststorequesthandlers.Therequestobjecthasseveralpiecesofdataattachedtoit,twoofwhichareusefulforroutingrequests:therequest.urlandrequest.methodfields.

Inserver.js,weconsulttherequest.urldatatodeterminewhichpagetoshow,afterparsing(usingurl.parse)toeasethedigestionprocess.Inthis

Page 210: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

case,wecandoasimplecomparisonofthepathnametodeterminewhichhandlermethodtouse.

SomewebapplicationscareabouttheHTTPverb(GET,DELETE,POST,andsoon)usedandmustconsulttherequest.methodfieldoftherequestobject.Forexample,POSTisfrequentlyusedforFORMsubmissions.

ThepathnameportionoftherequestURLisusedtodispatchtherequesttothecorrecthandler.Whilethisroutingmethod,basedonsimplestringcomparison,willworkforasmallapplication,it'llquicklybecomeunwieldy.LargerapplicationswillusepatternmatchingtousepartoftherequestURLtoselecttherequesthandlerfunctionandotherpartstoextractrequestdataoutoftheURL.We'llseethisinactionwhilelookingatExpresslaterintheGettingstartedwithExpresssection.

AsearchforaURLmatchinthenpmrepositoryturnsupseveralpromisingpackagesthatcouldbeusedtoimplementrequestmatchingandrouting.AframeworklikeExpresshasthiscapabilityalreadybakedinandtested.

IftherequestURLisnotrecognized,theserversendsbackanerrorpageusinga404resultcode.Theresultcodeinformsthebrowseraboutthestatusoftherequest,wherea200codemeanseverythingisfine,anda404codemeanstherequestedpagedoesn'texist.Thereare,ofcourse,manyotherHTTPresponsecodes,eachwiththeirownmeaning.

Page 211: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ES2015multilineandtemplatestringsThepreviousexampleshowedtwoofthenewfeaturesintroducedwithES2015,multilineandtemplatestrings.Thefeatureismeanttosimplifyourlifewhilecreatingtextstrings.

Theexistingstringrepresentationsusesinglequotesanddoublequotes.Templatestringsaredelimitedwiththebacktickcharacterthat'salsoknownasthegraveaccent:

`templatestringtext`

BeforeES2015,onewaytoimplementamultilinestringwasthisconstruct:

["<html><head><title>Hello,world!</title></head>",

"<body><h1>Hello,world!</h1>",

"<p><ahref='osinfo'>OSInfo<a></p>",

"</body></html>"]

.join('\n')

Yes,thatwasthecodeusedinthesameexampleinpreviousversionsofthisbook.ThisiswhatwecandowithES2015:

`<html><head><title>Hello,world!</title></head>

<body><h1>Hello,world!</h1>

<p><ahref='osinfo'>OSInfo<a></p>

</body></html>`

Thisismoresuccinctandstraightforward.Theopeningquoteisonthefirstline,theclosingquoteonthelastline,andeverythinginbetweenis

Page 212: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

partofourstring.

Therealpurposeofthetemplatestringsfeatureissupportingstringswherewecaneasilysubstitutevaluesdirectlyintothestring.Mostotherprogramminglanguagessupportthisability,andnowJavaScriptdoestoo.

Pre-ES2015,aprogrammercouldhavewrittencodelikethis:

[...

"<tr><th>OSType</th><td>{ostype}{osplat}{osarch}{osrelease}</td></tr>"

...].join('\n')

.replace("{ostype}",os.type())

.replace("{osplat}",os.platform())

.replace("{osarch}",os.arch())

.replace("{osrelease}",os.release())

Again,thisisextractedfromthesameexampleinpreviousversionsofthisbook.Withtemplatestrings,thiscanbewrittenasfollows:

`...<tr><th>OSType</th><td>${os.type()}${os.platform()}${os.arch()}

${os.release()}</td></tr>...`

Withinatemplatestring,thepartwithinthe${..}bracketsisinterpretedasanexpression.Itcanbeasimplemathematicalexpression,avariablereference,or,asinthiscase,afunctioncall.

Thelastthingtomentionisamatterofindentation.Innormalcoding,oneindentsalongargumentlisttothesamelevelasthecontainingfunctioncall.But,forthesemultilinestringexamples,thetextcontentisflushwithcolumnzero.What'sup?

Thismayimpedethereadabilityofyourcode,soit'sworthweighingcodereadabilityagainstanotherissue:excesscharactersintheHTMLoutput.TheblankswewouldusetoindentthecodeforreadabilitywillbecomepartofthestringandwillbeoutputintheHTML.Bymakingthecodeflushwithcolumnzero,wedon'taddexcessblankstotheoutputatthecostofsomecodereadability.

Page 213: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Thisapproachalsocarriesasecurityrisk.Haveyouverifiedthedataissafe?Thatitwillnotformthebasisofasecurityattack?Inthiscase,we'redealingwithsimplestringsandnumberscomingfromasafedatasource.ThereforethiscodeisassafeastheNode.jsruntime.Whataboutuser-suppliedcontent,andtheriskthatanefarioususermightsupplyinsecurecontentimplantingsomekindofmalwareintotargetcomputers?

Forthisandmanyotherreasons,itisoftensafertouseanexternaltemplateengine.ApplicationslikeExpressmakeiteasytodoso.

Page 214: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

HTTPSniffer–listeningtotheHTTPconversationTheeventsemittedbytheHTTPServerobjectcanbeusedforadditionalpurposesbeyondtheimmediatetaskofdeliveringawebapplication.ThefollowingcodedemonstratesausefulmodulethatlistenstoalltheHTTPServerevents.Itcouldbeausefuldebuggingtool,whichalsodemonstrateshowHTTPserverobjectsoperate.

Node.js'sHTTPServerobjectisanEventEmitterandtheHTTPSniffersimplylistenstoeveryserverevent,printingoutinformationpertinenttoeachevent.

Whatwe'reabouttodois:

1. Createamodule,httpsniffer,thatprintsinformationaboutHTTPrequests.

2. Addthatmoduletotheserver.jsscriptwejustcreated.3. RerunthatservertoviewatraceofHTTPactivity.

Createafilenamedhttpsniffer.jscontainingthefollowingcode:

constutil=require('util');

consturl=require('url');

consttimestamp=()=>{returnnewDate().toISOString();}

exports.sniffOn=function(server){

server.on('request',(req,res)=>{

console.log(`${timestamp()}e_request`);

console.log(`${timestamp()}${reqToString(req)}`);

});

Page 215: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

server.on('close',errno=>{console.log(`${timestamp()}e_close

${errno}`);});

server.on('checkContinue',(req,res)=>{

console.log(`${timestamp()}e_checkContinue`);

console.log(`${timestamp()}${reqToString(req)}`);

res.writeContinue();

});

server.on('upgrade',(req,socket,head)=>{

console.log(`${timestamp()}e_upgrade`);

console.log(`${timestamp()}${reqToString(req)}`);

});

server.on('clientError',()=>{console.log(`${timestamp()}

e_clientError`);});

};

constreqToString=exports.reqToString=(req)=>{

varret=`req${req.method}${req.httpVersion}${req.url}`+'\n';

ret+=JSON.stringify(url.parse(req.url,true))+'\n';

varkeys=Object.keys(req.headers);

for(vari=0,l=keys.length;i<l;i++){

varkey=keys[i];

ret+=`${i}${key}:${req.headers[key]}`+'\n';

}

if(req.trailers)ret+=util.inspect(req.trailers)+'\n';

returnret;

};

Thatwasalotofcode!ButthekeytoitisthesniffOnfunction.WhengivenanHTTPServerobject,itusesthe.onfunctiontoattachlistenerfunctionsthatprintdataabouteachemittedevent.ItgivesafairlydetailedtraceofHTTPtrafficonanapplication.

Inordertouseit,simplyinsertthiscodejustbeforethelistenfunctioninserver.js:

require('./httpsniffer').sniffOn(server);

server.listen(8124);

console.log('listeningtohttp://localhost:8124');

Withthisinplace,runtheserveraswedidearlier.Youcanvisithttp://localhost:8124/inyourbrowserandseethefollowingconsoleoutput:

$nodeserver.js

Page 216: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

listeningtohttp://localhost:8124

2017-12-03T19:21:33.162Zrequest

2017-12-03T19:21:33.162ZrequestGET1.1/

{"protocol":null,"slashes":null,"auth":null,"host":null,"port":null,"hostname

":null,"hash":null,"search":"","query":{},"pathname":"/","path":"","href":""}

0host:localhost:8124

1upgrade-insecure-requests:1

2accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

3user-agent:Mozilla/5.0(Macintosh;IntelMacOSX10_11_6)

AppleWebKit/604.3.5(KHTML,likeGecko)Version/11.0.1Safari/604.3.5

4accept-language:en-us

5accept-encoding:gzip,deflate

6connection:keep-alive

{}

2017-12-03T19:21:42.154Zrequest

2017-12-03T19:21:42.154ZrequestGET1.1/osinfo

{"protocol":null,"slashes":null,"auth":null,"host":null,"port":null,"hostname

":null,"hash":null,"search":"","query":

{},"pathname":"/osinfo","path":"osinfo","href":"osinfo"}

0host:localhost:8124

1connection:keep-alive

2upgrade-insecure-requests:1

3accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

4user-agent:Mozilla/5.0(Macintosh;IntelMacOSX10_11_6)

AppleWebKit/604.3.5(KHTML,likeGecko)Version/11.0.1Safari/604.3.5

5referer:http://localhost:8124/

6accept-language:en-us

7accept-encoding:gzip,deflate

{}

YounowhaveatoolforsnoopingonHTTPServerevents.Thissimpletechniqueprintsadetailedlogofeventdata.ThepatterncanbeusedforanyEventEmitterobject.YoucanusethistechniqueasawaytoinspecttheactualbehaviorofEventEmitterobjectsinyourprogram.

Page 217: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

WebapplicationframeworksTheHTTPServerobjectisveryclosetotheHTTPprotocol.Whilethisispowerfulinthesamewaythatdrivingastickshiftcargivesyoulow-levelcontroloverthedrivingexperience,typicalwebapplicationprogrammingisbetterdoneatahigherlevel.Doesanybodyuseassemblylanguagetowritewebapplications?It'sbettertoabstractawaytheHTTPdetailsandconcentrateonyourapplication.

TheNode.jsdevelopercommunityhasdevelopedquiteafewapplicationframeworkstohelpwithdifferentaspectsofabstractingawayHTTPprotocoldetails.Ofthem,Expressisthemostpopular,andKoa(http://koajs.com/)shouldbeconsideredbecauseitwasdevelopedbythesameteamandhasfullyintegratedsupportforasyncfunctions.

TheExpressJSWikihasalistofframeworksbuiltontopofExpressJS,ortoolsthatworkwithit.Thisincludestemplateengines,middlewaremodules,andmore.TheExpressJSWikiislocatedathttps://github.com/expressjs/express/wiki.

Onereasontouseawebframeworkisthattheyoftenprovidethebestpracticesusedinwebapplicationdevelopmentforover20years.Theusualbestpracticesincludethefollowing:

ProvidingapageforbadURLs(the404page)

ScreeningURLsandformsforanyinjectedscriptingattacks

Supportingtheuseofcookiestomaintainsessions

Loggingrequestsforbothusagetrackinganddebugging

Authentication

Page 218: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Handlingstaticfiles,suchasimages,CSS,JavaScript,orHTML

Providingcachecontrolheaderstocachingproxies

Limitingthingssuchaspagesizeorexecutiontime

WebframeworkshelpyouinvestyourtimeinthetaskwithoutgettinglostinthedetailsofimplementingHTTPprotocol.Abstractingawaydetailsisatime-honoredwayforprogrammerstobemoreefficient.Thisisespeciallytruewhenusingalibraryorframeworkprovidingprepackagedfunctionsthattakecareofthedetails.

Page 219: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

GettingstartedwithExpressExpressisperhapsthemostpopularNode.jswebappframework.It'ssopopularthatit'spartoftheMEANStackacronym.MEANreferstoMongoDB,ExpressJS,AngularJS,andNode.js.ExpressisdescribedasbeingSinatra-like,referringtoapopularRubyapplicationframework,andthatitisn'tanopinionatedframework,meaningtheframeworkauthorsdon'timposetheiropinionsaboutstructuringanapplication.ThismeansExpressisnotatallstrictabouthowyourcodeisstructured;youjustwriteitthewayyouthinkisbest.

YoucanvisitthehomepageforExpressathttp://expressjs.com/.

Shortly,we'llimplementasimpleapplicationtocalculateFibonaccinumbersusingExpress,andinlaterchapters,we'lldoquiteabitmorewithExpress.We'llalsoexplorehowtomitigatetheperformanceproblemsfromcomputationallyintensivecodewediscussedearlier.

Asofwritingthisbook,Express4.16isthecurrentversion,andExpress5isinAlphatesting.AccordingtotheExpressJSwebsite,thereareveryfewdifferencesbetweenExpress4andExpress5.

Let'sstartbyinstallingtheexpress-generator.Whilewecanjuststartwritingsomecode,theexpress-generatorprovidesablankstartingapplication.We'lltakethatandmodifyit.

Installitusingthefollowingcommands:

$mkdirfibonacci

$cdfibonacci

[email protected]

ThisisdifferentfromthesuggestedinstallationmethodontheExpress

Page 220: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

website,whichwastousethe-gtagforaglobalinstall.We'realsousinganexplicitversionnumbertoensurecompatibility.Asofwritingthisbook,[email protected],oneshouldbeabletousethe5.xversionwiththefollowinginstructions.

Earlier,wediscussedhowmanynowrecommendagainstinstallingmodulesglobally.IntheTwelve-Factormodel,it'sstronglyrecommendedtonotinstallglobaldependencies,andthat'swhatwe'redoing.

Theresultisthatanexpresscommandisinstalledinthe./node_modules/.bindirectory:

$lsnode_modules/.bin/

express

Runtheexpresscommandlikeso:

$./node_modules/.bin/express--help

Usage:express[options][dir]

Options:

-h,--helpoutputusageinformation

-V,--versionoutputtheversionnumber

-e,--ejsaddejsenginesupport(defaultstojade)

--hbsaddhandlebarsenginesupport

-H,--hoganaddhogan.jsenginesupport

-c,--css<engine>addstylesheet<engine>support

(less|stylus|compass|sass)(defaultstoplaincss)

--gitadd.gitignore

-f,--forceforceonnon-emptydirectory

Weprobablydon'twanttotype./node_modules/.bin/expresseverytimeweruntheexpress-generatorapplicationor,forthatmatter,anyoftheotherapplicationsthatprovidecommand-lineutilities.ReferbacktothediscussioninChapter3,Node.jsModulesaboutaddingthatdirectorytothePATHvariable.

Nowthatyou'veinstalledexpress-generatorinthefibonaccidirectory,useit

Page 221: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

tosetuptheblankframeworkapplication:

$./node_modules/.bin/express--view=hbs--git.

destinationisnotempty,continue?[y/N]y

create:.

create:./package.json

create:./app.js

create:./.gitignore

create:./public

create:./routes

create:./routes/index.js

create:./routes/users.js

create:./views

create:./views/index.hbs

create:./views/layout.hbs

create:./views/error.hbs

create:./bin

create:./bin/www

create:./public/javascripts

create:./public/images

create:./public/stylesheets

create:./public/stylesheets/style.css

installdependencies:

$cd.&&npminstall

runtheapp:

$DEBUG=fibonacci:*npmstart

$npmuninstallexpress-generator

added83packagesandremoved5packagesin4.104s

Thiscreatedabunchoffilesforus,whichwe'llwalkthroughinaminute.Thenode_modulesdirectorystillhastheexpress-generatormodule,whichisnownotuseful.Wecanjustleaveitthereandignoreit,orwecanaddittothedevDependenciesofthepackage.jsonitgenerated.Alternatively,wecanuninstallitasshownhere.

Thenextthingtodoisruntheblankapplicationinthewaywe'retold.Thecommandshown,npmstart,reliesonasectionofthesuppliedpackage.jsonfile:

Page 222: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

"scripts":{

"start":"node./bin/www"

},

Thenpmtoolsupportsscriptsthatarewaystoautomatevarioustasks.We'llusethiscapabilitythroughoutthebooktodovariousthings.WhentheTwelve-FactorApplicationmodelsuggestsautomatingallyouradministrativetasks,thenpmscriptsfeatureisanexcellentmechanismtodoso.MostnpmscriptsarerunwiththenpmrunscriptNamecommand,butthestartcommandisexplicitlyrecognizedbynpmandcanberunasshownpreviously.

Thestepsare:

1. Installthedependenciesnpminstall.2. Starttheapplicationusingnpmstart.3. Optionallymodifypackage.jsontoalwaysrunwithdebugging.

Toinstallthedependencies,andruntheapplication,typethesecommands:

$npminstall

$DEBUG=fibonacci:*npmstart

>[email protected]/chap04/fibonacci

>node./bin/www

fibonacci:serverListeningonport3000+0ms

SettingtheDEBUGvariablethiswayturnsonsomedebuggingoutput,whichincludesthismessageaboutlisteningonport3000.Otherwise,wearen'ttoldthisinformation.Thissyntaxiswhat'susedintheBashshelltorunacommandwithanenvironmentvariable.Ifyougetanerrortryrunningjust"npmstart"thenreadthenextsection.

Wecanmodifythesuppliednpmstartscripttoalwaysruntheappwithdebuggingenabled.Changethescriptssectiontothefollowing:

Page 223: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

"scripts":{

"start":"DEBUG=fibonacci:*node./bin/www"

},

Sincetheoutputsaysitislisteningonport3000,wedirectourbrowsertohttp://localhost:3000/andseethefollowingoutput:

Page 224: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SettingenvironmentvariablesinWindowscmd.execommandlineIfyou'reonWindowsthepreviousexamplemayhavefailedwithanerrorthatDEBUGisnotaknowncommand.TheproblemisthattheWindowsshell,thecmd.exeprogram,doesnotsupporttheBashcommand-linestructure.

AddingVARIABLE=valueatthebeginningofacommand-lineisspecifictosomeshells,likeBash,onLinuxandmacOS.Itsetsthatenvironmentvariableonlyforthecommand-linebeingexecuted,andisaveryconvenientwaytotemporarilyoverrideenvironmentvariablesforaspecificcommand.

Clearlyasolutionisrequiredifyourpackage.jsonistobeusableacrossdifferentoperatingsystems.

Thebestsolutionappearstobethecross-envpackageinthenpmrepository,see:https://www.npmjs.com/package/cross-envWiththispackageinstalled,commandsinthescriptssectioninpackage.jsoncansetenvironmentvariablesjustasinBashonLinux/macOS.Theusagelookslikeso:

"scripts":{

"start":"cross-envDEBUG=fibonacci:*node./bin/www"

},

"dependencies":{

...

"cross-env":"5.1.x"

}

Thenthecommandisexecutedasso:

Page 225: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

C:\Users\david\Documents\chap04\fibonacci>npminstall

...outputfrominstallingpackages

C:\Users\david\Documents\chap04\fibonacci>npmrunstart

>[email protected]:\Users\david\Documents\chap04\fibonacci

>cross-envDEBUG=fibonacci:*node./bin/www

fibonacci:serverListeningonport3000+0ms

GET/30490.597ms--

GET/stylesheets/style.css30414.480ms--

GET/fibonacci20084.726ms-503

GET/stylesheets/style.css3044.465ms--

GET/fibonacci?fibonum=225001069.049ms-327

GET/stylesheets/style.css3042.601ms--

Page 226: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

WalkingthroughthedefaultExpressapplicationWehaveaworking,blankExpressapplication;let'slookatwhatwasgeneratedforus.We'redoingthistofamiliarizeourselveswithExpressbeforedivingintostartcodingourFibonacciapplication.

Becauseweusedthe--view=hbsoption,thisapplicationissetuptousetheHandlebars.jstemplateengine.HandlebarswasbuiltontopofMustache,andwasoriginallydesignedforuseinthebrowser;formoreinformationseeitshomepageathttp://handlebarsjs.com/.TheversionshownherehasbeenpackagedforusewithExpress,andisdocumentedathttps://github.com/pillarjs/hbs.

Generallyspeaking,atemplateenginemakesitpossibletoinsertdataintogeneratedwebpages.TheExpressJSWikihasalistoftemplateenginesforExpresshttps://github.com/expressjs/express/wiki#template-engines.

Theviewsdirectorycontainstwofiles,error.hbsandindex.hbs.ThehbsextensionisusedforHandlebarsfiles.Anotherfile,layout.hbs,isthedefaultpagelayout.Handlebarshasseveralwaystoconfigurelayouttemplatesandevenpartials(snippetsofcodewhichcanbeincludedanywhere).

Theroutesdirectorycontainstheinitialroutingsetup,thatis,thecodetohandlespecificURLs.We'llmodifytheselater.

Thepublicdirectorywillcontainassetsthattheapplicationdoesn'tgenerate,butaresimplysenttothebrowser.What'sinitiallyinstalledisaCSSfile,public/stylesheets/style.css.

Thepackage.jsonfilecontainsourdependenciesandothermetadata.

Page 227: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Thebindirectorycontainsthewwwscriptthatwesawearlier.That'saNode.jsscript,whichinitializestheHTTPServerobjects,startsitlisteningonaTCPport,andcallsthelastfilewe'lldiscuss,app.js.ThesescriptsinitializeExpress,hookuptheroutingmodules,anddootherthings.

There'salotgoingoninthewwwandapp.jsscripts,solet'sstartwiththeapplicationinitialization.Let'sfirsttakealookatacoupleoflinesinapp.js:

varexpress=require('express');

...

varapp=express();

...

module.exports=app;

Thismeansthatapp.jsisamodulethatexportstheobjectreturnedbytheexpressmodule.Itdoesn'tstarttheHTTPserverobject,however.

Now,let'sturntothewwwscript.Thefirstthingtoseeisthatitstartswiththisline:

#!/usr/bin/envnode

ThisisaUnix/Linuxtechniquetomakeacommandscript.Itsaystorunthefollowingasascriptusingthenodecommand.Inotherwords,wehaveNode.jscodeandwe'reinstructingtheoperatingsystemtoexecutethatcodeusingtheNode.jsruntime:

$ls-lbin/www

-rwx------1davidstaff1595Feb51970bin/www

Wecanalsoseethatthescriptwasmadeexecutablebyexpress-generator.

Itcallstheapp.jsmoduleasfollows:

varapp=require('../app');

...

Page 228: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

varport=normalizePort(process.env.PORT||'3000');

app.set('port',port);

...

varserver=http.createServer(app);

...

server.listen(port);

server.on('error',onError);

server.on('listening',onListening);

Weseewhereport3000comesfrom;it'saparametertothenormalizePortfunction.WealsoseethatsettingthePORTenvironmentvariablewilloverridethedefaultport3000.Andfinally,weseethattheHTTPServerobjectiscreatedhere,andistoldtousetheapplicationinstancecreatedinapp.js.Tryrunningthefollowingcommand:

$PORT=4242DEBUG=fibonacci:*npmstart

Theapplicationnowtellsyouthatit'slisteningonport4242,whereyoucanponderthemeaningoflife.

Theappobjectisnextpassedtohttp.createServer().AlookintheNode.jsdocumentationtellsusthisfunctiontakesarequestListener,whichissimplyafunctionthattakestherequestandresponseobjectswe'veseenpreviously.Therefore,theappobjectissuchafunction.

Finally,thewwwscriptstartstheserverlisteningontheportwespecified.

Let'snowwalkthroughapp.jsinmoredetail:

app.set('views',path.join(__dirname,'views'));

app.set('viewengine','hbs');

ThistellsExpresstolookfortemplatesintheviewsdirectoryandtousetheEJStemplatingengine.

Theapp.setfunctionisusedforsettingapplicationproperties.It'llbeusefultobrowsetheAPIdocumentationaswegothrough(http://expressjs.com/en/4x/api.html).

Page 229: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Nextisaseriesofapp.usecalls:

app.use(logger('dev'));

app.use(bodyParser.json());

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

app.use(cookieParser());

app.use(express.static(path.join(__dirname,'public')));

app.use('',routes);

app.use('users',users);

Theapp.usefunctionmountsmiddlewarefunctions.ThisisanimportantpieceofExpressjargonwewilldiscussshortly.Atthemoment,let'ssaythatmiddlewarefunctionsareexecutedduringtheprocessingofroutes.Thismeansallthefeaturesnamedhereareenabledinapp.js:

LoggingisenabledusingtheMorganrequestlogger.Visithttps://www.npmjs.com/package/morganforitsdocumentation.

Thebody-parsermodulehandlesparsingHTTPrequestbodies.Visithttps://www.npmjs.com/package/body-parserforitsdocumentation.

Thecookie-parsermoduleisusedtoparseHTTPcookies.Visithttps://www.npmjs.com/package/cookie-parserforitsdocumentation.

Astaticfilewebserverisconfiguredtoservetheassetfilesinthepublicdirectory.

Tworoutermodules,routesandusers,tosetupwhichfunctionshandlewhichURLs.

Page 230: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TheExpressmiddlewareLet'sroundoutthewalkthroughofapp.jsbydiscussingwhatmiddlewarefunctionsdoforourapplication.Wehaveanexampleattheendofthescript:

app.use(function(req,res,next){

varerr=newError('Notfound');

err.status=404;

next(err);

});

Thecommentsayscatch404andforwardtoerrorhandler.Asyouprobablyknow,anHTTP404statusmeanstherequestedresourcewasnotfound.Weneedtotelltheusertheirrequestwasn'tsatisfied,andmaybeshowthemapictureofaflockofbirdspullingawhaleoutoftheocean.Thisisthefirststepindoingso.Beforegettingtothelaststepofreportingthiserror,youmustlearnhowmiddlewareworks.

Wedohaveamiddlewarefunctionrightinfrontofus.Refertoitsdocumentationathttp://expressjs.com/en/guide/writing-middleware.html.

Middlewarefunctionstakethreearguments.Thefirsttwo,requestandresponse,areequivalenttotherequestandresponseoftheNode.jsHTTPrequestobject.However,Expressexpandstheobjectswithadditionaldataandcapabilities.Thelast,next,isacallbackfunctioncontrollingwhentherequest-responsecycleends,anditcanbeusedtosenderrorsdownthemiddlewarepipeline.

Theincomingrequestgetshandledbythefirstmiddlewarefunction,thenthenext,thenthenext,andsoon.Eachtimetherequestistobepasseddownthechainofmiddlewarefunctions,thenextfunctioniscalled.Ifnextiscalledwithanerrorobject,asshownhere,anerrorisbeingsignaled.

Page 231: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Otherwise,thecontrolsimplypassestothenextmiddlewarefunctioninthechain.

Whathappensifnextisnotcalled?TheHTTPrequestwillhangbecausenoresponsehasbeengiven.Amiddlewarefunctiongivesaresponsewhenitcallsfunctionsontheresponseobject,suchasres.sendorres.render.

Forexample,considertheinclusionofapp.js:

app.get('/',function(req,res){res.send('HelloWorld!');});

Thisdoesnotcallnext,butinsteadcallsres.send.Thisisthecorrectmethodofendingtherequest-responsecycle,bysendingaresponse(res.send)totherequest.Ifneithernextnorres.sendiscalled,therequestnevergetsaresponse.

Hence,amiddlewarefunctiondoesoneofthefollowingfourthings:

Executesitsownbusinesslogic.Therequestloggermiddlewareshownearlierisanexample.

Modifiestherequestorresponseobjects.Boththebody-parserandcookie-parserdoso,lookingfordatatoaddtotherequestobject.

Callsnexttoproceedtothenextmiddlewarefunctionorelsesignalsanerror.

Sendsaresponse,endingthecycle.

Theorderingofmiddlewareexecutiondependsontheorderthey'readdedtotheappobject.Thefirstaddedisexecutedfirst,andsoon.

Page 232: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

MiddlewareandrequestpathsWe'veseentwokindsofmiddlewarefunctionssofar.Inone,thefirstargumentisthehandlerfunction.Intheother,thefirstargumentisastringcontainingaURLsnippet,andthesecondargumentisthehandlerfunction.

What'sactuallygoingonisapp.usehasanoptionalfirstargument:thepaththemiddlewareismountedon.ThepathisapatternmatchagainsttherequestURL,andthegivenfunctionistriggerediftheURLmatchesthepattern.There'sevenamethodtosupplynamedparametersintheURL:

app.use('userprofile/:id',function(req,res,next){

userProfiles.lookup(req.params.id,(err,profile)=>{

if(err)returnnext(err);

//dosomethingwiththeprofile

//Suchasdisplayittotheuser

res.send(profile.display());

});

});

Thispathspecificationhasapattern,:id,andthevaluewilllandinreq.params.id.Inthisexample,we'resuggestingauserprofilesservice,andthatforthisURLwewanttodisplayinformationaboutthenameduser.

AnotherwaytouseamiddlewarefunctionisonaspecificHTTPrequestmethod.Withapp.use,anyrequestwillbematched,butintruth,GETrequestsaresupposedtobehavedifferentlytoPOSTrequests.Youcallapp.METHODwhereMETHODmatchesoneoftheHTTPrequestverbs.Thatis,app.getmatchestheGETmethod,app.postmatchesPOST,andsoon.

Finally,wegettotherouterobject.ThisisakindofmiddlewareusedexplicitlyforroutingrequestsbasedontheirURL.Takealookatroutes/users.js:

Page 233: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

varexpress=require('express');

varrouter=express.Router();

router.get('/',function(req,res,next){

res.send('respondwitharesource');

});

module.exports=router;

Wehaveamodulewhoseexportsobjectisarouter.Thisrouterhasonlyoneroute,butitcanhaveanynumberofroutesyouthinkisappropriate.

Backinapp.js,thisisaddedasfollows:

app.use('/users',users);

Allthefunctionswediscussedfortheappobjectapplytotherouterobject.Iftherequestmatches,therouterisgiventherequestforitsownchainofprocessingfunctions.AnimportantdetailisthattherequestURLprefixisstrippedwhentherequestispassedtotherouterinstance.

You'llnoticethattherouter.getinusers.jsmatches'/'andthatthisrouterismountedon'/users'.Ineffect,thatrouter.getmatches/usersaswell,butbecausetheprefixwasstripped,itspecifies'/'instead.Thismeansaroutercanbemountedondifferentpathprefixeswithouthavingtochangetherouterimplementation.

Page 234: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ErrorhandlingNow,wecanfinallygetbacktothegeneratedapp.js,the404Errorpagenotfound,andanyothererrorstheapplicationmightwanttoshowtotheuser.

Amiddlewarefunctionindicatesanerrorbypassingavaluetothenextfunctioncall.OnceExpressseesanerror,itwillskipanyremainingnon-errorrouting,anditwillonlypassittoerrorhandlersinstead.Anerrorhandlerfunctionhasadifferentsignaturethanwhatwesawearlier.

Inapp.js,whichwe'reexamining,thisisourerrorhandler:

app.use(function(err,req,res,next){

res.status(err.status||500);

res.render('error',{

message:err.message,

error:{}

});

});

Errorhandlerfunctionstakefourparameters,witherraddedtothefamiliarreq,res,andnext.Forthishandler,weuseres.statustosettheHTTPresponsestatuscode,andweuseres.rendertoformatanHTMLresponseusingtheviews/error.hbstemplate.Theres.renderfunctiontakesdata,renderingitwithatemplatetoproduceHTML.

Thismeansanyerrorinourapplicationwilllandhere,bypassinganyremainingmiddlewarefunctions.

Page 235: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

CalculatingtheFibonaccisequencewithanExpressapplicationTheFibonaccinumbersaretheintegersequence:0,1,1,2,3,5,8,13,21,34,...

Eachentryinthelististhesumoftheprevioustwoentriesinthelist.Thesequencewasinventedin1202byLeonardoofPisa,whowasalsoknownasFibonacci.OnemethodtocalculateentriesintheFibonaccisequenceistherecursivealgorithmweshowedearlier.WewillcreateanExpressapplicationthatusestheFibonacciimplementationandthenexploreseveralmethodstomitigateperformanceproblemsincomputationallyintensivealgorithms.

Let'sstartwiththeblankapplicationwecreatedinthepreviousstep.WehadyounamethatapplicationFibonacciforareason.Wewerethinkingahead.

Inapp.js,makethefollowingchangestothetopportionofthefile:

constexpress=require('express');

consthbs=require('hbs');

constpath=require('path');

constfavicon=require('serve-favicon');

constlogger=require('morgan');

constcookieParser=require('cookie-parser');

constbodyParser=require('body-parser');

constindex=require('./routes/index');

constfibonacci=require('./routes/fibonacci');

constapp=express();

Page 236: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

//viewenginesetup

app.set('views',path.join(__dirname,'views'));

app.set('viewengine','hbs');

hbs.registerPartials(path.join(__dirname,'partials'));

//uncommentafterplacingyourfaviconin/public

//app.use(favicon(path.join(__dirname,'public','favicon.ico')));

app.use(logger('dev'));

app.use(bodyParser.json());

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

app.use(cookieParser());

app.use(express.static(path.join(__dirname,'public')));

app.use('/',index);

app.use('/fibonacci',fibonacci);

Mostofthisiswhatexpress-generatorgaveus.Thevarstatementshavebeenchangedtoconst,forthatlittleteensybitofextracomfort.Weexplicitlyimportedthehbsmodulesowecoulddosomeconfiguration.AndweimportedaroutermoduleforFibonacci,whichwe'llseeinaminute.

FortheFibonacciapplication,wedon'tneedtosupportusers,andthereforedeletedthatroutingmodule.Thefibonaccimodule,whichwe'llshownext,servestoqueryanumberforwhichwe'llcalculatetheFibonaccinumber.

Inthetop-leveldirectory,createafile,math.js,containingthisextremelysimplisticFibonacciimplementation:

exports.fibonacci=function(n){

if(n===0)return0;

elseif(n===1||n===2)return1;

elsereturnexports.fibonacci(n-1)+exports.fibonacci(n-2);

};

Intheviewsdirectory,lookatthefilenamedlayout.hbswhichexpress-generatorcreated:

<!DOCTYPEhtml>

<html>

<head>

<title>{{title}}</title>

<linkrel='stylesheet'href='stylesheetsstyle.css'/>

Page 237: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

</head>

<body>

{{{body}}}

</body>

</html>

Thisfilecontainsthestructurewe'lluseforHTMLpages.GoingbytheHandlebarssyntax,weseethat{{title}}appearswithintheHTMLtitletag.Itmeanswhenwecallres.render,weshouldsupplyatitleattribute.The{{{body}}}tagiswheretheviewtemplatecontentlands.

Changeviews/index.hbstojustcontainthefollowing:

<h1>{{title}}</h1>

{{>navbar}}

Thisservesasthefrontpageofourapplication.Itwillbeinsertedinplaceof{{{body}}}inlayout.hbs.Themarker,{{>navbar}},referstoapartialnamednavbar.Earlier,weconfiguredadirectorynamedpartialstoholdpartials.Nowlet'screateafile,partials/navbar.html,containing:

<divclass='navbar'>

<p><ahref=''>home<a>|<ahref='fibonacci'>Fibonacci's<a></p>

</div>

Thiswillserveasanavigationbarthat'sincludedoneverypage.

Createafile,views/fibonacci.hbs,containingthefollowingcode:

<h1>{{title}}</h1>

{{>navbar}}

{{#iffiboval}}

<p>Fibonaccifor{{fibonum}}is{{fiboval}}</p>

<hr/>

{{/if}}

<p>Enteranumbertoseeits'Fibonaccinumber</p>

<formname='fibonacci'action='/fibonacci'method='get'>

<inputtype='text'name='fibonum'/>

<inputtype='submit'value='Submit'/>

</form>

Page 238: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Rememberthatthefilesinviewsaretemplatesintowhichdataisrendered.TheyservetheViewaspectoftheModel-View-Controller(MVC)paradigm,hencethedirectoryname.

Intheroutesdirectory,deletetheuser.jsmodule.ItisgeneratedbytheExpressframework,butwewillnotuseitinthisapplication.

Inroutes/index.js,changetherouterfunctiontothefollowing:

/*GEThomepage.*/

router.get('/',function(req,res,next){

res.render('index',{title:"WelcometotheFibonacciCalculator"});

});

Theanonymousobjectpassedtores.rendercontainsthedatavaluesweprovidetothelayoutandviewtemplates.

Then,finally,intheroutesdirectory,createafilenamedfibonacci.jscontainingthefollowingcode:

constexpress=require('express');

constrouter=express.Router();

constmath=require('../math');

router.get('',function(req,res,next){

if(req.query.fibonum){

/Calculatedirectlyinthisserver

res.render('fibonacci',{

title:"CalculateFibonaccinumbers",

fibonum:req.query.fibonum,

fiboval:math.fibonacci(req.query.fibonum)

});

}else{

res.render('fibonacci',{

title:"CalculateFibonaccinumbers",

fiboval:undefined

});

}

});

module.exports=router;

Thepackage.jsonisalreadysetupsowecanusenpmstarttorunthescript

Page 239: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

andalwayshavedebuggingmessagesenabled.Andnowwe'rereadytodoso:

$npmstart

>[email protected]/chap04/fibonacci

>DEBUG=fibonacci:*node./bin/www

fibonacci:serverListeningonport3000+0ms

Asitsuggests,youcanvisithttp://localhost:3000/andseewhatwehave:

Thispageisrenderedfromtheviews/index.hbstemplate.SimplyclickontheFibonacci'slinktogotothenextpage,whichisofcourserenderedfromtheviews/fibonacci.hbstemplate.Onthatpage,you'llbeabletoenteranumber,clickontheSubmitbutton,andgetananswer(hint:pickanumberbelow40ifyouwantyouranswerinareasonableamountoftime):

Page 240: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Let'swalkthroughtheapplicationtodiscusshowitworks.

Therearetworoutesinapp.js:theroutefor/,whichishandledbyroutes/index.js,andtheroutefor/fibonacci,whichishandledbyroutes/fibonacci.js.

Theres.renderfunctionrendersthenamedtemplateusingtheprovideddatavaluesandemitstheresultasanHTTPresponse.Forthehomepageofthisapplication,therenderingcode(routes/index.js)andtemplate(views/index.hbs)aren'tmuch,anditisontheFibonaccipagewherealltheactionishappening.

Theviews/fibonacci.hbstemplatecontainsaforminwhichtheuserentersanumber.BecauseitisaGETform,whentheuserclicksontheSubmitbutton,thebrowserwillissueanHTTPGETonthe/fibonacciURL.WhatdistinguishesoneGETon/fibonaccifromanotheriswhethertheURLcontainsaqueryparameternamedfibonum.Whentheuserfirstentersthepage,thereisnofibonumandhencenothingtocalculate.AftertheuserhasenteredanumberandclickedonSubmit,thereisafibonumandsomethingtocalculate.

Expressautomaticallyparsesthequeryparameters,makingthemavailable

Page 241: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

asreq.query.Thatmeansroutes/fibonacci.jscanquicklycheckwhetherthereisafibonum.Ifthereis,itcallsthefibonaccifunctiontocalculatethevalue.

Earlier,weaskedyoutoenteranumberlessthan40.Goaheadandenteralargernumber,suchas50,butgotakeacoffeebreakbecausethisisgoingtotakeawhiletocalculate.Orproceedontoreadingthenextsectionwherewestarttodiscussuseofcomputationallyintensivecode.

Page 242: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ComputationallyintensivecodeandtheNode.jseventloopThisFibonacciexampleispurposelyinefficienttodemonstrateanimportantconsiderationforyourapplications.WhathappenstotheNode.jseventloopwhenrunninglongcomputations?Toseetheeffect,opentwobrowserwindows,eachopenedtotheFibonaccipage.Inone,enterthenumber55orgreater,andintheother,enter10.Notethatthesecondwindowfreezes,andifyouleaveitrunninglongenough,theanswerwilleventuallypopupinbothwindows.What'shappeningistheNode.jseventloopisblockedfromprocessingeventsbecausetheFibonaccialgorithmisrunninganddoesnoteveryieldtotheeventloop.

SinceNode.jshasasingleexecutionthread,processingrequestsdependonrequesthandlersquicklyreturningtotheeventloop.Normally,theasynchronouscodingstyleensuresthattheeventloopexecutesregularly.

Thisistrueevenforrequeststhatloaddatafromaserverhalfwayaroundtheglobe,becausetheasynchronousI/Oisnon-blockingandcontrolisquicklyreturnedtotheeventloop.ThenaïveFibonaccifunctionwechosedoesn'tfitintothismodelbecauseit'salong-runningblockingoperation.ThistypeofeventhandlerpreventsthesystemfromprocessingrequestsandstopsNode.jsfromdoingwhatit'smeanttodo,namelytobeablisteringlyfastwebserver.

Inthiscase,thelong-response-timeproblemisobvious.ResponsetimequicklyescalatestothepointwhereyoucantakeavacationtoTibetandperhapsgetreincarnatedasallamainPeruduringthetimeittakestorespondwiththeFibonaccinumber!

Page 243: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Toseethismoreclearly,createafilenamedfibotimes.jscontainingthefollowingcode:

constmath=require('./math');

constutil=require('util');

for(varnum=1;num<80;num++){

letnow=newDate().toISOString();

console.log(`${now}Fibonaccifor${num}=${math.fibonacci(num)}`);

}

Nowrunit.Youwillgetthefollowingoutput:

$nodefibotimes.js

2017-12-10T23:04:42.342ZFibonaccifor1=1

2017-12-10T23:04:42.345ZFibonaccifor2=1

2017-12-10T23:04:42.345ZFibonaccifor3=2

2017-12-10T23:04:42.345ZFibonaccifor4=3

2017-12-10T23:04:42.345ZFibonaccifor5=5

...

2017-12-10T23:04:42.345ZFibonaccifor10=55

2017-12-10T23:04:42.345ZFibonaccifor11=89

2017-12-10T23:04:42.345ZFibonaccifor12=144

2017-12-10T23:04:42.345ZFibonaccifor13=233

2017-12-10T23:04:42.345ZFibonaccifor14=377

...

2017-12-10T23:04:44.072ZFibonaccifor40=102334155

2017-12-10T23:04:45.118ZFibonaccifor41=165580141

2017-12-10T23:04:46.855ZFibonaccifor42=267914296

2017-12-10T23:04:49.723ZFibonaccifor43=433494437

2017-12-10T23:04:54.218ZFibonaccifor44=701408733

...

2017-12-10T23:06:07.531ZFibonaccifor48=4807526976

2017-12-10T23:07:08.056ZFibonaccifor49=7778742049

^C

Thisquicklycalculatesthefirst40orsomembersoftheFibonaccisequence,butafterthe40thmember,itstartstakingacoupleofsecondsperresultandquicklydegradesfromthere.Itisuntenabletoexecutecodeofthissortonasingle-threadedsystemthatreliesonaquickreturntotheeventloop.Awebservicecontainingsuchcodewouldgivepoorperformancetotheusers.

Page 244: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TherearetwogeneralwaystosolvethisprobleminNode.js:

Algorithmicrefactoring:Perhaps,liketheFibonaccifunctionwechose,oneofyouralgorithmsissuboptimalandcanberewrittentobefaster.Or,ifnotfaster,itcanbesplitintocallbacksdispatchedthroughtheeventloop.We'lllookatonesuchmethodinamoment.

Creatingabackendservice:CanyouimagineabackendserverdedicatedtocalculatingFibonaccinumbers?Okay,maybenot,butit'squitecommontoimplementbackendserverstooffloadworkfromfrontendservers,andwewillimplementabackendFibonacciserverattheendofthischapter.

Page 245: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

AlgorithmicrefactoringToprovethatwehaveanartificialproblemonourhands,hereisamuchmoreefficientFibonaccifunction:

exports.fibonacciLoop=function(n){

varfibos=[];

fibos[0]=0;

fibos[1]=1;

fibos[2]=1;

for(vari=3;i<=n;i++){

fibos[i]=fibos[i-2]+fibos[i-1];

}

returnfibos[n];

}

Ifwesubstituteacalltomath.fibonacciLoopinplaceofmath.fibonacci,thefibotimesprogramrunsmuchfaster.Eventhisisn'tthemostefficientimplementation;forexample,asimpleprewiredlookuptableismuchfasteratthecostofsomememory.

Editfibotimes.jsasfollowsandrerunthescript.Thenumberswillflybysofastyourheadwillspin:

for(varnum=1;num<8000;num++){

letnow=newDate().toISOString();

console.log(`${now}Fibonaccifor${num}=${math.fibonacciLoop(num)}`);

}

Somealgorithmsaren'tsosimpletooptimizeandstilltakealongtimetocalculatetheresult.Inthissection,we'reexploringhowtohandleinefficientalgorithms,andthereforewillstickwiththeinefficientFibonacciimplementation.

Itispossibletodividethecalculationintochunksandthendispatchthe

Page 246: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

computationofthosechunksthroughtheeventloop.Addthefollowingcodetomath.js:

exports.fibonacciAsync=function(n,done){

if(n===0)done(undefined,0);

elseif(n===1||n===2)done(undefined,1);

else{

setImmediate(()=>{

exports.fibonacciAsync(n-1,(err,val1)=>{

if(err)done(err);

elsesetImmediate(()=>{

exports.fibonacciAsync(n-2,(err,val2)=>{

if(err)done(err);

elsedone(undefined,val1+val2);

});

});

});

});

}

};

Thisconvertsthefibonaccifunctionfromasynchronousfunctiontoatraditionalcallback-orientedasynchronousfunction.We'reusingsetImmediateateachstageofthecalculationtoensuretheeventloopexecutesregularlyandthattheservercaneasilyhandleotherrequestswhilechurningawayonacalculation.Itdoesnothingtoreducethecomputationrequired;thisisstillthesilly,inefficientFibonaccialgorithm.Allwe'vedoneisspreadthecomputationthroughtheeventloop.

Infibotimes.js,wecanusethis:

constmath=require('./math');

constutil=require('util');

(async()=>{

for(varnum=1;num<8000;num++){

awaitnewPromise((resolve,reject)=>{

math.fibonacciAsync(num,(err,fibo)=>{

if(err)reject(err);

else{

letnow=newDate().toISOString();

console.log(`${now}Fibonaccifor${num}=

${fibo}`);

Page 247: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

resolve();

}

})

})

}

})().catch(err=>{console.error(err);});

Thisversionoffibotimes.jsexecutesthesame,wesimplytypenodefibotimes.However,usingfibonacciAsyncwillrequirechangesintheserver.

Becauseit'sanasynchronousfunction,wewillneedtochangeourroutercode.Createanewfile,namedroutes/fibonacci-async1.js,containingthefollowing:

constexpress=require('express');

constrouter=express.Router();

constmath=require('../math');

router.get('/',function(req,res,next){

if(req.query.fibonum){

//Calculateusingasync-awarefunction,inthisserver

math.fibonacciAsync(req.query.fibonum,(err,fiboval)=>{

res.render('fibonacci',{

title:"CalculateFibonaccinumbers",

fibonum:req.query.fibonum,

fiboval:fiboval

});

});

}else{

res.render('fibonacci',{

title:"CalculateFibonaccinumbers",

fiboval:undefined

});

}

});

module.exports=router;

Thisisthesameasearlier,justrewrittenforanasynchronousFibonaccicalculation.

Inapp.js,makethischangetotheapplicationwiring:

Page 248: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

//constfibonacci=require('./routes/fibonacci');

constfibonacci=require('./routes/fibonacci-async1');

Withthischange,theservernolongerfreezeswhencalculatingalargeFibonaccinumber.Thecalculationofcoursestilltakesalongtime,butatleastotherusersoftheapplicationaren'tblocked.

Youcanverifythisbyagainopeningtwobrowserwindowsintheapplication.Enter60inonewindow,andintheotherstartrequestingsmallerFibonaccinumbers.Unlikewiththeoriginalfibonaccifunction,usingfibonacciAsyncallowsbothwindowstogiveanswers,thoughifyoureallydidenter60inthefirstwindowyoumightaswelltakethatthree-monthvacationtoTibet:

Page 249: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

It'suptoyou,andyourspecificalgorithms,tochoosehowtobestoptimizeyourcodeandtohandleanylong-runningcomputationsyoumayhave.

Page 250: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

MakingHTTPClientrequestsThenextwaytomitigatecomputationallyintensivecodeistopushthecalculationtoabackendprocess.Toexplorethatstrategy,we'llrequestcomputationsfromabackendFibonacciserver,usingtheHTTPClientobjecttodoso.However,beforewelookatthat,let'sfirsttalkingeneralaboutusingtheHTTPClientobject.

Node.jsincludesanHTTPClientobject,usefulformakingHTTPrequests.IthasthecapabilitytoissueanykindofHTTPrequest.Inthissection,we'llusetheHTTPClientobjecttomakeHTTPrequestssimilartocallingaRepresentationalStateTransfer(REST)webservice.

Let'sstartwithsomecodeinspiredbythewgetorcurlcommandstomakeHTTPrequestsandshowtheresults.Createafilenamedwget.jscontainingthiscode:

consthttp=require('http');

consturl=require('url');

constutil=require('util');

constargUrl=process.argv[2];

constparsedUrl=url.parse(argUrl,true);

//Theoptionsobjectispassedtohttp.request

//tellingittheURLtoretrieve

constoptions={

host:parsedUrl.hostname,

port:parsedUrl.port,

path:parsedUrl.pathname,

method:'GET'

};

if(parsedUrl.search)options.path+="?"+parsedUrl.search;

constreq=http.request(options);

//Invokedwhentherequestisfinished

req.on('response',res=>{

Page 251: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

console.log('STATUS:'+res.statusCode);

console.log('HEADERS:'+util.inspect(res.headers));

res.setEncoding('utf8');

res.on('data',chunk=>{console.log('BODY:'+chunk);});

res.on('error',err=>{console.log('RESPONSEERROR:'+err);});

});

//Invokedonerrors

req.on('error',err=>{console.log('REQUESTERROR:'+err);});

req.end();

Youcanrunthescriptasfollows:

$nodewget.jshttp://example.com

STATUS:200

HEADERS:{'accept-ranges':'bytes',

'cache-control':'max-age=604800',

'content-type':'text/html',

date:'Sun,10Dec201723:40:44GMT',

etag:'"359670651"',

expires:'Sun,17Dec201723:40:44GMT',

'last-modified':'Fri,09Aug201323:54:35GMT',

server:'ECS(rhv/81A7)',

vary:'Accept-Encoding',

'x-cache':'HIT',

'content-length':'1270',

connection:'close'}

BODY:<!doctypehtml>

<html>

...

There'smoreintheprintout,namelytheHTMLofthepageathttp://example.com/.Thepurposeofwget.jsistomakeanHTTPrequestandshowyouvoluminousdetailsoftheresponse.AnHTTPrequestisinitiatedwiththehttp.requestmethod,asfollows:

varhttp=require('http');

varoptions={

host:'example.com',

port:80,

path:null,

method:'GET'

};

varrequest=http.request(options);

request.on('response',response=>{

...

Page 252: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

});

Theoptionsobjectdescribestherequesttomake,andthecallbackfunctioniscalledwhentheresponsearrives.Theoptionsobjectisfairlystraightforward,withthehost,port,andpathfieldsspecifyingtheURLbeingrequested.ThemethodfieldmustbeoneoftheHTTPverbs(GET,PUT,POST,andsoon).YoucanalsoprovideaheadersarrayfortheheadersintheHTTPrequest.Forexample,youmightneedtoprovideacookie:

varoptions={

headers:{'Cookie':'..cookievalue'}

};

TheresponseobjectisitselfanEventEmitter,whichemitsthedataanderrorevents.Thedataeventiscalledasdataarrives,andtheerroreventis,ofcourse,calledonerrors.

TherequestobjectisaWritableStream,whichisusefulforHTTPrequestscontainingdata,suchasPUTorPOST.Thismeanstherequestobjecthasawritefunctionthatwritesdatatotherequester.ThedataformatinanHTTPrequestisspecifiedbythestandardMultipurposeInternetMailExtensions(MIME)originallycreatedtogiveusbetteremail.Around1992,theWWWcommunityworkedwiththeMIMEstandardcommitteewhichwasdevelopingaformatformultipart,multi-media-richelectronicmail.Receivingfancy-lookingemailissocommonplacetodaythatonemightnotbeawarethatemailusedtobeplaintext.MIME-typesweredevelopedtodescribetheformatofeachpieceofdata,andtheWWWcommunityadoptedthisforuseontheweb.HTMLformswillpostwithaContent-Typeofmultipart/form-data,forexample.

Page 253: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

CallingaRESTbackendservicefromanExpressapplicationNowthatwe'veseenhowtomakeHTTPclientrequests,wecanlookathowtomakeaRESTqueryinsideanExpresswebapplication.WhatthateffectivelymeansistomakeanHTTPGETrequesttoabackendserver,whichrespondswiththeFibonaccinumberrepresentedbytheURL.Todoso,we'llrefactortheFibonacciapplicationtomakeaFibonacciserverthatiscalledfromtheapplication.WhilethisisoverkillforcalculatingFibonaccinumbers,itletsuslookatthebasicsofimplementingamultitierapplicationstackinExpress.

Inherently,callingaRESTserviceisanasynchronousoperation.ThatmeanscallingtheRESTservicewillinvolveafunctioncalltoinitiatetherequestandacallbackfunctiontoreceivetheresponse.RESTservicesareaccessedoverHTTP,sowe'llusetheHTTPclientobjecttodoso.

Page 254: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ImplementingasimpleRESTserverwithExpressWhileExpresshasapowerfultemplatingsystem,makingitsuitablefordeliveringHTMLwebpagestobrowsers,itcanalsobeusedtoimplementasimpleRESTservice.TheparameterizedURLsweshowedearlier(/user/profile/:id)canactlikeparameterstoaRESTcall.AndExpressmakesiteasytoreturndataencodedinJSON.

Now,createafilenamedfiboserver.jscontainingthiscode:

constmath=require('./math');

constexpress=require('express');

constlogger=require('morgan');

constapp=express();

app.use(logger('dev'));

app.get('fibonacci:n',(req,res,next)=>{

math.fibonacciAsync(Math.floor(req.params.n),(err,val)=>{

if(err)next('FIBOSERVERERROR'+err);

elseres.send({n:req.params.n,result:val});

});

});

app.listen(process.env.SERVERPORT);

Thisisastripped-downExpressapplicationthatgetsrighttothepointofprovidingaFibonaccicalculationservice.TheonerouteitsupportshandlestheFibonaccicomputationusingthesamefunctionswe'vealreadyworkedwith.

Thisisthefirsttimewe'veseenres.sendused.It'saflexiblewaytosendresponseswhichcantakeanarrayofheadervalues(fortheHTTPresponseheader),andanHTTPstatuscode.Asusedhere,itautomaticallydetectstheobject,formatsitasJSONtext,andsendsitwiththecorrectContent-Type.

Page 255: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Inpackage.json,addthistothescriptssection:

"server":"SERVERPORT=3002node./fiboserver"

ThisautomateslaunchingourFibonacciservice.

Notethatwe'respecifyingtheTCP/IPportviaanenvironmentvariableandusingthatvariableintheapplication.ThisisanotheraspectoftheTwelve-Factorapplicationmodel:toputconfigurationdataintheenvironment.

Now,let'srunit:

$npmrunserver

>[email protected]/chap04/fibonacci

>SERVERPORT=3002node./fiboserver

Then,inaseparatecommandwindow,wecanusethecurlprogramtomakesomerequestsagainstthisservice:

$curl-fhttp://localhost:3002fibonacci10

{"n":"10","result":55}

$curl-fhttp://localhost:3002fibonacci11

{"n":"11","result":89}

$curl-fhttp://localhost:3002fibonacci12

{"n":"12","result":144}

Overinthewindowwheretheserviceisrunning,we'llseealogofGETrequestsandhowlongeachtooktoprocess:

$npmrunserver

>[email protected]/chap04/fibonacci

>SERVERPORT=3002node./fiboserver

GETfibonacci102000.393ms-22

GETfibonacci112000.647ms-22

GETfibonacci122000.772ms-23

Now,let'screateasimpleclientprogram,fiboclient.js,toprogrammaticallycalltheFibonacciservice:

Page 256: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

consthttp=require('http');

[

"fibonacci30","fibonacci20","fibonacci10",

"fibonacci9","fibonacci8","fibonacci7",

"fibonacci6","fibonacci5","fibonacci4",

"fibonacci3","fibonacci2","fibonacci1"

].forEach(path=>{

console.log(`${newDate().toISOString()}requesting${path}`);

varreq=http.request({

host:"localhost",

port:process.env.SERVERPORT,

path:path,

method:'GET'

},res=>{

res.on('data',chunk=>{

console.log(`${newDate().toISOString()}BODY:${chunk}`);

});

});

req.end();

});

Then,inpackage.json,addthistothescriptssection:

"scripts":{

"start":"node./bin/www",

"server":"SERVERPORT=3002node./fiboserver",

"client":"SERVERPORT=3002node./fiboclient"

}

Thenruntheclientapp:

$npmrunclient

>[email protected]/chap04/fibonacci

>SERVERPORT=3002node./fiboclient

2017-12-11T00:41:14.857Zrequestingfibonacci30

2017-12-11T00:41:14.864Zrequestingfibonacci20

2017-12-11T00:41:14.865Zrequestingfibonacci10

2017-12-11T00:41:14.865Zrequestingfibonacci9

2017-12-11T00:41:14.866Zrequestingfibonacci8

2017-12-11T00:41:14.866Zrequestingfibonacci7

2017-12-11T00:41:14.866Zrequestingfibonacci6

2017-12-11T00:41:14.866Zrequestingfibonacci5

2017-12-11T00:41:14.866Zrequestingfibonacci4

Page 257: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

2017-12-11T00:41:14.866Zrequestingfibonacci3

2017-12-11T00:41:14.867Zrequestingfibonacci2

2017-12-11T00:41:14.867Zrequestingfibonacci1

2017-12-11T00:41:14.884ZBODY:{"n":"9","result":34}

2017-12-11T00:41:14.886ZBODY:{"n":"10","result":55}

2017-12-11T00:41:14.891ZBODY:{"n":"6","result":8}

2017-12-11T00:41:14.892ZBODY:{"n":"7","result":13}

2017-12-11T00:41:14.893ZBODY:{"n":"8","result":21}

2017-12-11T00:41:14.903ZBODY:{"n":"3","result":2}

2017-12-11T00:41:14.904ZBODY:{"n":"4","result":3}

2017-12-11T00:41:14.905ZBODY:{"n":"5","result":5}

2017-12-11T00:41:14.910ZBODY:{"n":"2","result":1}

2017-12-11T00:41:14.911ZBODY:{"n":"1","result":1}

2017-12-11T00:41:14.940ZBODY:{"n":"20","result":6765}

2017-12-11T00:41:18.200ZBODY:{"n":"30","result":832040}

We'rebuildingourwaytowardaddingtheRESTservicetothewebapplication.Atthispoint,we'veprovedseveralthings,oneofwhichistheabilitytocallaRESTserviceinourprogram.

Wealsoinadvertentlydemonstratedanissuewithlong-runningcalculations.You'llnoticetherequestsweremadefromthelargesttothesmallest,buttheresultsappearedinaverydifferentorder.Why?It'sbecauseoftheprocessingtimeforeachrequest,andtheinefficientalgorithmwe'reusing.Thecomputationtimeincreasesenoughtoensurethatthelargerrequestvaluesrequireenoughprocessingtimetoreversetheorder.

Whathappensisthatfiboclient.jssendsallitsrequestsrightaway,andtheneachonewaitsfortheresponsetoarrive.BecausetheserverisusingfibonacciAsync,itwillworkoncalculatingallresponsessimultaneously.Thevaluesthatarequickesttocalculatearetheonesthatwillbereadyfirst.Astheresponsesarriveintheclient,thematchingresponsehandlerfires,andinthiscase,theresultprintstotheconsole.Theresultswillarrivewhenthey'rereadyandnotamillisecondsooner.

Page 258: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

RefactoringtheFibonacciapplicationforRESTNowthatwe'veimplementedaREST-basedserver,wecanreturntotheFibonacciapplication,applyingwhatwe'velearnedtoimproveit.Wewillliftsomeofthecodefromfiboclient.jsandtransplantitintotheapplicationtodothis.Createanewfile,routes/fibonacci-rest.js,withthefollowingcode:

constexpress=require('express');

constrouter=express.Router();

consthttp=require('http');

constmath=require('../math');

router.get('/',function(req,res,next){

if(req.query.fibonum){

varhttpreq=http.request({

host:"localhost",

port:process.env.SERVERPORT,

path:"fibonacci"+Math.floor(req.query.fibonum),

method:'GET'

});

httpreq.on('response',response=>{

response.on('data',chunk=>{

vardata=JSON.parse(chunk);

res.render('fibonacci',{

title:"CalculateFibonaccinumbers",

fibonum:req.query.fibonum,

fiboval:data.result

});

});

response.on('error',err=>{next(err);});

});

httpreq.on('error',err=>{next(err);});

httpreq.end();

}else{

res.render('fibonacci',{

title:"CalculateFibonaccinumbers",

fiboval:undefined

Page 259: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

});

}

});

module.exports=router;

Inapp.js,makethischange:

constindex=require('./routes/index');

//constfibonacci=require('./routes/fibonacci');

//constfibonacci=require('./routes/fibonacci-async1');

//constfibonacci=require('./routes/fibonacci-await');

constfibonacci=require('./routes/fibonacci-rest');

Then,inpackage.json,changethescriptsentrytothefollowing:

"scripts":{

"start":"DEBUG=fibonacci:*node./bin/www",

"startrest":"DEBUG=fibonacci:*SERVERPORT=3002node./bin/www",

"server":"DEBUG=fibonacci:*SERVERPORT=3002node./fiboserver",

"client":"DEBUG=fibonacci:*SERVERPORT=3002node./fiboclient"

},

HowcanwehavethesamevalueforSERVERPORTforallthreescriptsentries?Theansweristhatthevariableisuseddifferentlyindifferentplaces.Instartrest,thatvariableisusedinroutes/fibonacci-rest.jstoknowatwhichporttheRESTserviceisrunning.Likewise,inclient,fiboclient.jsusesthatvariableforthesamepurpose.Finally,inserver,thefiboserver.jsscriptusestheSERVERPORTvariabletoknowwhichporttolistenon.

Instartandstartrest,novalueisgivenforPORT.Inbothcases,bin/wwwdefaultstoPORT=3000ifitisnotspecified.

Inonecommandwindow,startthebackendserver,andintheother,starttheapplication.Openabrowserwindowasbefore,andmakeafewrequests.Youshouldseeoutputsimilartothis:

$npmrunserver

Page 260: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

>[email protected]/chap04/fibonacci

>DEBUG=fibonacci:*SERVERPORT=3002node./fiboserver

GETfibonacci3420021124.036ms-27

GETfibonacci122001.578ms-23

GETfibonacci162006.600ms-23

GETfibonacci2020033.980ms-24

GETfibonacci282001257.514ms-26

Theoutputlikethisfortheapplication:

$npmrunstartrest

>[email protected]/chap04/fibonacci

>DEBUG=fibonacci:*SERVERPORT=3002node./bin/www

fibonacci:serverListeningonport3000+0ms

GET/fibonacci?fibonum=3420021317.792ms-548

GET/stylesheets/style.css30420.952ms--

GET/fibonacci?fibonum=12304109.516ms--

GET/stylesheets/style.css3040.465ms--

GET/fibonacci?fibonum=1620083.067ms-544

GET/stylesheets/style.css3040.900ms--

GET/fibonacci?fibonum=20200221.842ms-545

GET/stylesheets/style.css3040.778ms--

GET/fibonacci?fibonum=282001428.292ms-547

GET/stylesheets/style.css30419.083ms--

Becausewehaven'tchangedthetemplates,thescreenwilllookexactlyasitdidearlier.

Wemayrunintoanotherproblemwiththissolution.TheasynchronousimplementationofourinefficientFibonaccialgorithmmaycausetheFibonacciserviceprocesstorunoutofmemory.IntheNode.jsFAQ,https://github.com/nodejs/node/wiki/FAQ,it'ssuggestedtousethe--max_old_space_sizeflag.You'daddthisinpackage.jsonasfollows:

"server":"SERVERPORT=3002node./fiboserver--max_old_space_size5000",

However,theFAQalsosaysthatifyou'rerunningintomaximummemoryspaceproblems,yourapplicationshouldprobablyberefactored.Thisgets

Page 261: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

backtoourpointseveralpagesagothatthereareseveralapproachestoaddressingperformanceproblems,oneofwhichisthealgorithmicrefactoringofyourapplication.

WhygotothetroubleofdevelopingthisRESTserverwhenwecouldjustdirectlyusefibonacciAsync?

WecannowpushtheCPUloadforthisheavyweightcalculationtoaseparateserver.DoingsowouldpreserveCPUcapacityonthefrontendserversoitcanattendtowebbrowsers.GPUco-processorsarenowwidelyusedfornumericalcomputingandcanbeaccessedviaasimplenetworkAPI.Theheavycomputationcanbekeptseparate,andyoucanevendeployaclusterofbackendserverssittingbehindaloadbalancer,evenlydistributingrequests.Decisionslikethisaremadeallthetimetocreatemultitiersystems.

Whatwe'vedemonstratedisthatit'spossibletoimplementsimplemultitierRESTservicesinafewlinesofNode.jsandExpress.ThewholeexercisegaveusachancetothinkaboutcomputationallyintensivecodeinNode.js.

Page 262: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SomeRESTfulmodulesandframeworksHereareafewavailablepackagesandframeworkstoassistyourREST-basedprojects:

Restify(>http://restify.com/):Thisoffersbothclient-sideandserver-sideframeworksforbothendsofRESTtransactions.Theserver-sideAPIissimilartoExpress.

Loopback(http://loopback.io/):ThisisanofferingfromStrongLoop,thecurrentsponsoroftheExpressproject.Itoffersalotoffeaturesandis,ofcourse,builtontopofExpress.

Page 263: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SummaryYoulearnedalotinthischapteraboutNode'sHTTPsupport,implementingwebapplications,andevenRESTserviceimplementation.

Nowwecanmoveontoimplementingamorecompleteapplication:onefortakingnotes.WewillusetheNotesapplicationforseveralupcomingchaptersasavehicletoexploretheExpressapplicationframework,databaseaccess,deploymenttocloudservicesoronyourownserver,anduserauthentication.

Inthenextchapter,wewillbuildthebasicinfrastructure.

Page 264: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

YourFirstExpressApplicationNowthatwe'vegotourfeetwetbuildinganExpressapplicationforNode.js,let'sworkonanapplicationthatperformsausefulfunction.Theapplicationwe'llbuildwillkeepalistofnotes,anditwillletusexploresomeaspectsofarealapplication.

Inthischapter,we'llonlybuildthebasicinfrastructureoftheapplication,andinthelaterchapters,we'llextendtheapplicationconsiderably.

Thetopicscoveredinthischapterincludes

UsingPromisesandasyncfunctionsinExpressrouterfunctions

ApplyingtheMVCparadigmtoExpressapplications

BuildinganExpressapplication

JavaScriptClassdefinitions

ImplementingtheCRUDparadigm

Handlebarstemplates

Page 265: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you
Page 266: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Promises,asyncfunctions,andExpressrouterfunctionsBeforewegetintodevelopingourapplication,wemusttakeadeeperlookatapairofnewES-2015/2016/2017featuresthatcollectivelyrevolutionizeJavaScriptprogramming:ThePromiseclassandasyncfunctions.Bothareusedfordeferredandasynchronouscomputationandcanmakeintenselynestedcallbackfunctionsathingofthepast:

APromiserepresentsanoperationthathasn'tcompletedyetbutisexpectedtobecompletedinthefuture.We'veseenPromisesinuse.The.thenor.catchfunctionsareinvokedwhenthepromisedresult(orerror)isavailable.

Generatorfunctionsareanewkindoffunctionthatcanbepausedandresumed,andcanreturnresultsfromthemiddleofthefunction.

Thosetwofeaturesweremixedwithanother,theiterationprotocol,alongwithsomenewsyntax,tocreateasyncfunctions.

Themagicofasyncfunctionsisthatwecanwriteasynchronouscodeasifit'ssynchronouscode.It'sstillasynchronouscode,meaninglong-runningrequesthandlerswon'tblocktheeventloop.Thecodelookslikethesynchronouscodewe'dwriteinotherlanguages.Onestatementfollowsanother,theerrorsarethrownasexceptions,andtheresultslandonthenextlineofcode.Promiseandasyncfunctionsaresomuchofanimprovementthatit'sextremelycompellingfortheNode.jscommunitytoswitchparadigms,meaningrewritinglegacycallback-orientedAPIs.

Page 267: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Overtheyears,severalotherapproacheshavebeenusedtomanageasynchronouscode,andyoumaycomeacrosscodeusingtheseothertechniques.BeforethePromiseobjectwasstandardized,atleasttwoimplementationswereavailable:Bluebird(http://bluebirdjs.com/)andQ(https://www.npmjs.com/package/q).Useofanon-standardPromiselibraryshouldbecarefullyconsidered,sincethereisvalueinmaintainingcompatibilitywiththestandardPromiseobject.

ThePyramidofDoomisnamedaftertheshapethecodetakesafterafewlayersofnesting.Anymultistageprocesscanquicklyescalatetocodenested15levelsdeep.Considerthefollowingexample:

router.get('pathto/something',(req,res,next)=>{

doSomething(arg1,arg2,(err,data1)=>{

if(err)returnnext(err);

doAnotherThing(arg3,arg2,data1,(err2,data2)=>{

if(err2)returnnext(err2);

somethingCompletelyDifferent(arg1,arg42,(err3,data3)=>{

if(err3)returnnext(err3);

doSomethingElse((err4,data4)=>{

if(err4)returnnext(err4);

res.render('page',{data});

});

});

});

});

});

Rewritingthisasanasyncfunctionwillmakethismuchclearer.Togetthere,weneedtoexaminethefollowingideas:

UsingPromisestomanageasynchronousresults

GeneratorfunctionsandPromises

asyncfunctions

WegenerateaPromisethisway:

Page 268: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

exports.asyncFunction=function(arg1,arg2){

returnnewPromise((resolve,reject)=>{

//performsometaskorcomputationthat'sasynchronous

//foranyerrordetected:

if(errorDetected)returnreject(dataAboutError);

//Whenthetaskisfinished

resolve(theResult);

});

};

NotethatasyncFunctionisanasynchronousfunction,butitdoesnottakeacallback.Instead,itreturnsaPromiseobject,andtheasynchronouscodeisexecutedwithinacallbackpassedtothePromiseclass.

Yourcodemustindicatethestatusoftheasynchronousoperationviatheresolveandrejectfunctions.Asimpliedbythefunctionnames,rejectindicatesanerroroccurredandresolveindicatesasuccessresult.Yourcallerthenusesthefunctionasfollows:

asyncFunction(arg1,arg2)

.then((result)=>{

//theoperationsucceeded

//dosomethingwiththeresult

returnnewResult;

})

.catch(err=>{

//anerroroccurred

});

Thesystemisfluidenoughthatthefunctionpassedina.thencanreturnsomething,suchasanotherPromise,andyoucanchainthe.thencallstogether.Thevaluereturnedina.thenhandler(ifany)becomesanewPromiseobject,andinthiswayyoucanconstructachainof.thenand.catchcallstomanageasequenceofasynchronousoperations.

Asequenceofasynchronousoperationswouldbeimplementedasachainof.thenfunctions,aswewillseeinthenextsection.

Page 269: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

notes.read(req.query.key).then(note=>{returnfilterNote(note);}).then(note=>{returnswedishChefSpeak(note);}).then(note=>{

res.render('noteview',{

title:note?note.title:"",notekey:req.query.key,note:note

});

})

.catch(err=>{next(err);});

Thereareseveralplaceswhereerrorscanoccurinthislittlebitofcode.Thenotes.readfunctionhasseveralpossiblefailuremodes:thefilterNotefunctionmightwanttoraiseanalarmifitdetectsacross-sitescriptingattack.TheSwedishchefcouldbeonstrike.Therecouldbeafailureinres.renderorthetemplatebeingused.Butwehaveonlyonewaytocatchandreporterrors.Arewemissingsomething?

ThePromiseclassautomaticallycaptureserrors,sendingthemdownthechainofoperationsattachedtothePromise.IfthePromiseclasshasanerroronitshands,itskipsoverthe.thenfunctionsandwillinsteadinvokethefirst.catchfunctionitfinds.Inotherwords,usinginstancesofPromiseprovidesahigherassuranceofcapturingandreportingerrors.Withtheolderconvention,errorreportingwastrickier,anditwaseasytoforgettoaddcorrecterrorhandling.

Page 270: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

FlatteningourasynchronouscodeTheproblembeingaddressedisthatasynchronouscodinginJavaScriptresultsinthePyramidofDoom.Toexplain,let'sreiteratetheexampleRyanDahlgaveastheprimaryNode.jsidiom:

db.query('SELECT..etc..',function(err,resultSet){

if(err){

//Instead,errorsarrivehere

}else{

//Instead,resultsarrivehere

}

});

//WeWANTtheerrorsorresultstoarrivehere

Thegoalwastoavoidblockingtheeventloopwithalongoperation.DeferringtheprocessingofresultsorerrorsusingcallbackfunctionswasanexcellentsolutionandisthefoundingidiomofNode.js.Theimplementationofcallbackfunctionsledtothispyramid-shapedproblem.Namely,thatresultsanderrorslandinthecallback.Ratherthandeliveringthemtothenextlineofcode,theerrorsandresultsareburied.

Promiseshelpflattenthecodesothatitnolongertakesapyramidalshape.Theyalsocaptureerrors,ensuringdeliverytoausefullocation.Butthoseerrorsandresultsarestillburiedinsideananonymousfunctionanddonotgetdeliveredtothenextlineofcode.

Further,usingPromisesresultsinalittlebitofboilerplatecodethatobscurestheprogrammersintent.It'slessboilerplatethanwithregularcallbackfunctions,buttheboilerplateisstillthere.

Fortunately,theECMAScriptcommitteekeptworkingontheproblem.

Page 271: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

PromisesandgeneratorsbirthedasyncfunctionsGeneratorsandtheassociatedIterationProtocolarealargetopic,whichwewillbrieflycover.

TheIterationProtocoliswhat'sbehindthenewfor..ofloop,andsomeothernewloopingconstructs.Theseconstructscanbeusedwithanythingproducinganiterator.Formoreaboutboth,seehttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols.

Ageneratorisakindoffunctionwhichcanbestoppedandstartedusingtheyieldkeyword.Generatorsproduceaniteratorwhosevaluesarewhateverisgiventotheyieldstatement.Formoreonthis,seehttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator.

Considerthis:

$catgen.js

function*gen(){

yield1;

yield2;

yield3;

yield4;

}

for(letgofgen()){

console.log(g);

}

$nodegen.js

1

2

3

4

Theyieldstatementcausesageneratorfunctiontopauseandtoprovidethe

Page 272: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

valuegiventothenextcallonitsnextfunction.Thenextfunctionisn'texplicitlyseenhere,butiswhatcontrolstheloop,andispartoftheiterationprotocol.Insteadoftheloop,trycallinggen().next()severaltimes:

vargeniter=gen();

console.log(geniter.next());

console.log(geniter.next());

console.log(geniter.next());

You'llseethis:

$nodegen.js

{value:1,done:false}

{value:2,done:false}

{value:3,done:false}

TheIterationprotocolsaystheiteratorisfinishedwhendoneistrue.Inthiscase,wedidn'tcallitenoughtotriggertheendstateoftheiterator.

WheregeneratorsbecameinterestingiswhenusedwithfunctionsthatreturnaPromise.ThePromiseiswhat'smadeavailablethroughtheiterator.ThecodeconsumingtheiteratorcanwaitonthePromisetogetitsvalue.Aseriesofasynchronousoperationscouldbeinsidethegeneratorandinvokedinaniterablefashion.

Withthehelpofanextrafunction,ageneratorfunctionalongwithPromise-returningasynchronousfunctionscanbeaverynicewaytowriteasynchronouscode.WesawanexampleofthisinChapter2,SettingupNode.js,whileexploringBabel.Babelhasaplugintorewriteasyncfunctionsintoageneratoralongwithahelperfunction,andwetookalookatthetranspiledcodeandthehelperfunction.Thecolibrary(https://www.npmjs.com/package/co)isapopularhelperfunctionforimplementingasynchronouscodingingenerators.Createafilenamed2files.js:

constfs=require('fs-extra');

constco=require('co');

constutil=require('util');

co(function*(){

Page 273: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

vartexts=[

yieldfs.readFile('hello.txt','utf8'),

yieldfs.readFile('goodbye.txt','utf8')

];

console.log(util.inspect(texts));

});

Thenrunitlikeso:

$node2files.js

['Hello,world!\n','Goodbye,world!\n']

Normally,fs.readFilesendsitsresulttoacallbackfunction,andwe'dbuildalittlepyramid-shapedpieceofcodetoperformthistask.Thefs-extramodulecontainsimplementationsofallfunctionsfromthebuilt-infsmodulebutchangedtoreturnaPromiseinsteadofacallbackfunction.Therefore,eachfs.readFileshownhereisreturningaPromisethat'sresolvedwhenthefilecontentisfullyreadintomemory.WhatcodoesisitmanagesthedanceofwaitingforthePromisetoberesolved(orrejected),andreturnsthevalueofthePromise.Therefore,withtwosuitabletextfileswehavetheresultshownfromexecuting2files.js.

Theimportantthingisthatthecodeisverycleanandreadable.Wearen'tcaughtupinboilerplatecoderequiredtomanageasynchronousoperations.Theintentoftheprogrammerisprettyclear.

asyncfunctionstakethatsamecombinationofgeneratorsandPromisesanddefineastandardizedsyntaxintheJavaScriptlanguage.Createafilenamed2files-async.js:

constfs=require('fs-extra');

constutil=require('util');

asyncfunctiontwofiles(){

vartexts=[

awaitfs.readFile('hello.txt','utf8'),

awaitfs.readFile('goodbye.txt','utf8')

];

console.log(util.inspect(texts));

}

twofiles().catch(err=>{console.error(err);});

Page 274: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Thenrunitlikeso:

$node2files-async.js

['Hello,world!\n','Goodbye,world!\n']

Clean.Readable.Theintentoftheprogrammerisclear.Nodependencyonanadd-onlibrary,withsyntaxbuilt-intotheJavaScriptlanguage.Mostimportantly,everythingishandledinanaturalway.Errorsareindicatednaturallybythrowingexceptions.Theresultsofanasynchronousoperationnaturallyappearastheresultoftheoperation,withtheawaitkeywordfacilitatingthedeliveryofthatresult.

Toseetherealadvantage,let'sreturntothePyramidofDoomexamplefromearlier:

router.get('pathto/something',async(req,res,next)=>{

try{

letdata1=awaitdoSomething(req.query.arg1,req.query.arg2);

letdata2=awaitdoAnotherThing(req.query.arg3,req.query.arg2,

data1);

letdata3=awaitsomethingCompletelyDifferent(req.query.arg1,

req.query.arg42);

letdata4=awaitdoSomethingElse();

res.render('page',{data1,data2,data3,data4});

}catch(err){

next(err);

}

});

Otherthanthetry/catch,thisexamplebecameverycleancomparedtoitsformasacallbackpyramid.Alltheasynchronouscallbackboilerplateiserased,andtheintentoftheprogrammershinesclearly.

Whywasthetry/catchneeded?Normally,anasyncfunctioncatchesthrownerrors,automaticallyreportingthemcorrectly.ButsincethisexampleiswithinanExpressrouterfunction,we'relimitedbyitscapabilities.Expressdoesn'tknowhowtorecognizeanasyncfunction,andthereforeitdoesnotknowtolookforthethrownerrors.Instead,we'rerequiredtocatchthem

Page 275: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

andcallnext(err).

Thisimprovementisonlyforcodeexecutinginsideanasyncfunction.CodeoutsideanasyncfunctionstillrequirescallbacksorPromisesforasynchronouscoding.Further,thereturnvalueofanasyncfunctionisaPromise.

Refertotheofficialspecificationofasyncfunctionsathttps://tc39.github.io/ecmascript-asyncawait/fordetails.

Page 276: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ExpressandtheMVCparadigmExpressdoesn'tenforceanopiniononhowyoushouldstructuretheModel,View,andControllermodulesofyourapplication,orwhetheryoushouldfollowanykindofMVCparadigmatall.Aswelearnedinthepreviouschapter,theblankapplicationcreatedbytheExpressGeneratorprovidestwoaspectsoftheMVCmodel:

Theviewsdirectorycontainstemplatefiles,controllingthedisplayportion,correspondingtotheView.

TheroutesdirectorycontainscodeimplementingtheURLsrecognizedbytheapplicationandcoordinatingtheresponsetoeachURL.Thiscorrespondstothecontroller.

Thisleavesyouwonderingwheretoputcodecorrespondingtothemodel.Modelsholdtheapplicationdata,changingitasinstructedbythecontrollerandsupplyingdatarequestedbyViewcode.Ataminimum,theModelcodeshouldbeinseparatemodulesfromtheControllercode.Thisistoensureacleanseparationofconcerns,forexample,toeasetheunittestingofeach.

Theapproachwe'lluseistocreateamodelsdirectoryasasiblingoftheviewsandroutesdirectories.Themodelsdirectorywillholdmodulesforstoringthenotesandrelateddata.TheAPIofthemodulesinthemodelsdirectorywillprovidefunctionstocreate,read,update,ordeletedataitemsCreate,Read,Update,andDeleteorDestroy(CRUDmodel)andotherfunctionsnecessaryfortheViewcodetodoitsthing.

TheCRUDmodel(create,read,update,destroy)isthefourbasicoperationsofpersistentdatastorage.TheNotesapplicationisstructuredas

Page 277: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

aCRUDapplicationtodemonstrateimplementingeachoftheseoperations.

We'llusefunctionsnamedcreate,read,update,anddestroytoimplementeachofthebasicoperations.

We'reusingtheverbdestroyratherthandelete,becausedeleteisareservedwordinJavaScript.

Page 278: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

<strong>$mkdirnotes</strong><br/><strong>$cdnotes</strong><br/><strong>[email protected]</strong><br/><strong>$./node_modules/.bin/express--view=hbs--git.</strong><br/><strong>destinationisnotempty,continue?[y/N]y</strong><br/><br/><strong>create:.</strong><br/><strong>create:./package.json</strong><br/><strong>create:./app.js</strong><br/><strong>create:./.gitignore</strong><br/><strong>create:./public</strong><br/><strong>create:./routes</strong><br/><strong>create:./routes/index.js</strong><br/><strong>create:./routes/users.js</strong><br/><strong>create:./views</strong><br/><strong>create:./views/index.hbs</strong><br/><strong>create:./views/layout.hbs</strong><br/><strong>create:./views/error.hbs</strong><br/><strong>create:./bin</strong><br/><strong>create:./bin/www</strong><br/><strong>create:./public/stylesheets</strong><br/><strong>create:./public/stylesheets/style.css</strong><br/><br/><strong>installdependencies:</strong><br/><strong>$cd.&&npminstall</strong><br/><br/><strong>runtheapp:</strong><br/><strong>$DEBUG=notes:*npmstart</strong><br/><br/><strong>create:./public/javascripts</strong><br/><strong>create:./public/images</strong><br/><strong>$npminstall</strong><br/><strong>added82packagesandremoved5packagesin97.188s</strong><br/><strong>$npmuninstallexpress-generator</strong><br/><strong>uptodatein8.325s</strong>

Ifyouwish,youcanrunnpmstartandviewtheblankapplicationinyourbrowser.Instead,let'smoveontosettingupthecode.

Page 279: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

YourfirstNotesmodelCreateadirectorynamedmodels,asasiblingoftheviewsandroutesdirectories.

Then,createafilenamedNote.jsinthatdirectory,andputthiscodeinit:

constnotekey=Symbol('key');

constnotetitle=Symbol('title');

constnotebody=Symbol('body');

module.exports=classNote{

constructor(key,title,body){

this[notekey]=key;

this[notetitle]=title;

this[notebody]=body;

}

getkey(){returnthis[notekey];}

gettitle(){returnthis[notetitle];}

settitle(newTitle){this[notetitle]=newTitle;}

getbody(){returnthis[notebody];}

setbody(newBody){this[notebody]=newBody;}

};

Thisdefinesanewclass,Note,forusewithinourNotesapplication.Theintentistoholddatarelatedtonotesbeingexchangedbetweenusersofourapplication.

Page 280: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

UnderstandingES-2015classdefinitionsThissortofobjectclassdefinitionisnewtoJavaScriptwithES-2015.ItsimplifiesdefiningclassesoverpreviousmethodsandbringsJavaScriptclassdefinitionsclosertothesyntaxinotherlanguages.Underthehood,JavaScriptclassesstilluseprototype-basedinheritance,butwithasimplersyntax,andthecoderdoesn'tevenhavetothinkabouttheobjectprototype.

Wecanreliablydeterminewhetheranobjectisanotewiththeinstanceofoperator:

$node

>constNote=require('./Note');

>typeofNote

'function'

>constaNote=newNote('foo','TheRainInSpain','Fallsmainlyonthe

plain');

>varnotNote={}

>notNoteinstanceofNote

false

>aNoteinstanceofNote

true

>typeofaNote

'object'

Thisshowsustheclearestmethodtoidentifyanobjectiswiththeinstanceofoperator.ThetypeofoperatorinformsusNoteisafunction(becauseoftheprototype-basedinheritancebehindthescenes),andthataninstanceoftheNoteclassisanobject.Withinstanceof,wecaneasilydeterminewhetheranobjectisaninstanceofagivenclass.

WiththeNoteclass,wehaveusedSymbolinstancestoprovideasmallmeasureofdatahiding.JavaScriptclassesdon'tprovideadata-hiding

Page 281: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

mechanism—youcan'tlabelafieldprivateasyoucaninJava,forexample.It'susefultoknowhowtohideimplementationdetails.Thisisanimportantattributeofobject-orientedprogramming,becauseit'susefultohavethefreedomtochangetheimplementationatwill.Andthere'stheissueofcontrollingwhichcodecanmanipulatetheobject'sfields.

First,wedeclaredgetterandsetterfunctionstoprovideaccesstothevalues.Wewentovernormalgetter/setterusageinChapter4,HTTPServersandClients.

Accesstoagetter-basedfieldisbyusingthenameoftheproperty,andnotbycallingafunction-aNote.titleandnotaNote.title().Itlookslikeyou'reaccessinganobjectpropertybyassigningavalueoraccessingthevalue.Inactuality,thefunctiondefinedintheclassisexecutedoneveryaccess.Youcandefinearead-onlypropertybyonlyimplementingagetter,andnosetter,aswedidwiththekeyfield.

Therearesignificantdifferencesbetweentheprecedingandsimplydefininganonymousobjects:

{

key:'foo',title:'TheRaininSpain',

body:'Fallsmainlyontheplain'

}

WewritecodelikethatinJavaScriptallthetime.It'seasy,it'squick,andit'saveryfluidwaytosharedatabetweenfunctions.Butthere'snomeasureofhidingimplementationdetails,andnoclearidentificationofobjecttype.

IntheNoteclass,wecouldhaveusedthisconstructormethod:

classNote{

constructor(key,title,body){

this.key=key;

this.title=title;

this.body=body;

}

Page 282: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

}

That'seffectivelythesameastheanonymousobject,inthatnodetailshavebeenhiddenandnocontrolisimplementedintermsofwhichcodecandowhattoobjectinstances.Theonlyadvantageoverananonymousobjectisusingtheinstanceofoperatortoidentifyobjectinstances.

ThemethodwechoseusestheSymbolclass,whichisalsonewwithES-2015.ASymbolisanopaqueobjectwithtwomainusecases:

Generatinguniquekeystouseaspropertyfields—asinthepreviousNoteclass

SymbolicidentifiersthatyoucanuseforconceptslikeCOLOR_RED

YoudefineaSymbolthroughafactorymethodthatgeneratesSymbolinstances:

>letsymfoo=Symbol('foo')

EachtimeyouinvoketheSymbolfactorymethod,anewanduniqueinstanceiscreated.Forexample,Symbol('foo')===Symbol('foo')isfalse,asissymfoo===Symbol('foo'),becauseanewinstanceiscreatedoneachsideoftheequalityoperator.However,symfoo===symfooistrue,becausetheyarethesameinstance.

Whatthismeansinpracticeisthatifwetryadirectapproachtoaccessafield,itfails:

>aNote[Symbol('title')]

undefined

RememberthateachtimeweusetheSymbolfactorymethodwegetanewinstance.ThenewinstanceofSymbol('title')isnotthesameinstanceused

Page 283: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

withintheNote.jsmodule.

ThebottomlineisthatusingSymbolobjectsforthefieldsprovidesasmallmeasureofimplementationhiding.

Page 284: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Fillingoutthein-memoryNotesmodelCreateafilenamednotes-memory.jsinthemodelsdirectory,withthiscode:

constNote=require('./Note');

varnotes=[];

exports.update=exports.create=asyncfunction(key,title,body){

notes[key]=newNote(key,title,body);

returnnotes[key];

};

exports.read=asyncfunction(key){

if(notes[key])returnnotes[key];

elsethrownewError(`Note${key}doesnotexist`);

};

exports.destroy=asyncfunction(key){

if(notes[key]){

deletenotes[key];

}elsethrownewError(`Note${key}doesnotexist`);

};

exports.keylist=asyncfunction(){returnObject.keys(notes);};

exports.count=asyncfunction(){returnnotes.length;};

exports.close=asyncfunction(){}

Thisisasimplein-memorydatastorethat'sfairlyself-explanatory.ThekeyforeachNoteinstanceisusedastheindextoanarray,whichinturnholdstheNoteinstance.Simple,fast,andeasytoimplement.Itdoesnotsupportanylong-termdatapersistence.Anydatastoredinthismodelwilldisappearwhentheserveriskilled.

Wehaveusedasyncfunctionsbecauseinthefuturewe'llbestoringdatainthefilesystemorindatabases.Therefore,weneedanasynchronousAPI.

Page 285: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Thecreateandupdatefunctionsarebeinghandledbythesamefunction.AtthisstageoftheNotesapplication,thecodeforboththesefunctionscanbeexactlythesamebecausetheyperformtheexactsameoperation.Later,whenweadddatabasesupporttoNotes,thecreateandupdatefunctionswillneedtobedifferent.Forexample,inaSQLdatamodel,createwouldbeimplementedwithanINSERTINTOcommand,whileupdatewouldbeimplementedwithanUPDATEcommand.

Page 286: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TheNoteshomepageWe'regoingtomodifythestarterapplicationtosupportcreating,editing,updating,viewing,anddeletingnotes.Let'sstartbyfixingupthehomepage.Itshouldshowalistofnotes,andthetopnavigationbarshouldlinktoanADDNotepagesothatwecanalwaysaddanewnote.

Whilewewillbemodifyingthegeneratedapp.js,itneedsnomodificationtosupportthehomepage.Theselinesofcodearerelatedtothehomepage:

constindex=require('./routes/index');

..

app.use('/',index);

Additionally,tosupportHandlebarstemplatesapp.jsrequiresthesechanges:

consthbs=require('hbs');

...

app.set('viewengine','hbs');

hbs.registerPartials(path.join(__dirname,'partials'));

We'llputHandlebarspartialsinadirectory,partials,whichisasiblingto

theviewsdirectory.Changeroutes/index.jstothis:

constexpress=require('express');

constrouter=express.Router();

constnotes=require('../models/notes-memory');

/*GEThomepage.*/

router.get('/',async(req,res,next)=>{

letkeylist=awaitnotes.keylist();

letkeyPromises=keylist.map(key=>{

Page 287: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

returnnotes.read(key)

});

letnotelist=awaitPromise.all(keyPromises);

res.render('index',{title:'Notes',notelist:notelist});

});

module.exports=router;

Thisgathersdataaboutthenotesthatwe'llbedisplayingonthehomepage.Bydefault,we'llshowasimpletableofnotetitles.Wedoneedtotalkaboutthetechnique.

ThePromise.allfunctionexecutesanarrayofPromises.ThePromisesareevaluatedinparallel,allowingourcodetopotentiallymakeparallelrequeststoaservice.Thisshouldexecutemorequicklythanmakingtherequestsoneatatimesequentially.

Wecouldhavewrittenasimpleforlooplikeso:

letkeylist=awaitnotes.keylist();

letnotelist=[];

for(keyofkeylist){

letnote=awaitnotes.read(keylist);

notelist.push({key:note.key,title:note.title});

}

Whilesimplertoread,thenotesareretrievedoneatatimewithnoopportunitytooverlapreadoperations.

ThePromisearrayisconstructedusingthemapfunction.Withmap,oneiteratesoveranarraytoproduceanewarray.Inthiscase,thenewarraycontainsthePromisesgeneratedbythenotes.readfunctioncalls.

BecausewewroteawaitPromise.all,thenotelistarraywillbecompletelyfilledwiththecorrectdataonceallthePromisessucceed.IfanyPromisefails—isrejected,inotherwords—anexceptionwillbethrowninstead.Whatwe'vedoneisenqueuealistofasynchronousoperationsandneatlywaitedforthemalltofinish.

Page 288: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Thenotelistarrayisthenpassedintotheviewtemplateswe'reabouttowrite.

Startwithviews/layout.hbs,containing:

<!DOCTYPEhtml>

<html>

<head>

<title>{{title}}</title>

<linkrel='stylesheet'href='stylesheetsstyle.css'/>

</head>

<body>

{{>header}}

{{{body}}}

</body>

</html>

Thisisthegeneratedfile,withtheadditionofapartialforthepageheader.Wealreadydeclaredpartialstoliveinthepartialsdirectory.Createpartials/header.hbs,containing:

<header>

<h1>{{title}}</h1>

<divclass='navbar'>

<p><ahref='/'>Home</a>|<ahref='notesadd'>ADDNote</a></p>

</div>

</header>

Changeviews/index.hbstothis:

{{#eachnotelist}}

<ul>

<li>{{key}}:

<ahref="notesview?key={{key}}">{{title}}</a>

</li>

</ul>

{{/each}}

Thissimplystepsthroughthearrayofnotedataandformatsasimplelisting.EachitemlinkstothenotesviewURLwithakeyparameter.Wehaveyettolookatthatcode,butthisURLwillobviouslydisplaythenote.

Page 289: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

AnotherthingofnoteisthatnoHTMLforthelistisgeneratedifthenotelistisempty.

There'sofcourseawholelotmorethatcouldbeputintothis.Forexample,it'seasytoaddjQuerysupporttoeverypagejustbyaddingtheappropriatescripttagshere.

Wenowhaveenoughwrittentoruntheapplication;let'sviewthehomepage:

$DEBUG=notes:*npmstart

>[email protected]/chap05/notes

>node./bin/www

notes:serverListeningonport3000+0ms

GET/20087.300ms-308

GETstylesheetsstyle.css20027.744ms-111

Ifwevisithttp://localhost:3000,wewillseethefollowingpage:

Becausetherearen'tanynotes(yet),there'snothingtoshow.ClickingontheHomelinkjustrefreshesthepage.ClickingontheADDNotelink

Page 290: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

throwsanerrorbecausewehaven't(yet)implementedthatcode.Thisshowsthattheprovidederrorhandlerinapp.jsisperformingasexpected.

Page 291: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Addinganewnote–createNow,let'slookatcreatingnotes.Becausetheapplicationdoesn'thavearouteconfiguredforthenotesaddURL,wemustaddone.Todothat,weneedacontrollerforthenotes.

Inapp.js,makethefollowingchanges.

Commentouttheselines:

//varusers=require('./routes/users');

..

//app.use('/users',users);

Atthisstage,theNotesapplicationdoesnotsupportusers,andtheseroutesarenotrequired.Thatwillchangeinafuturechapter.

Whatwereallyneedtodoisaddcodeforthenotescontroller:

//constusers=require('./routes/users');

constnotes=require('./routes/notes');

..

//app.use('users',users);

app.use('notes',notes);

Now,we'lladdaControllermodulecontainingthenotesrouter.Createafilenamedroutes/notes.js,withthiscontent:

constutil=require('util');

constexpress=require('express');

constrouter=express.Router();

constnotes=require('../models/notes-memory');

//AddNote.

router.get('/add',(req,res,next)=>{

Page 292: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

res.render('noteedit',{

title:"AddaNote",

docreate:true,

notekey:"",note:undefined

});

});

module.exports=router;

TheresultingnotesaddURLcorrespondstothelinkinpartials/header.hbs.

Intheviewsdirectory,addatemplatenamednoteedit.hbs,containingthefollowing:

<formmethod='POST'action='notessave'>

<inputtype='hidden'name='docreate'value='<%=

docreate?"create":"update"%>'>

<p>Key:

{{#ifdocreate}}

<inputtype='text'name='notekey'value=''/>

{{else}}

{{#ifnote}}{{notekey}}{{/if}}

<inputtype='hidden'name='notekey'

value='{{#ifnote}}{{notekey}}{{/if}}'/>

{{/if}}

</p>

<p>Title:<inputtype='text'name='title'

value='{{#ifnote}}{{note.title}}{{/if}}'><p>

<br/><textarearows=5cols=40name='body'>

{{#ifnote}}{{note.body}}{{/if}}

</textarea>

<br/><inputtype='submit'value='Submit'/>

</form>

We'llbereusingthistemplatetosupportbotheditingnotesandcreatingnewones.

Noticethatthenoteandnotekeyobjectspassedtothetemplateareemptyinthiscase.Thetemplatedetectsthisconditionandensurestheinputareasareempty.Additionally,aflag,docreate,ispassedinsothattheformrecordswhetheritisbeingusedtocreateorupdateanote.Atthispoint,we'readdinganewnote,sononoteobjectexists.Thetemplatecodeisbeingwrittendefensivelytonotthrowerrors.

Page 293: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ThistemplateisaformthatwillPOSTitsdatatothenotessaveURL.Ifyouweretoruntheapplicationatthistime,itwouldgiveyouanerrormessagebecausenorouteisconfiguredforthatURL.

TosupportthenotessaveURL,addthistoroutes/notes.js:

//SaveNote(update)

router.post('/save',async(req,res,next)=>{

varnote;

if(req.body.docreate==="create"){

note=awaitnotes.create(req.body.notekey,

req.body.title,req.body.body);

}else{

note=awaitnotes.update(req.body.notekey,

req.body.title,req.body.body);

}

res.redirect('notesview?key='+req.body.notekey);

});

BecausethisURLwillalsobeusedforbothcreatingandupdatingnotes,itneedstodetectthedocreateflagandcalltheappropriatemodeloperation.

ThemodelreturnsaPromiseforbothnotes.createandnotes.update.Ofcourse,wemustcallthecorrespondingModelfunctionbasedonthedocreateflag.

ThisisaPOSToperationhandler.BecauseofthebodyParsermiddleware,theformdataisaddedtothereq.bodyobject.Thefieldsattachedtoreq.bodycorresponddirectlytoelementsintheHTMLform.

Now,wecanruntheapplicationagainandusetheAddaNoteform:

Page 294: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ButuponclickingontheSubmitbutton,wegetanerrormessage.Thereisn'tanything,yet,implementingthenotesviewURL.

YoucanmodifytheURLinthelocationboxtorevisithttp://localhost:3000,andyou'llseesomethinglikethefollowingscreenshotonthehomepage:

Page 295: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Thenoteisactuallythere;wejustneedtoimplementnotesview.Let'sgetonwiththat.

Page 296: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Viewingnotes–readNowthatwe'velookedathowtocreatenotes,weneedtomoveontoreadingthem.ThismeansimplementingcontrollerlogicandviewtemplatesforthenotesviewURL.

Toroutes/notes.js,addthisrouterfunction:

//ReadNote(read)

router.get('/view',async(req,res,next)=>{

varnote=awaitnotes.read(req.query.key);

res.render('noteview',{

title:note?note.title:"",

notekey:req.query.key,note:note

});

});

Becausethisrouteismountedonarouterhandling/notes,thisroutehandlesnotesview.

Ifnotes.readsuccessfullyreadsthenote,itisrenderedwiththenoteviewtemplate.Ifsomethinggoeswrong,we'llinsteaddisplayanerrortotheuserthroughExpress.

Totheviewsdirectory,addthenoteview.hbstemplate,referencedbythiscode:{{#ifnote}}<h3>{{note.title}}</h3>{{/if}}{{#ifnote}}<p>{{note.body}}</p>{{/if}}<p>Key:{{notekey}}</p>{{#ifnotekey}}<hr/><p><ahref="notesdestroy?key={{notekey}}">Delete</a>|<ahref="notesedit?key={{notekey}}">Edit</a></p>{{/if}}

Page 297: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Asexpected,withthiscode,theapplicationcorrectlyredirectstonotesview,andwecanseeourhandiwork.Also,asexpected,clickingoneithertheDeleteorEditlinkswillgiveyouanerror,becausethecodehasn'tbeenimplemented.

Thisisstraightforward:takingdataoutofthenoteobjectanddisplayingusingHTML.Atthebottomaretwolinks,onetonotesdestroytodeletethenoteandtheothertonotesedittoeditit.

Thecodeforneitheroftheseexistsatthemoment.Butthatwon'tstopusfromgoingaheadandexecutingtheapplication:

Page 298: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Editinganexistingnote–updateNowthatwe'velookedatthecreateandreadoperations,let'slookathowtoupdateoreditanote.

Toroutes/notes.js,addthisrouterfunction:

//Editnote(update)

router.get('/edit',async(req,res,next)=>{

varnote=awaitnotes.read(req.query.key);

res.render('noteedit',{

title:note?("Edit"+note.title):"AddaNote",

docreate:false,

notekey:req.query.key,note:note

});

});

We'rereusingthenoteedit.ejstemplate,becauseitcanbeusedforbothcreateandupdate/editoperations.Noticethatwepassfalsefordocreate,informingthetemplatethatitistobeusedforediting.

Inthiscase,wefirstretrievethenoteobjectandthenpassitthroughtothetemplate.Thisway,thetemplateissetupforeditingratherthannotecreation.WhentheuserclicksontheSubmitbutton,we'llendupinthesamenotessaveroutehandlershownintheprecedingscreenshot.Italreadydoestherightthing:callingthenotes.updatemethodinthemodelratherthannotes.create.

Becausethat'sallweneeddo,wecangoaheadandreruntheapplication:

Page 299: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ClickontheSubmitbuttonhere,andyouwillberedirectedtothenotesviewscreenandwillthenbeabletoreadthenewlyeditednote.Backtothenotesviewscreen:we'vejusttakencareoftheEditlink,buttheDeletelinkstillproducesanerror.

Page 300: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Deletingnotes–destroyNow,let'slookathowtoimplementthenotesdestroyURLtodeletenotes.

Toroutes/notes.js,addthefollowingrouterfunction:

//AsktoDeletenote(destroy)

router.get('/destroy',async(req,res,next)=>{

varnote=awaitnotes.read(req.query.key);

res.render('notedestroy',{

title:note?note.title:"",

notekey:req.query.key,note:note

});

});

Destroyinganoteisasignificantstepifonlybecausethere'snotrashcantoretrieveitfromifwemakeamistake.Therefore,wewanttoasktheuserwhetherthey'resuretheywanttodeletethatnote.Inthiscase,weretrievethenoteandthenrenderthefollowingpage,displayingaquestiontoensuretheydowanttodeletethenote.

Totheviewsdirectory,addanotedestroy.hbstemplate:<formmethod='POST'action='notesdestroy/confirm'><inputtype='hidden'name='notekey'value='{{#ifnote}}{{notekey}}{{/if}}'><p>Delete{{note.title}}?</p><br/><inputtype='submit'value='DELETE'/><ahref="notesview?key={{#ifnote}}{{notekey}}{{/if}}">Cancel</a></form>

Thisisasimpleform,askingtheusertoconfirmbyclickingonthebutton.TheCancellinkjustsendsthembacktothenotesviewpage.ClickingontheSubmitbuttongeneratesaPOSTrequestonthenotesdestroy/confirmURL.

Page 301: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Nowthateverythingisworkingintheapplication,youcanclickonanybuttonorlinkandkeepallthenotesyouwant.

ThatURLneedsarequesthandler.Addthiscodetoroutes/notes.js://Reallydestroynote(destroy)router.post('destroyconfirm',async(req,res,next)=>{awaitnotes.destroy(req.body.notekey);res.redirect('/');});

Thiscallsthenotes.destroyfunctioninthemodel.Ifitsucceeds,thebrowserisredirectedtothehomepage.Ifnot,anerrormessageisshowntotheuser.Rerunningtheapplication,wecannowviewitinaction:

Page 302: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ThemingyourExpressapplicationTheExpressteamhasdoneadecentjobofmakingsureExpressapplicationslookokayoutofthegate.OurNotesapplicationwon'twinanydesignawards,butatleastitisn'tugly.There'salotofwaystoimproveit,nowthatthebasicapplicationisrunning.Let'stakeaquicklookattheminganExpressapplication.InChapter6,ImplementingtheMobile-FirstParadigm,we'lltakeadeeperdive,focusingonthatall-importantgoalofaddressingthemobilemarket.

Ifyou'rerunningtheNotesapplicationusingtherecommendedmethod,npmstart,anicelogofactivityisbeingprintedinyourconsolewindow.Oneofthoseisthefollowing:

GETstylesheetsstyle.css3040.702ms--

Thisisduetothislineofcodethatweputinlayout.hbs:

<linkrel='stylesheet'href='stylesheetsstyle.css'/>

ThisfilewasautogeneratedforusbytheExpressGeneratorattheoutsetanddroppedinsidethepublicdirectory.ThepublicdirectoryismanagedbytheExpressstaticfileserver,usingthislineinapp.js:

app.use(express.static(path.join(__dirname,'public')));

Let'sopenpublicstylesheetsstyle.cssandtakealook:

body{

padding:50px;

Page 303: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

font:14px"LucidaGrande",Helvetica,Arial,sans-serif;

}

a{

color:#00B7FF;

}

Somethingthatleapsoutisthattheapplicationcontenthasalotofwhitespaceatthetopandleft-handsidesofthescreen.Thereasonisthatbodytagshavethepadding:50pxstyle.Changingitisquickbusiness.

SincethereisnocachingintheExpressstaticfileserver,wecansimplyedittheCSSfileandreloadthepage,andtheCSSwillbereloadedaswell.It'spossibletoturnoncache-controlheadersandETagsgeneration,asyouwoulddoforaproductionwebsite.LookintheonlineExpressdocumentationfordetails.

Itinvolvesalittlebitofwork:

body{

padding:5px;

..

}

..

header{

background:#eeeeee;

padding:5px;

}

Asaresult,we'llhavethis:

Page 304: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

We'renotgoingtowinanydesignawardswiththiseither,butthere'sthebeginningofsomebrandingandthemingpossibilities.

Generallyspeaking,thewaywe'vestructuredthepagetemplates,applyingasite-widethemeisjustamatterofaddingappropriatecodetolayout.hbsalongwithappropriatestylesheetsandotherassets.Manyofthemodernthemingframeworks,suchasTwitter'sBootstrap,serveupCSSandJavaScriptfilesoutofaCDNserver,makingitincrediblyeasytoincorporateintoasitedesign.

ForjQuery,refertohttp://jquery.com/download/.

Google'sHostedLibrariesserviceprovidesalonglistoflibraries,hostedonGoogle'sCDNinfrastructure.Refertohttps://developers.google.com/speed/libraries/.

Whileit'sstraightforwardtousethird-partyCDNstohosttheseassets,it'ssafertohostthemyourself.Notonlydoyoutakeresponsibilityfor

Page 305: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

bandwidthconsumptionofyourapplication,butyou'recertainofnotbeingaffectedbyanyoutagesofthird-partyservices.AsreliableasGooglemightbe,theirservicecangodown,andifthatmeansjQueryandBootstrapdoesn'tload,yourcustomerwillthinkyoursiteisbroken.Butifthosefilesareloadedfromthesameserverasyourapplication,thereliabilityofdeliveringthosefileswillexactlyequalthereliabilityofyourapplication.

InChapter6,ImplementingtheMobile-FirstParadigm,wewilllookatasimplemethodtoaddthosefront-endlibrariestoyourapplication.

Page 306: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Scalingup–runningmultipleNotesinstancesNowthatwe'vegotourselvesarunningapplication,you'llhaveplayedaroundabitandcreated,read,updated,anddeletedmanynotes.

Supposeforamomentthisisn'tatoyapplication,butonethatisinterestingenoughtodrawamillionusersaday.Servingahighloadtypicallymeansaddingservers,loadbalancers,andmanyotherthings.Acorepartistohavemultipleinstancesoftheapplicationrunningatthesametimetospreadtheload.

Let'sseewhathappenswhenyourunmultipleinstancesoftheNotesapplicationatthesametime.

Thefirstthingistomakesuretheinstancesareondifferentports.Inbin/www,you'llseethatsettingthePORTenvironmentvariablecontrolstheportbeingused.IfthePORTvariableisnotset,itdefaultstohttp://localhost:3000,orwhatwe'vebeenusingallalong.

Let'sopenuppackage.jsonandaddtheselinestothescriptssection:

"scripts":{

"start":"DEBUG=notes:*node./bin/www",

"server1":"DEBUG=notes:*PORT=3001node./bin/www",

"server2":"DEBUG=notes:*PORT=3002node./bin/www"},

Theserver1scriptrunsonPORT3001,whiletheserver2scriptrunsonPORT3002.Isn'titnicetohaveallthisdocumentedinoneplace?

Then,inonecommandwindow,runthis:

Page 307: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

$npmrunserver1

>[email protected]/chap05/notes

>DEBUG=notes:*PORT=3001node./bin/www

notes:serverListeningonport3001+0ms

Inanothercommandwindow,runthis:

$npmrunserver2

>[email protected]/chap05/notes

>DEBUG=notes:*PORT=3002node./bin/www

notes:serverListeningonport3002+0ms

ThisgivesustwoinstancesoftheNotesapplication.Usetwobrowserwindowstovisithttp://localhost:3001andhttp://localhost:3002.Enteracoupleofnotes,andyoumightseesomethinglikethis:

Page 308: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Aftereditingandaddingsomenotes,yourtwobrowserwindowscouldlookliketheprecedingscreenshot.Thetwoinstancesdonotsharethesamedatapool.Eachisinsteadrunninginitsownprocessandmemoryspace.Youaddanoteinone,anditdoesnotshowintheotherscreen.

Additionally,becausethemodelcodedoesnotpersistdataanywhere,thenotesarenotsaved.YoumighthavewrittenthegreatestNode.jsprogrammingbookofalltime,butassoonastheapplicationserverrestarts,it'sgone.

Typically,yourunmultipleinstancesofanapplicationtoscaleperformance.That'stheoldthrowmoreserversatittrick.Forthistowork,thedataofcoursemustbeshared,andeachinstancemustaccessthesamedatasource.Typically,thisinvolvesadatabase.Andwhenitcomestouseridentityinformation,itmightevenentailarmedguards.

Page 309: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Holdon—we'llgettodatabaseimplementationshortly.Beforethat,we'llcovermobile-firstdevelopment.

Page 310: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SummaryWe'vecomealongwayinthischapter.

WestartedwiththePyramidofDoomandhowthePromiseobjectandasyncfunctionscanhelpusatameasynchronouscode.We'llbeusingthesetechniquesallthroughthisbook.

WequicklymovedontowritingthefoundationofarealapplicationwithExpress.Atthemoment,itkeepsitsdatainmemory,butithasthebasicfunctionalityofwhatwillbecomeanote-takingapplicationsupportingreal-timecollaborativecommentingonthenotes.

Inthenextchapter,we'lldipourtoesinthewaterofresponsive,mobile-friendlywebdesign.Duetothegrowingpopularityofmobilecomputingdevices,it'sbecomenecessarytoaddressmobiledevicesfirstbeforedesktopcomputerusers.Inordertoreachthosemillionsofusersaday,theNotesapplicationusersneedagooduserexperiencewhenusingtheirsmartphone.

Infollowingchapters,we'llkeepgrowingthecapabilitiesoftheNotesapplication,startingwithdatabasestoragemodels.

Page 311: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ImplementingtheMobile-FirstParadigmNowthatourfirstExpressapplicationisusable,weactonthemantraofthisageofsoftwaredevelopment:mobile-first.Mobiledevices,whethertheybesmartphones,tabletcomputers,automobiledashboards,refrigeratordoors,orbathroommirrors,aretakingovertheworld.

Anotherissueismobile-firstindexing,meaningthatsearchenginesarestartingtopreferenceindexingthemobileversionofawebsite.Searchenginessofarconcentratedonindexingthedesktopversionofwebsites,butthegrowingpopularityofmobiledevicesmeanssearchengineresultsareskewedawayfromwhatfolksareusing.Googlesaysitisnotfairtomobileusersifthesearchresult,whichwasderivedfromthedesktopversion,doesnotmatchthemobileversionofawebsite.ForGoogle'stake,includingtechnicaltipsonthemarkuptouse,seehttp://webmasters.googleblog.com/2017/12/getting-your-site-ready-for-mobile.html.

Theprimaryconsiderationsindesigningformobilesarethesmallscreensizes,thetouch-orientedinteraction,thatthere'snomouse,andthesomewhatdifferentuserinterfaceexpectations.WiththeNotesapplication,ouruserinterfaceneedsaremodest,andthelackofamousedoesn'tmakeanydifferencetous.

Inthischapter,wewon'tdomuchNode.jsdevelopment.Instead,we'll:

Modifythetemplatesforbettermobilepresentation

EditCSSandSASSfilestocustomizethestyle

LearnaboutBootstrap4,apopularframeworkforresponsiveUIdesign

Page 312: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Bydoingso,we'lldipourtoesinthewaterofwhatitmeanstobeafullstackwebengineer.

Page 313: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Problem–theNotesappisn'tmobilefriendlyLet'sstartbyquantifyingtheproblem.Weneedtoexplorehowwell(ornot)theapplicationbehavesonamobiledevice.Thisissimpletodo:

1. StarttheNotesapplication.DeterminetheIPaddressofthehostsystem.

2. Usingyourmobiledevice,connecttotheserviceusingtheIPaddress,andbrowsearoundtheNotesapplication,puttingitthroughitspaces,notinganydifficulties.

Anotherwaytoapproachthisistouseyourdesktopbrowser,resizingittobeverynarrow.TheChromeDevToolsalsoincludesamobiledeviceemulator.Eitherway,youcanmimicthesmallscreensizeofasmartphoneonyourdesktop.

Toseearealuserinterfaceproblemonamobilescreen,editviews/noteedit.ejsandchangethisline:

<br/><textarearows=5cols=80name='body'>

{{#ifnote}}{{note.body}}{{/if}}

</textarea>

What'schangedisthecols=80parameter.Wewantthistextareaelementtobeoverlylargesothatyoucanexperiencehowanon-responsivewebappappearsonamobiledevice.Viewtheapplicationonamobiledeviceandyou'llseesomethinglikeoneofthescreensinthisscreenshot:

Page 314: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you
Page 315: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ViewinganoteworkswellonaniPhone6,butthescreenforediting/addinganoteisnotgood.Thetextentryareaissowidethatitrunsoffthesideofthescreen.EventhoughinteractionwithFORMelementsworkwell,it'sclumsy.Ingeneral,browsingtheNotesapplicationgivesanacceptablemobileuserexperiencethatdoesn'tsuckandwon'tmakeourusersgiveravereviews.

Page 316: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Mobile-firstparadigmMobiledeviceshaveasmallerscreen,aregenerallytouchoriented,andhavedifferentuserexperienceexpectationsthanadesktopcomputer.

Toaccommodatesmallerscreens,weuseresponsivewebdesigntechniques.Thismeansdesigningtheapplicationtoaccommodatethescreensizeandensuringwebsitesprovideoptimalviewingandinteractionacrossawiderangeofdevices.Techniquesincludechangingfontsizes,rearrangingelementsonthescreen,usingcollapsibleelementsthatopenwhentouched,andresizingimagesorvideostofitavailablespace.Thisiscalledresponsivebecausetheapplicationrespondstodevicecharacteristicsbymakingthesechanges.

Bymobile-first,wemeanthatyoudesigntheapplicationfirsttoworkwellonamobiledeviceandthenmoveontodeviceswithlargerscreens.It'saboutprioritizingmobiledevicesfirst.

Theprimarytechniqueisusingmediaqueriesinstylesheetstodetectdevicecharacteristics.Eachmediaquerysectiontargetsarangeofdevices,usingCSSdeclarationtoappropriatelyrestylecontent.

Let'sconsultaconcreteexample.TheTwentyTwelvethemeforWordpresshasastraightforwardresponsivedesignimplementation.It'snotbuiltwithanyframework,soyoucanseeclearlyhowthemechanismworks,andthestylesheetissmallenoughtobeeasilydigestible.RefertoitssourcecodeintheWordpressrepositoryathttps://themes.svn.wordpress.org/twentytwelve/1.9/style.css.

Thestylesheetstartswithanumberofresets,wherethestylesheetoverridessometypicalbrowserstylesettingswithcleardefaults.Then,thebulkofthestylesheetdefinesstylingformobiledevices.TowardthebottomofthestylesheetisasectionlabeledMediaquerieswhere,forcertainsizedscreens,thestylesdefinedformobiledevicesareoverridden

Page 317: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

toworkondeviceswithlargerscreens.

Itdoesthiswiththefollowingtwomediaqueries:

@mediascreenand(min-width:600px){/*Screensabove600pxwidth}

@mediascreenand(min-width:960px){Screensabove960pxwidth*/}

Thefirstsegmentofthestylesheetconfiguresthepagelayoutforalldevices.Next,foranybrowserviewportatleast600pxwide,reconfigurethepagetodisplayonthelargerscreen.Then,foranybrowserviewportatleast960pxwide,reconfigureitagain.Thestylesheethasafinalmediaquerytocoverprintdevices.

Thesewidthsarewhat'scalledabreakpoint.Thosethresholdviewportwidthsarewherethedesignchangesitselfaround.Youcanseebreakpointsinactionbygoingtoanyresponsivewebsite,thenresizingthebrowserwindow.Watchhowthedesignjumpsatcertainsizes.Thosearethebreakpointschosenbytheauthorofthatwebsite.

There'sawiderangeofdifferingopinionsaboutthebeststrategytochooseyourbreakpoints.Doyoutargetspecificdevicesordoyoutargetgeneralcharacteristics?TheTwentyTwelvethemedidfairlywellonmobiledevicesusingonlytwoviewport-sizemediaqueries.TheCSS-Tricksbloghaspostedanextensivelistofspecificmediaqueriesforeveryknowndevice,whichisavailableathttps://css-tricks.com/snippets/css/media-queries-for-standard-devices/.

Weshouldatleasttargetthesedevices:

Small:ThisincludesiPhone5SE.

Medium:Thiscanrefertotabletcomputersorthelargersmartphones.

Large:Thisincludeslargertabletcomputersorthesmallerdesktopcomputers.

Page 318: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Extra-large:Thisreferstolargerdesktopcomputersandotherlargescreens.

Landscape/portrait:Youmaywanttocreateadistinctionbetweenlandscapemodeandportraitmode.Switchingbetweenthetwoofcoursechangesviewportwidth,possiblypushingitpastabreakpoint.However,yourapplicationmayneedtobehavedifferentlyinthetwomodes.

Enoughwiththetheory;let'sgetbacktoourcode.

Page 319: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

UsingTwitterBootstrapontheNotesapplicationBootstrapisamobile-firstframeworkconsistingofHTML5,CSS3,andJavaScriptcodeprovidingacomprehensivesetofworldclass,responsivewebdesigncomponents.ItwasdevelopedbyengineersatTwitterandthenreleasedtotheworldinAugust2011.

Theframeworkincludescodetoretrofitmodernfeaturesontoolderbrowsers,aresponsive12-columngridsystem,andalonglistofcomponents(someusingJavaScript)forbuildingwebapplicationsandwebsites.It'smeanttoprovideastrongfoundationonwhichtobuildyourapplication.

Refertohttp://getbootstrap.comformoredetails.

Page 320: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SettingitupThefirststepistoduplicatethecodeyoucreatedinthepreviouschapter.If,forexample,youcreatedadirectorynamedchap05/notes,thencreateonenamedchap06/notesfromthecontentofchap05/notes.

Now,weneedtogoaboutaddingBootstrap'scodeintheNotesapplication.TheBootstrapwebsitesuggestsloadingtherequiredCSSandJavaScriptfilesoutoftheBootstrap(andjQuery)publicCDN.Whilethat'seasytodo,wewon'tdothisfortworeasons:

Itviolatestheprincipleofkeepingalldependencieslocaltotheapplicationandnotrelyingonglobaldependencies

Itpreventsusfromgeneratingacustomtheme

Instead,we'llinstallalocalcopyofBootstrap.ThereareseveralwaystoinstallBootstrap.Forexample,theBootstrapwebsiteoffersadownloadableTAR/GZIParchive(tarball).Thebetterapproachisanautomateddependencymanagementtool.

ThemoststraightforwardchoiceisusingBootstrap(https://www.npmjs.com/package/bootstrap),popper.js(https://www.npmjs.com/package/popper.js),andjQuery(https://www.npmjs.com/package/jquery)packagesinthenpmrepository.ThesepackagesprovidenoNode.jsmodules,andinsteadarefrontendcodedistributedthroughnpm.

Weinstallthepackagesusingthefollowingcommand:

[email protected]

npmnoticecreatedalockfileaspackage-lock.json.Youshouldcommitthis

file.

Page 321: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

[email protected]@1.9.1-3butnoneis

installed.Youmustinstallpeerdependenciesyourself.

[email protected]@^1.14.0butnoneis

installed.Youmustinstallpeerdependenciesyourself.

[email protected]

added1packagein1.026s

[email protected]

[email protected]

[email protected]

[email protected]

Asweseehere,whenweinstallBootstrap,ithelpfullytellsusthecorrespondingversionsofjQueryandpopper.jstouse.Therefore,wedutifullyinstallthoseversions.What'smostimportantistoseewhatgotdownloaded:

$lsnode_modules/bootstrap/dist/*

...directorycontents

$lsnode_modules/jquery/

...directorycontents

$lsnode_modules/popper.js/dist

...directorycontents

WithineachofthesedirectoriesaretheCSSandJavaScriptfilesthataremeanttobeusedinthebrowser.Moreimportantly,thesefilesarelocatedinagivendirectorywhosepathnameisknown—specifically,thedirectorieswejustinspected.Let'sseehowtoconfigureourExpressapptousethosethreepackagesonthebrowserside.

Page 322: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

AddingBootstraptoapplicationtemplatesOntheBootstrapwebsite,theygivearecommendedHTMLstructure.We'llbeinterpolatingfromtheirrecommendationtouseBootstrapcodeprovidedthroughtheCDNtoinsteadusethelocalcopiesofBootstrap,jQuery,andPopperthatwejustinstalled.RefertotheGettingstartedpageathttp://getbootstrap.com/docs/4.0/getting-started/introduction/.

Whatwe'lldoismodifyviews/layout.hbstomatchtheirrecommendedtemplate:

<!doctypehtml>

<htmllang="en">

<head>

<title>{{title}}</title>

<metacharset="utf-8">

<metaname="viewport"

content="width=device-width,initial-scale=1,shrink-to-

fit=no">

<linkrel="stylesheet"

href="assetsvendor/bootstrap/css/bootstrap.min.css">

<linkrel='stylesheet'href='assetsstylesheets/style.css'/>

</head>

<body>

{{>header}}

{{{body}}}

<!--jQueryfirst,thenPopper.js,thenBootstrapJS-->

<scriptsrc="assetsvendor/jquery/jquery.min.js"></script>

<scriptsrc="assetsvendor/popper.js/popper.min.js"></script>

<scriptsrc="assetsvendor/bootstrap/js/bootstrap.min.js"></script>

</body>

</html>

ThisislargelythetemplateshownontheBootstrapsite,incorporatingthepreviouscontentofviews/layout.hbs.Ourownstylesheetisloadedfollowing

Page 323: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

theBootstrapstylesheet,givingustheopportunitytooverrideanythinginBootstrapwewanttochange.What'sdifferentisthatinsteadofloadingBootstrap,popper.js,andjQuerypackagesfromtheirrespectiveCDNs,weusethepathassetsvendor/product-nameinstead.

ThisisthesameasrecommendedontheBootstrapwebsiteexcepttheURLspointtoourownsiteratherthanrelyingonthepublicCDN.

ThisassetsvendorURLisnotcurrentlyrecognizedbytheNotesapplication.Toaddthissupport,editapp.jstoaddtheselines:

app.use(express.static(path.join(__dirname,'public')));

app.use('assetsvendor/bootstrap',express.static(

path.join(__dirname,'node_modules','bootstrap','dist')));

app.use('assetsvendor/jquery',express.static(

path.join(__dirname,'node_modules','jquery')));

app.use('assetsvendor/popper.js',express.static(

path.join(__dirname,'node_modules','popper.js','dist')));

Withinthepublicdirectory,wehavealittlehouse-keepingtodo.Whenexpress-generatorsetuptheinitialproject,itgeneratedpublic/images,public/javascripts,andpublic/stylesheetsdirectories.Wewanteachtobewithinthe/assetsdirectory,sodothis:

$mkdirpublic/assets

$mvpublic/images/public/javascripts/public/stylesheets/publicassets

Wenowhaveourassetfiles,includingBootstrap,popper.js,andjQuery,allavailabletotheNotesapplicationunderthe/assetsdirectory.ThepagelayoutreferstotheseassetsandshouldgiveusthedefaultBootstraptheme:

$npmstart

>[email protected]/Users/David/chap06/notes

>DEBUG=notes:*node./bin/www

notes:serverListeningonport3000+0ms

GET/200306.660ms-883

GET/stylesheets/style.css404321.057ms-2439

GETassetsstylesheets/style.css200160.371ms-165

Page 324: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

GETassetsvendor/bootstrap/js/bootstrap.min.js200157.459ms-50564

GETassetsvendor/popper.js/popper.min.js200769.508ms-18070

GETassetsvendor/jquery/jquery.min.js200777.988ms-92629

GETassetsvendor/bootstrap/css/bootstrap.min.css200788.028ms-127343

Theon-screendifferencesareminor,butthisistheproofnecessarythattheCSSandJavaScriptfilesforBootstraparebeingloaded.Wehaveaccomplishedthefirstmajorgoal—usingamodern,mobile-friendlyframeworktoimplementamobile-firstdesign.

Page 325: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

AlternativelayoutframeworksBootstrapisn'ttheonlyJavaScript/CSSframeworkprovidingaresponsivelayoutandusefulcomponents.We'reusingBootstrapinthisprojectbecauseofitspopularity.Theseframeworksareworthyofalook:

Pure.css(https://purecss.io/):AresponsiveCSSframeworkwithanemphasisonasmallcodefootprint.

PicnicCSS(https://picnicss.com/):AresponsiveCSSframeworkemphasizingsmallsizeandbeauty.

Shoelace(https://shoelace.style/):ACSSframeworkemphasizingusingfutureCSS,meaningitusesCSSconstructsattheleadingedgeofCSSstandardization.Sincemostbrowsersdon'tsupportthosefeatures,cssnext(http://cssnext.io/)isusedtoretrofitthatsupport.ShoelaceusesagridlayoutsystembasedonBootstrap'sgrid.

PaperCSS(https://www.getpapercss.com/):AninformalCSSframeworkwhichlookslikeitwashanddrawn.

Foundation(https://foundation.zurb.com/):Self-describedasthemostadvancedresponsivefrontendframeworkintheworld.

Base(http://getbase.org/):AlightweightmodernCSSframework.

HTML5Boilerplate(https://html5boilerplate.com/)isanextremelyusefulbasisfromwhichtocodetheHTMLandotherassets.ItcontainsthecurrentbestpracticesfortheHTMLcodeinwebpages,aswellastoolsto

Page 326: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

normalizeCSSsupportandconfigurationfilesforseveralwebservers.

Page 327: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

FlexboxandCSSGridsOthernewtechnologiesimpactingwebapplicationdevelopmentaretwonewCSSlayoutmethodologies.TheCSS3committeehasbeenworkingonseveralfronts,includingpagelayout.

Inthedistantpast,weusednestedHTMLtablesforpagelayout.Thatisabadmemorythatwedon'thavetorevisit.Morerecently,we'vebeenusingaboxmodelusingDIVs,andevenattimesusingabsoluteorrelativeplacementtechniques.Allthesetechniqueshavebeensuboptimalinseveralways,somemorethanothers.

Onepopularlayouttechniqueistodividethehorizontalspaceintocolumnsandassignacertainnumberofcolumnstoeachthingonthepage.Withsomeframeworks,wecanevenhavenestedDIVs,eachwiththeirownsetofcolumns.Bootstrap3,andothermodernframeworks,usedthatlayouttechnique.

ThetwonewCSSlayoutmethodologies,Flexbox(https://en.wikipedia.org/wiki/CSS_flex-box_layout)andCSSGrids(https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout),areasignificantimprovementoverallpreviousmethodologies.Wearementioningthesetechnologiesbecausethey'rebothworthyofattention.Botharesomewhatearlyintheiradoptioncurve—they'vebeenstandardizedbycommitteesandadoptedinthelatestbrowsers,butofcoursetherearealotofoldbrowsersinthefield.

WithBootstrap4,theBootstrapteamchosetogowithFlexbox.Therefore,underthehoodareFlexboxCSSconstructs.

Page 328: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Mobile-firstdesignfortheNotesapplicationWe'velearnedaboutthebasicsofresponsivedesignandBootstrap,andwehookedtheBootstrapframeworkintoourapplication.Nowwe'rereadytolauncharedesignoftheapplicationsothatitworkswellonmobiledevices.

Page 329: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

LayingtheBootstrapgridfoundationBootstrapusesa12-columngridsystemtocontrollayout,givingapplicationsaresponsivemobile-firstfoundationonwhichtobuild.Itautomaticallyscalescomponentsastheviewportchangessizeorshape.Themethodrelieson<div>elementswithclassestodescribetheroleeach<div>playsinthelayout.

Thebasiclayoutpatternisasfollows:

<divclass="container-fluid">

<divclass="row">

<divclass="col-sm-3">Column1content</div><!--25%-->

<divclass="col-sm-9">Column2content</div><!--75%-->

</div>

<divclass="row">

<divclass="col-sm-3">Column1content</div><!--25%-->

<divclass="col-sm-6">Column2content</div><!--50%-->

<divclass="col-sm-3">Column3content</div><!--25%-->

</div>

</div>

Theoutermostlayeristhe.containeror.container-fluidelement.Containersprovideameanstocenterorhorizontallypadthecontent.Containersmarkedas.container-fluidactasiftheyhavewidth:100%,meaningtheyexpandtofillthehorizontalspace.

A.rowiswhatitsoundslike,a"row".Technically,arowisawrapperforcolumns.Containersarewrappersforrows,androwsarewrappersforcolumns,andcolumnscontainthestuffdisplayedtoourusers.Gotthat?

Columnsaremarkedwithvariationsofthe.colclass.Withthebasiccolumnclass,.col,thecolumnsdivideequallyintotheavailablespace.

Page 330: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Youcanspecifyanumericalcolumncounttoassigndifferentwidthstoeachcolumn.Bootstrapsupportsupto12numberedcolumns,henceeachrowintheexampleaddsupto12columns.

Youcanalsospecifyabreakpointtowhichthecolumnapplies:

Usingcol-xstargetsextra-smalldevices(smartphones,<576px)

Usingcol-smtargetssmalldevices(>=576px)

Usingcol-mdtargetsmediumdevices(>=768px)

Usingcol-lgtargetslargedevices(>=992px)

Usingcol-xltargetsextra-largedevices(>=1200px)

Specifyingabreakpoint,forexamplecol-sm,meansthatitappliestodevicesmatchingthatbreakpoint,orlarger.Hence,intheexampleshownearlier,thecolumndefinitionsappliedtocol-sm,col-md,col-lg,andcol-xldevices,butnottocol-xsdevices.

Thecolumncountisappendedtotheclassname.Thatmeansusingcol-#whennottargetingabreakpoint,forexample,col-4,orcol-{breakpoint}-#whentargetingabreakpoint,forexample,col-md-4.Ifthecolumnsadduptomorethan12,thecolumnsbeyondthetwelfthcolumnwraparoundtobecomeanewrow.Thewordautocanbeusedinsteadofanumericalcolumncounttosizethecolumntothenaturalwidthofitscontent.

It'spossibletomixandmatchtotargetmultiplebreakpoints:

<divclass="container-fluid">

<divclass="row">

<divclass="col-xs-9col-md-3col-lg-6">Column1content</div>

<divclass="col-xs-3col-md-9col-lg-6">Column2content</div>

</div>

...

</div>

Page 331: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Thisdeclaresthreedifferentlayouts,oneforextra-smalldevices,anotherformediumdevices,andthelastforlargedevices.ThisgivesusenoughtostartmodifyingtheNotesapplication.Thegridsystemcandoalotmore.Fordetails,seethedocumentation:http://getbootstrap.com/docs/4.0/layout/grid/.

Page 332: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

<!DOCTYPEhtml>

<html>

<head>..headerStuff</head>

<body>

..pageHeader

..maincontent<br/>..bottomOfPageStuff

</body>

</html>

Thepagecontentthereforehastwovisiblerows:theheaderandthemaincontent.AtthebottomofthepageareinvisiblethingsliketheJavaScriptfilesforBootstrapandjQuery.

Nochangeisrequiredinviews/layout.hbs.Onemightthinkthecontainer-fluidwrapperwouldbeinthatfile,withtherowsandcolumnsspecifiedintheothertemplates.Instead,we'lldoitinthetemplatestogiveusthemostlayoutfreedom.

Page 333: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

UsingiconlibrariesandimprovingvisualappealTheworldaroundusisn'tconstructedofwords,butinsteadthings.Hence,apictorialstyle,asicons,shouldhelpcomputersoftwaretobemorecomprehensible.Givingagooduserexperienceshouldmakeourusersrewarduswithmorelikesintheappstore.

Thereareseveraliconlibrariesthatcanbeusedinawebsite.TheBootstrapteamhasacuratedlistathttp://getbootstrap.com/docs/4.1/extend/icons/.Forthisproject,we'lluseFeatherIcons(https://feathericons.com/)anditsconvenientlyavailablenpmpackage,https://www.npmjs.com/package/feathericons.

Inpackage.json,addthistothedependencies:

"feathericons":">=4.5.x"

Thenrunnpminstalltodownloadthenewpackage.Youcantheninspectthedownloadedpackageandseethat./node_modules/feathericons/dist/feather.jscontainsbrowser-sidecode,makingiteasytousetheicons.

Wemakethatdirectoryavailablebymountingitinapp.js,justaswedidforBootstrapandjQuerylibraries.Addthiscodetoapp.js:

app.use('assetsvendor/feathericons',express.static(

path.join(__dirname,'node_modules','feathericons','dist')));

Goingbythedocumentation,wemustputthisatthebottomofviews/layout.hbstoenablefeathericonssupport:

Page 334: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

<scriptsrc="assetsvendor/feathericons/feather.js"></script>

<script>

feather.replace();

</script>

Touseoneoftheicons,useadata-featherattributespecifyingoneoftheiconnames,likeso:

<idata-feather="circle"></i>

What'simportantisthedata-featherattribute,whichtheFeatherIconslibraryusestoidentifytheSVGfiletouse.TheFeatherIconslibrarycompletelyreplacestheelementwhereitfoundthedata-featherattribute.Therefore,ifyouwanttheicontobeaclickablelink,it'snecessarytowraptheicondefinitionwithan<a>tag,ratherthanaddingdata-feathertothe<a>tag.Thenextsectionshowsanexample.

Page 335: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ResponsivepageheadernavigationbarTheheadersectionwedesignedbeforecontainsapagetitleandalittlenavigationbar.Bootstraphasseveralwaystospiffthisup,andevengiveusaresponsivenavigationbarwhichneatlycollapsestoamenuonsmalldevices.

Inviews/pageHeader.ejs,makethischange:

<headerclass="page-header">

<h1>{{title}}</h1>

<navclass="navbarnavbar-expand-mdnavbar-darkbg-dark">

<aclass="navbar-brand"href=''><idata-feather="home"><i></a>

<buttonclass="navbar-toggler"type="button"

data-toggle="collapse"data-target="#navbarSupportedContent"

aria-controls="navbarSupportedContent"

aria-expanded="false"aria-label="Togglenavigation">

<spanclass="navbar-toggler-icon"></span>

</button>

<divclass="collapsenavbar-collapse"id="navbarSupportedContent">

<divclass="navbar-navcol">

{{#ifbreadcrumb}}

<aclass="nav-itemnav-link"href='{{breadcrumb.url}}'>

{{breadcrumb.title}}</a>

{{/if}}

</div>

<aclass="nav-itemnav-linkbtnbtn-lightcol-auto"

href='notesadd'>ADDNote</a>

</div>

</nav>

</header>

Addingclass="page-header"informsBootstrapthisis,well,thepageheader.Withinthatwehavethe<h1>headerasbefore,givingthepagetitle,andthenaresponsiveBootstrapnavbar.

Page 336: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Bydefaultthenavbarisexpanded—meaningthecomponentsinsidethenavbararevisible—becauseofthenavbar-expand-mdclass.Thisnavbarisusinganavbar-togglerbuttonwhichgovernstheresponsivenessofthenavbar.Bydefault,thisbuttonishiddenandthebodyofthenavbarisvisible.Ifthescreenissmallenough,thenavbar-togglerisswitchedsoit'svisible,thebodyofthenavbarisinvisible,andwhenclickingonthenow-visiblenavbar-toggler,amenudropsdowncontainingthebodyofthenavbar:

Wechosethefeathericonshomeiconbecauseitsaysgohome.It'sintendedthatthemiddleportionofthenavbarwillcontainabreadcrumbaswenavigatearoundtheNotesapplication.

TheADDNotebuttonisgluedtotheright-hand-sidewithalittleFlexboxmagic.ThecontainerisaFlexbox,meaningwecanusetheBootstrapclassestocontrolthespaceconsumedbyeachitem.Thebreadcrumbareaisemptyinthiscase,butthe<div>thatwouldcontainitisthereanddeclaredwithclass="col",meaningthatittakesupacolumnunit.TheADDNotebuttonis,ontheotherhand,declaredwithclass="col-auto",meaningittakesuponlytheroomrequiredforitself.Itistheemptybreadcrumbareathatwillexpandtofillthespace,whiletheADDNotebuttonfillsonlyitsownspace,andisthereforepushedovertotheside.

Becauseit'sthesameapplication,thefunctionalityallworks;we'resimplyworkingonthepresentation.We'veaddedafewnotesbutthepresentationofthelistonthefrontpageleavesalottobedesired.Thesmallsizeofthetitleisnotverytouch-friendly,sinceitdoesn'tpresentalargetargetarea

Page 337: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

forafingertip.Andcanyouexplainwhythenotekeyvaluehastobedisplayedonthehomepage?

Page 338: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ImprovingtheNoteslistonthefrontpageThecurrenthomepagehasasimpletextlistthat'snotterriblytouch-friendlyandshowingthekeyatthefrontofthelinemightbeinexplicabletotheuser.Let'sfixthis.

Editviews/index.hbsandmakethischange:

<divclass="container-fluid">

<divclass="row">

<divclass="col-12btn-group-vertical"role="group">

{{#eachnotelist}}

<aclass="btnbtn-lgbtn-blockbtn-outline-dark"

href="notesview?key={{key}}">{{title}}</a>

{{/each}}

</div>

</div>

</div>

Thefirstchangeistoswitchawayfromusingalistandtouseaverticalbuttongroup.Bymakingthetextlinkslookandbehavelikebuttons,we'reimprovingtheuserinterface,especiallyitstouchfriendliness.Wechosethebtn-outline-darkbuttonstylebecauseitlooksgoodintheuserinterface.Weuselargebuttons(btn-lg)thatfillthewidthofthecontainer(btn-block).

Weeliminatedshowingthenotekeytotheuser.Thisinformationdoesn'taddanythingtotheuserexperience:

Page 339: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Thisisbeginningtotakeshape,withadecent-lookinghomepagethathandlesresizingverynicelyandistouchfriendly.

There'sstillsomethingmoretodowiththis,sincetheheaderareaistakingupafairamountofspace.Weshouldalwaysfeelfreetorethinkaplanaswelookatintermediateresults.Earlier,wecreatedonedesignfortheheaderarea,butonreflectionthatdesignlookstobetoolarge.Theintentionhadbeentoinsertabreadcrumbjusttotherightofthehomeicon,andtoleavethe<h1>titleatthetopoftheheaderarea.Butthisistakingupverticalspaceandwecantightenuptheheaderandpossiblyimprovetheappearance.

Editpartials/header.hbsandreplaceitwiththefollowing:

<headerclass="page-header">

<navclass="navbarnavbar-expand-mdnavbar-darkbg-dark">

<aclass="navbar-brand"href='/'><idata-feather="home"></i></a>

<buttonclass="navbar-toggler"type="button"

data-toggle="collapse"data-target="#navbarSupportedContent"

aria-controls="navbarSupportedContent"

aria-expanded="false"

aria-label="Togglenavigation">

<spanclass="navbar-toggler-icon"></span>

</button>

<divclass="collapsenavbar-collapse"id="navbarSupportedContent">

<spanclass="navbar-textcol">{{title}}</span>

<aclass="nav-itemnav-linkbtnbtn-lightcol-auto"href='notesadd'>ADD

Note</a>

</div>

Page 340: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

</nav>

</header>

Thisremovesthe<h1>tagatthetopoftheheaderarea,immediatelytighteningthepresentation.

Withinthenavbar-collapsearea,we'vereplacedwhathadbeenintendedasthebreadcrumb,withasimplenavbar-textcomponent.TokeeptheADDNotebuttongluedtotheright,we'remaintainingtheclass="col"andclass="col-auto"settings:

Whichheaderareadesignisbetter?That'sagoodquestion.Sincebeautyisintheeyeofthebeholder,bothdesignsareprobablyequallygood.Whatwehavedemonstratedistheeasewithwhichwecanupdatethedesignbyeditingthetemplatefiles.

Page 341: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

CleaninguptheNoteviewingexperienceViewingaNoteisn'tbad,buttheuserexperiencecanbeimproved.Theuserdoesnotneedtoseethenotekey,forexample.Additionally,Bootstraphasnicer-lookingbuttonswecanuse.

Inviews/noteview.hbs,makethesechanges:

<divclass="container-fluid">

<divclass="row"><divclass="col-xs-12">

{{#ifnote}}<h3>{{note.title}}</h3>{{/if}}

{{#ifnote}}<p>{{note.body}}</p>{{/if}}

<p>Key:{{notekey}}</p>

</div></div>

{{#ifnotekey}}

<divclass="row"><divclass="col-xs-12">

<divclass="btn-group">

<aclass="btnbtn-outline-dark"

href="notesdestroy?key={{notekey}}"

role="button">Delete</a>

<aclass="btnbtn-outline-dark"

href="notesedit?key={{notekey}}"

role="button">Edit</a>

</div>

</div></div>

{{/if}}

</div>

Wehavedeclaredtworows,onefortheNote,andanotherforbuttonstoactontheNote.Botharedeclaredtoconsumeall12columns,andthereforetakeupthefullavailablewidth.Thebuttonsareagaincontainedwithinabuttongroup:

Page 342: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Dowereallyneedtoshowthenotekeytotheuser?We'llleaveitthere,butthat'sanopenquestionfortheuserexperienceteam.Otherwise,we'veimprovedthenote-readingexperience.

Page 343: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Cleaninguptheadd/editnoteformThenextmajorglaringproblemistheformforaddingandeditingnotes.Aswesaidearlier,it'seasytogetthetextinputareatooverflowasmallscreen.Ontheotherhand,Bootstraphasextensivesupportformakingnice-lookingformsthatworkwellonmobiledevices.

Changetheforminviews/noteedit.hbstothis:

<formmethod='POST'action='notessave'>

<divclass="container-fluid">

{{#ifdocreate}}

<inputtype='hidden'name='docreate'value="create">

{{else}}

<inputtype='hidden'name='docreate'value="update">

{{/if}}

<divclass="form-grouprowalign-items-center">

<labelfor="notekey"class="col-1col-form-label">Key</label>

{{#ifdocreate}}

<divclass="col">

<inputtype='text'class="form-control"

placeholder="notekey"name='notekey'value=''/>

</div>

{{else}}

{{#ifnote}}

<spanclass="input-group-text">{{notekey}}</span>

{{/if}}

<inputtype='hidden'name='notekey'

value='{{#ifnote}}{{notekey}}{{/if}}'/>

{{/if}}

</div>

<divclass="form-grouprow">

<labelfor="title"class="col-1col-form-label">Title</label>

<divclass="col">

<inputtype="text"class="form-control"

id='title'name='title'placeholder="notetitle"

value='{{#ifnote}}{{note.title}}{{/if}}'>

Page 344: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

</div>

</div>

<divclass="form-grouprow">

<textareaclass="form-control"name='body'

rows="5">{{#ifnote}}{{note.body}}{{/if}}</textarea>

</div>

<buttontype="submit"class="btnbtn-default">Submit</button>

</div>

</form>

There'salotgoingonhere.Whatwe'vedoneisreorganizetheformsoBootstrapcandotherightthingswithit.Thefirstthingtonoteisthatwehaveseveralinstancesofthis:

<divclass="form-grouprow">..</div>

Thesearecontainedwithinacontainer-fluid,meaningthatwe'vesetupthreerowsintheform.

Bootstrapusesform-groupelementstoaddstructuretoforms,andtoencourageproperuseof<label>elements,alongwithotherformelements.It'sgoodpracticetousea<label>withevery<input>toimproveassistivebehaviorinthebrowser,ratherthanifyousimplyleftsomedanglingtext.

Everyformelementhasclass="form-control".Bootstrapusesthistoidentifythecontrolssoitcanaddstylingandbehavior.

Bydefault,Bootstrapformatsform-groupelementssothelabelappearsonanotherlinefromtheinputcontrol.Notethatwe'veaddedclass="col-1"tothelabelsandclass="col"tothe<div>wrappingtheinput.Thisdeclarestwocolumns,thefirstconsumingonecolumnunitandtheotherconsumingtheremainder.

Theplaceholder='key'attributeputssampletextinanotherwiseemptytextinputelement.Itdisappearsassoonastheusertypessomethingandisanexcellentwaytoprompttheuserwithwhat'sexpected.

Finally,wechangedtheSubmitbuttontobeaBootstrapbutton.These

Page 345: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

looknice,andBootstrapmakessurethattheyworkgreat:

TheresultlooksgoodandworkswellontheiPhone.Itautomaticallysizesitselftowhateverscreenit'son.Everythingbehavesnicely.Inthisscreenshot,we'veresizedthewindowsmallenoughtocausethenavbartocollapse.Clickingontheso-calledhamburgericonontheright(thethreehorizontallines)causesthenavbarcontentstopopupasamenu.

Page 346: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Cleaningupthedelete-notewindowThewindowusedtoverifythedesiretodeleteaNotedoesn'tlookbad,butitcanbeimproved.

Editviews/notedestroy.hbstocontainthefollowing:

<formmethod='POST'action='notesdestroy/confirm'>

<divclass="container-fluid">

<inputtype='hidden'name='notekey'value='{{#ifnote}}{{notekey}}{{/if}}'>

<pclass="form-text">Delete{{note.title}}?</p>

<divclass="btn-group">

<buttontype="submit"value='DELETE'

class="btnbtn-outline-dark">DELETE</button>

<aclass="btnbtn-outline-dark"

href="notesview?key={{#ifnote}}{{notekey}}{{/if}}"

role="button">

Cancel</a>

</div>

</div>

</form>

We'vereworkedeverythingtouseBootstrapformgoodness.ThequestionaboutdeletingtheNoteiswrappedwithclass="form-text"sothatBootstrapcandisplaythisproperly.

Thebuttonsarewrappedwithclass="btn-group"asbefore.Thebuttonshaveexactlythesamestylingasonotherscreens,givingaconsistentlookacrosstheapplication:

Page 347: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ThereisanissuethatthetitletextinthenavbardoesnotusethewordDelete.Inroutes/notes.js,wecanmakethischange:

//AsktoDeletenote(destroy)

router.get('/destroy',async(req,res,next)=>{

varnote=awaitnotes.read(req.query.key);

res.render('notedestroy',{

title:note?`Delete${note.title}`:"",

notekey:req.query.key,note:note

});

});

Whatwe'vedoneischangedthetitleparameterpassedtothetemplate.We'ddonethisinthenoteseditroutehandlerandseeminglymisseddoingsointhishandler.

Page 348: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

BuildingacustomizedBootstrapOnereasontouseBootstrapisthatyoucaneasilybuildacustomizedversion.StylesheetsarebuiltusingSASS,whichisoneoftheCSSpreprocessorstosimplifyCSSdevelopment.InBootstrap'scode,onefile(scss/_variables.scss)containsvariablesusedthroughouttherestofBootstrap's.scssfiles.ChangeonevariableanditcanautomaticallyaffecttherestofBootstrap.

Earlier,weoverrodeacoupleofBootstrapbehaviorswithourcustomCSSfile,public/stylesheets/style.css.Thisisaneasywaytochangeacoupleofspecificthings,butitdoesn'tworkforlarge-scalechangestoBootstrap.SeriousBootstrapcustomizationrequiresgeneratingacustomizedBootstrapbuild.

TheofficialdocumentationontheBootstrapwebsite(http://getbootstrap.com/docs/4.1/getting-started/build-tools/)isusefulforreferenceonthebuildprocess.

Ifyou'vefollowedthedirectionsgivenearlier,youhaveadirectory,chap06/notes,containingtheNotesapplicationsourcecode.Createadirectorynamedchap06notestheme,withinwhichwe'llsetupacustomBootstrapbuildprocess.

AsstudentsoftheTwelveFactorApplicationmodel,we'llbeusingapackage.jsoninthatdirectorytoautomatethebuildprocess.Thereisn'tanyNode.jscodeinvolved;npmisalsoaconvenienttooltoautomatethesoftwarebuildprocesses.

Tostart,downloadtheBootstrapsourcetreefromhttps://github.com/twbs/bootstrap.WhiletheBootstrapnpmpackageincludesSASSsourcefiles,it

Page 349: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

isn'tsufficienttobuildBootstrap,andthereforeonemustdownloadthesourcetree.WhatwedoisnavigatetotheGitHubrepository,clickontheReleasestab,andselecttheURLforthemostrecentrelease.

Withtheme/package.jsoncontainingthisscriptssection:

{

"scripts":{

"download":"wget-O-

https://github.com/twbs/bootstrap/archive/v4.1.0.tar.gz|tarxvfz-",

"postdownload":"cdbootstrap-4.1.0&&npminstall"

}

}

Typethiscommand:

$npmrundownload

Thisdownloadsthetar-gzip(tarball)archivefromtheBootstraprepositoryandimmediatelyunpacksit.IfyouareonWindows,itwillbeeasiesttorunthatscriptinWindowsSubsystemforLinuxtoexecutethesecommands.Afterdownloadingandunpackingthearchive,thepostdownloadsteprunsnpminstallinthedirectory.TheBootstrapteamusestheirpackage.json,notonlytotrackallthedependenciesrequiredtobuildBootstrap,buttodrivethebuildprocess.

ThenpminstallforBootstrapwilltakealongtime,sobepatient.

ThismuchonlyinstallsthetoolsnecessarytobuildBootstrap.BuildingtheBootstrapdocumentationrequiresinstallingadditionalRuby-basedtools(Jekyllandsomeplugins).

TobuildBootstrap,let'saddthefollowinglinestothescriptssectioninourtheme/package.json:

"scripts":{

...

"build":"cdbootstrap-4.1.0&&npmrundist",

Page 350: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

"watch":"cdbootstrap-4.1.0&&npmrunwatch"

...

}

Obviouslyyou'llneedtoadjustthedirectorynameastheBootstrapprojectissuesnewreleases.IntheBootstrapsourcetree,runningnpmrundist

buildsBootstrap,whilenpmrunwatchsetsupanautomatedprocesstoscan

forchangedfilesandrebuildsBootstrapuponchanginganyfile.Byaddingtheselinestoourtheme/package.json,wecanstartthisintheterminal

anditautomaticallyrerunsthebuildasneeded.

Nowrunabuildwiththiscommand:

$npmrunbuild

Thebuiltfileslandinthetheme/bootstrap-4.1.0/distdirectory.Thecontentofthatdirectorywillmatchthecontentsofthecorrespondingnpmpackage.

Incaseithasn'tbeenobviousallalong—thereareBootstrapversionnumbersembeddedintheseURLsandfileordirectorynames.AsnewBootstrapreleasesareissued,youmustadjustthepathnamestomatchthecurrentversionnumber.

Beforeproceeding,let'stakealookaroundtheBootstrapsourcetree.ThescssdirectorycontainstheSASSsourcethatwillbecompiledintotheBootstrapCSSfiles.TogenerateacustomizedBootstrapbuildwillrequireafewmodificationsinthatdirectory.

Thebootstrap-4.1.0/scss/bootstrap.scssfilecontains@importdirectivestopullinallBootstrapcomponents.Thefilebootstrap-4.1.0/scss/_variables.scsscontainsdefinitionsusedintheremainderoftheBootstrapSASSsource.Editing,oroverriding,thesevalueswillchangethelookofwebsitesusingtheresultingBootstrapbuild.

Forexample,thesedefinitionsdeterminethemaincolorvalues:

Page 351: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

$white:#fff!default;

$gray-100:#f8f9fa!default;

...

$gray-800:#343a40!default;

...

$blue:#007bff!default;

...

$red:#dc3545!default;

$orange:#fd7e14!default;

$yellow:#ffc107!default;

$green:#28a745!default;

...

$primary:$blue!default;

$secondary:$gray-600!default;

$success:$green!default;

$info:$cyan!default;

$warning:$yellow!default;

$danger:$red!default;

$light:$gray-100!default;

$dark:$gray-800!default;

ThesearesimilartoCSSstatements.The!defaultattributedesignatesthesevaluesasthedefault.Any!defaultvaluescanbeoverriddenwithoutediting_values.scss.

Createafile,theme/_custom.scss,containingthefollowing:

$white:#fff!default;

$gray-900:#212529!default;

$body-bg:$gray-900!default;

$body-color:$white!default;

Thisreversesthevaluesforthe$body-bgand$body-colorsettingsin_variables.scss.TheNotesappwillnowusewhitetextonadarkbackground,ratherthanthedefaultwhitebackgroundwithdarktext.Becausethesedeclarationsdonotuse!default,they'lloverridethevaluesin_variables.scss.

Thenmakeacopyofscss/bootstrap.scssinthethemedirectoryandmodifyit,likeso:

Page 352: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

@import"custom";

@import"functions";

@import"variables";

...

We'reimportingthe_custom.scssfilewejustcreated.Finally,addthislinetothescriptssectionoftheme/package.json:

"prebuild":"cp_custom.scssbootstrap.scssbootstrap-4.1.0/scss",

Withthatinplace,beforebuildingBootstrapthesetwofileswillbecopiedinplace.Next,rebuildBootstrap:

$npmrunbuild

>@prebuildUsersDavid/chap06notestheme

>cp_custom.scssbootstrap.scssbootstrap-4.1.0/scss

>@buildUsersDavid/chap06notestheme

>cdbootstrap-4.1.0&&npmrundist

...

Whilethat'sbuilding,let'smodifynotes/app.jstomountthebuilddirectory:

//app.use('assetsvendor/bootstrap',express.static(

//path.join(__dirname,'node_modules','bootstrap','dist')));

app.use('assetsvendor/bootstrap',express.static(

path.join(__dirname,'theme','bootstrap-4.1.0','dist')));

Whatwe'vedoneisswitchfromtheBootstrapinnode_modulestowhatwejustbuiltinthethemedirectory.TheBootstrapversionnumbershowsuphere,sothismustalsobeupdatedasnewBootstrapreleasesareadopted.

Thenreloadtheapplication,andyou'llseethefollowing:

Page 353: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Togetexactlythis,youmayneedtomakeachangeinthetemplates.TheButtonelementsweusedearlierhavethebtn-outline-darkclass,whichworkswellonalightbackground.Thebackgroundisnowdarkandthesebuttonsneedtouselightcoloring.

Tochangethebuttons,inviews/index.hbsmakethischange:

<aclass="btnbtn-lgbtn-blockbtn-outline-light"

href="notesview?key={{key}}">{{title}}</a>

Makeasimilarchangeinviews/noteview.hbs:

<aclass="btnbtn-outline-light"href="notesdestroy?key={{notekey}}"

role="button">Delete</a>

<aclass="btnbtn-outline-light"href="notesedit?key={{notekey}}"

role="button">Edit</a>

That'scool,wecannowreworktheBootstrapcolorschemeanywaywewant.Don'tshowthistoyouruserexperienceteam,becausethey'llthrowafit.Wedidthistoprovethepointthatwecanedit_custom.scssandchangetheBootstraptheme.

Page 354: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

PrebuiltcustomBootstrapthemesIfallthisistoocomplicatedforyou,severalwebsitesprovideprebuiltBootstrapthemes,orelsesimplifiedtoolstogenerateaBootstrapbuild.Togetourfeetwet,let'sdownloadathemefromBootswatch(https://bootswatch.com/).ThisisbothacollectionoffreeandopensourcethemesandabuildsystemforgeneratingcustomBootstrapthemes(https://github.com/thomaspark/bootswatch/).

Let'susetheMintythemefromBootswatchtoexploretheneededchanges.Youcandownloadthethemefromthewebsiteoraddthefollowingtothescriptssectionofpackage.json:

"dl-minty":"mkdir-pminty&&npmrundl-minty-css&&npmrundl-minty-min-

css",

"dl-minty-css":"wgethttps://bootswatch.com/4/minty/bootstrap.css-O

minty/bootstrap.css",

"dl-minty-min-css":"wgethttps://bootswatch.com/4/minty/bootstrap.min.css-O

minty/bootstrap.min.css"

ThiswilldownloadtheprebuiltCSSfilesforourchosentheme.Inpassing,noticethattheBootswatchwebsiteoffers_variables.scssand_bootswatch.scssfileswhichshouldbeusablewithaworkflowsimilartowhatweimplementedintheprevioussection.TheGitHubrepositorymatchingtheBootswatchwebsitehasacompletebuildprocedureforbuildingcustomthemes.

Performthedownload:

$npmrundl-minty

>[email protected]/chap06/notes

Page 355: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

>mkdir-pminty&&npmrundl-minty-css&&npmrundl-minty-min-css

>[email protected]/chap06/notes

>wgethttps://bootswatch.com/4/minty/bootstrap.css-Ominty/bootstrap.css

>[email protected]/chap06/notes

>wgethttps://bootswatch.com/4/minty/bootstrap.min.css-O

minty/bootstrap.min.css

Inapp.jswewillneedtochangetheBootstrapmountstoseparatelymounttheJavaScriptandCSSfiles.Usethefollowing:

//app.use('assetsvendor/bootstrap',express.static(

//path.join(__dirname,'node_modules','bootstrap','dist')));

//app.use('assetsvendor/bootstrap',express.static(

//path.join(__dirname,'theme','bootstrap-4.0.0','dist')));

app.use('assetsvendor/bootstrap/js',express.static(

path.join(__dirname,'node_modules','bootstrap','dist','js')));

app.use('assetsvendor/bootstrap/css',express.static(

path.join(__dirname,'minty')));

Insteadofonemountforvendorbootstrap,wenowhavetwomountsforeachofthesubdirectories.Simplymakethevendorbootstrap/cssmountpointtoadirectorycontainingtheCSSfilesyoudownloadedfromthethemeprovider.

BecauseMintyisalight-coloredtheme,thebuttonsneedtousethedarkstyle.Wehadearlierchangedthebuttonstousealightstylebecauseofthedarkbackground.Wemustnowswitchfrombtn-outline-lightbacktobtn-outline-dark.Inpartials/header.hbs,thecolorschemerequiresachangetothenavbarcontent:

<divclass="collapsenavbar-collapse"id="navbarSupportedContent">

<spanclass="navbar-texttext-darkcol">{{title}}</span>

<aclass="nav-itemnav-linkbtnbtn-darkcol-auto"href='notesadd'>ADD

Note</a>

</div>

Weselectedtext-darkandbtn-darkclassestoprovidesomecontrastagainstthebackground.

Page 356: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Re-runtheapplicationandyou'llseesomethinglikethis:

Page 357: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SummaryThepossibilitiesforusingBootstrapareendless.Whilewecoveredalotofmaterial,weonlytouchedthesurface,andwecouldhavedonemuchmoretotheNotesapplication.

YoulearnedwhattheTwitterBootstrapframeworkcando.Bootstrap'sgoalistomakemobile-responsivedevelopmenteasy.WeusedBootstraptomakegreatimprovementstothewaytheNotesapplooksandfeels.WecustomizedBootstrap,dippingourtoesintogeneratingacustomtheme.

Now,wewanttogetbacktowritingNode.jscode.WeleftoffChapter5,YourFirstExpressApplication,withtheproblemofpersistencesothattheNotesapplicationcanbestoppedandrestartedwithoutlosingournotes.InChapter7,DataStorageandRetrieval,we'lldiveintousingdatabasestostoreourdata.

TogiveourselvessomeexperiencewiththeES6Moduleformat,we'llrewritetheNotesapplicationaccordingly.

Page 358: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

DataStorageandRetrievalIntheprevioustwochapters,webuiltasmallandsomewhatusefulapplicationforstoringnotes,andthenmadeitworkonmobiledevices.Whiletheapplicationworksreasonablywell,itdoesn'tstorethosenotesanywhereonalong-termbasis,meaningthenotesarelostwhenyoustoptheserverand,ifyourunmultipleinstancesofNotes,eachinstancehasitsownsetofnotes.Thetypicalnextstepistointroduceadatabasetier.

Inthischapter,wewilllookatdatabasesupportinNode.js,sotheuserseesthesamesetofnotesforanyNotesinstanceaccessed,andtoreliablystorenotesforlong-termretrieval.

We'llstartwiththeNotesapplicationcodeusedinthepreviouschapter.Westartedwithasimple,in-memorydatamodelusinganarraytostorethenotes,andthenmadeitmobilefriendly.Inthischapter,wewill:

Discoverloggingoperationalanddebugginginformation

BeginusingtheES6moduleformat

ImplementdatapersistenceforNotesobjectsusingseveraldatabaseengines

Let'sgetstarted!

Thefirststepistoduplicatethecodefromthepreviouschapter.Forinstance,ifyouwereworkinginchap06/notes,duplicatethattobechap07/notes.

Page 359: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

DatastorageandasynchronouscodeBydefinition,externaldatastoragesystemsrequireasynchronouscodeintheNode.jsarchitecture.Theaccesstimetoretrievedatafromdisk,fromanotherprocess,orfromadatabase,alwaystakessufficienttimetorequiredeferredexecution.

TheexistingNotesdatamodelisanin-memorydatastore.Intheory,in-memorydataaccessdoesnotrequireasynchronouscodeandtherefore,theexistingmodelmodulecouldhaveusedregularfunctionsratherthanasyncfunctions.

WeknewthatNotesmustmovetousingdatabases,andwouldrequireanasynchronousAPItoaccessNotesdata.Forthatreason,theexistingNotesmodelAPIusesasyncfunctionssothatinthischapter,wecanpersistNotedatatodatabases.

Page 360: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

LoggingBeforewegetintodatabases,wehavetoaddressoneoftheattributesofahigh-qualitysoftwaresystem:managingloggedinformation,includingnormalsystemactivity,systemerrors,anddebugginginformation.Logsgiveusaninsightintothebehaviorofthesystem.Howmuchtrafficisitgetting?Ifit'sawebsite,whichpagesarepeoplehittingthemost?Howmanyerrorsoccurandofwhatkind?Doattacksoccur?Aremalformedrequestsbeingsent?

Logmanagementisalsoanissue.Logrotationmeansregularlymovingthelogfileoutoftheway,tostartwithafreshlogfile.Youshouldprocessloggeddatatoproducereports.Ahighpriorityonscreeningforsecurityvulnerabilitiesisamust.

TheTwelveFactorapplicationmodelsuggestssimplysendinglogginginformationtotheconsole,andthensomeothersoftwaresystemcapturesthatoutputanddirectsittoaloggingservice.Followingtheiradvicecanreducesystemcomplexitybyhavingfewerthingsthatcanbreak.Inalaterchapter,we'llusePM2forthatpurpose.

Let'sfirstcompleteatourofinformationloggingasitstandsrightnowinNotes.

WhenweusedtheExpressGeneratortoinitiallycreatetheNotesapplication,itconfiguredanactivityloggingsystemusingmorgan:

constlogger=require('morgan');

..

app.use(logger('dev'));

ThisiswhatprintstherequestsontheTerminalwindow.Visithttps://github.com/expressjs/morganformoreinformation.

Page 361: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Internally,ExpressusestheDebugpackagefordebuggingtraces.YoucanturntheseonusingtheDEBUGenvironmentvariable.Weshouldtrytousethispackageinourapplicationcode.Formoreinformation,visithttps://www.npmjs.com/package/debug.

Finally,theapplicationmightgenerateuncaughtexceptions.TheuncaughtExceptionerrorneedstobecaptured,logged,anddealtwithappropriately.

Page 362: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

RequestloggingwithMorganTheMorganpackagehastwogeneralareasforconfiguration:

Logformat

Loglocation

Asitstands,Notesusesthedevformat,whichisdescribedasaconcisestatusoutputmeantfordevelopers.Thiscanbeusedtologwebrequestsasawaytomeasurewebsiteactivityandpopularity.TheApachelogformatalreadyhasalargeecosystemofreportingtoolsand,sureenough,Morgancanproducelogfilesinthisformat.

Tochangetheformat,simplychangethislineinapp.js:

app.use(logger(process.env.REQUEST_LOG_FORMAT||'dev'));

ThenrunNotesasfollows:

$REQUEST_LOG_FORMAT=commonnpmstart

>[email protected]/chap07/notes

>node./bin/www

::1--[12/Feb/2016:05:51:21+0000]"GETHTTP1.1"304-

::1--[12/Feb/2016:05:51:21+0000]"GET

vendorbootstrap/css/bootstrap.min.cssHTTP/1.1"304-

::1--[12/Feb/2016:05:51:21+0000]"GETstylesheetsstyle.cssHTTP/1.1"304

-

::1--[12/Feb/2016:05:51:21+0000]"GETvendorbootstrap/js/bootstrap.min.js

HTTP/1.1"304-

Toreverttothepreviousloggingoutput,simplydonotsetthisenvironmentvariable.Ifyou'velookedatApacheaccesslogs,thislogging

Page 363: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

formatwilllookfamiliar.The::1notationatthebeginningofthelineisIPV6notationforthelocalhost,whichyoumaybemorefamiliarwithas127.0.0.1.

Wecandeclarevictoryonrequestloggingandmoveontodebuggingmessages.However,let'slookatloggingthistoafiledirectly.Whileit'spossibletocapturestdoutthroughaseparateprocess,MorganisalreadyinstalledinNotesanditdoesprovidethecapabilitytodirectitsoutputtoafile.

TheMorgandocumentationsuggeststhis:

//createawritestream(inappendmode)

varaccessLogStream=fs.createWriteStream(__dirname+'access.log',{flags:

'a'})

/setupthelogger

app.use(morgan('combined',{stream:accessLogStream}));

Butthishasaproblem;it'simpossibletoperformlogrotationwithoutkillingandrestartingtheserver.Instead,we'llusetheirrotating-file-streampackage.

First,installthepackage:

$npminstallrotating-file-stream--save

Thenweaddthiscodetoapp.js:

constfs=require('fs-extra');

...

constrfs=require('rotating-file-stream');

varlogStream;

//Logtoafileifrequested

if(process.env.REQUEST_LOG_FILE){

(async()=>{

letlogDirectory=path.dirname(process.env.REQUEST_LOG_FILE);

awaitfs.ensureDir(logDirectory);

logStream=rfs(process.env.REQUEST_LOG_FILE,{

Page 364: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

size:'10M',//rotateevery10MegaByteswritten

interval:'1d',//rotatedaily

compress:'gzip'//compressrotatedfiles

});

})().catch(err=>{console.error(err);});

}

..

app.use(logger(process.env.REQUEST_LOG_FORMAT||'dev',{

stream:logStream?logStream:process.stdout

}));

Here,we'reusinganenvironmentvariable,REQUEST_LOG_FILE,tocontrolwhethertosendthelogtostdoutortoafile.Thelogcangointoadirectory,andthecodewillautomaticallycreatethatdirectoryifitdoesn'texist.Byusingrotating-file-stream(https://www.npmjs.com/package/rotating-file-stream),we'reguaranteedtohavelogfilerotationwithnoextrasystemsrequired.

Thefs-extramoduleisbeingusedbecauseitaddsPromise-basedfunctionstothefsmodule(https://www.npmjs.com/package/fs-extra).Inthiscase,fs.ensureDirchecksifthenameddirectorystructureexistsand,ifnot,thedirectorypathiscreated.

Page 365: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

<strong>$DEBUG=express:*npmstart</strong>

constdebug=require('debug')('module-name');..

debug('somemessage');..

debug(`gotfile${fileName}`);

Page 366: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

CapturingstdoutandstderrImportantmessagescanbeprintedtoprocess.stdoutorprocess.stderr,whichcanbelostifyoudon'tcapturethatoutput.TheTwelveFactormodelsuggestsusingasystemfacilitytocapturetheseoutputstreams.WithNotes,we'llusePM2forthatpurpose,whichwe'llcoverinChapter10,DeployingNode.jsApplications.

Thelogbookmodule(https://github.com/jpillora/node-logbook)offerssomeusefulcapabilitiesintermofnotonlycapturingprocess.stdoutandprocess.stderr,butsendingthatoutputtousefulplaces.

Page 367: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

consterror=require('debug')('notes:error');

process.on('uncaughtException',function(err){

error("I'vecrashed!!!-"+(err.stack||err));});

..

if(app.get('env')==='development'){

app.use(function(err,req,res,next){

//util.log(err.message);res.status(err.status||500);error((err.status||500)+''+error.message);res.render('error',{

message:err.message,error:err});

});

}

..

app.use(function(err,req,res,next){

//util.log(err.message);res.status(err.status||500);error((err.status||500)+''+error.message);res.render('error',{

message:err.message,error:{}

});

});

Thedebugpackagehasaconventionwe'refollowing.Foranapplicationwithseveralmodules,alldebuggerobjectsshouldusethenamingpatternapp-name:module-name.Inthiscase,weusednotes:errorthatwillbeusedforallerrormessages.Wecouldalsousenotes:memory-modelornotes:mysql-modelfordebuggingdifferentmodels.

Page 368: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Whileweweresettingupthehandlerforuncaughtexceptions,itisalsoagoodideatoadderrorloggingintotheerrorhandlers.

Page 369: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

UnhandledPromiserejectionsUsingPromiseandasyncfunctionsautomaticallychannelserrorsinausefuldirection.ErrorswillcauseaPromisetoflipintoarejectedstate,whichmusteventuallybehandledina.catchmethod.Sincewe'reallhuman,we'reboundtoforgettoensurethatallcodepathshandletheirrejectedPromise's.

Currently,Node.jsprintsthefollowingwarningifitdetectsanunhandledPromiserejection:(node:4796)UnhandledPromiseRejectionWarning:Unhandledpromiserejection

ThewarninggoesontosaythatthedefaulthandlerforunhandledPromiserejectionhasbeendeprecatedandthatsuchPromiserejectionswillcrashtheNodeprocessratherthanprintthismessage.Thebuilt-inprocessmoduledoesemitaneventinthiscase,soit'seasyenoughtoaddahandler:importutilfrom'util';...process.on('unhandledRejection',(reason,p)=>{error(`UnhandledRejectionat:${util.inspect(p)}reason:${reason}`);});

Attheminimum,wecanprintanerrormessagesuchasthefollowing:

notes:errorUnhandledRejectionat:Promise{

notes:error<rejected>TypeError:model(...).keylistisnotafunction

...fullstacktrace

}reason:TypeError:model(...).keylistisnotafunction+3s

Page 370: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

UsingtheES6moduleformatWewrotetheNotesapplicationusingCommonJSmodules,thetraditionalNode.jsmoduleformat.Whiletheapplicationcouldcontinueusingthatformat,theJavaScriptcommunityhaschosentoswitchtoES6modulesinbothbrowserandNode.jscode,andthereforeit'simportanttoswitchES6modulessowecanallgetonboardwithacommonmoduleformat.Let'srewritetheapplicationusingES6modules,andthenwriteES6modulesforanythingnewweadd.

Thechangesrequiredarelargetoreplacerequirestatementswithimportstatements,andrenamingfilesfromfoo.jstofoo.mjs.Let'sgetstarted.

Page 371: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Rewritingapp.jsasanES6moduleLet'sstartwithapp.js,changingitsnametoapp.mjs:

$mvapp.jsapp.mjs

Changetheblockofrequirestatementsatthetoptothefollowing:

importfsfrom'fs-extra';

importurlfrom'url';

importexpressfrom'express';

importhbsfrom'hbs';

importpathfrom'path';

importutilfrom'util';

importfaviconfrom'serve-favicon';

importloggerfrom'morgan';

importcookieParserfrom'cookie-parser';

importbodyParserfrom'body-parser';

importDBGfrom'debug';

constdebug=DBG('notes:debug');

consterror=DBG('notes:error');

import{routerasindex}from'./routes/index';

//constusers=require('./routes/users');

import{routerasnotes}from'./routes/notes';

//Workaroundforlackof__dirnameinES6modules

const__dirname=path.dirname(newURL(import.meta.url).pathname);

constapp=express();

importrfsfrom'rotating-file-stream';

Then,atthebottomofthescript,makethischange:

exportdefaultapp;

Page 372: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Let'stalkalittleabouttheworkaroundmentionedhere.TherewereseveralglobalvariablesautomaticallyinjectedbyNode.jsintoCommonJSmodules.ThosevariablesarenotsupportedbyES6modules.ThecriticalvariableforNotesis__dirname,whichisusedinapp.mjsinseveralplaces.ThecodechangeshownhereincludesaworkaroundbasedonabrandnewJavaScriptfeaturethatisavailablestartinginNode.js10.x,theimport.meta.urlvariable.

Theimport.metaobjectismeanttoinjectusefulinformationintoanES6module.Asthenameimplies,theimport.meta.urlvariablecontainstheURLdescribingwherethemodulewasloadedfrom.ForNode.js,atthistime,ES6modulescanonlybeloadedfromafile://URLonthelocalfilesystem.Thatmeans,ifweextractthepathnameofthatURL,wecaneasilycalculatethedirectorycontainingthemodule,asshownhere.

Whythissolution?Whynotuseapathnamebeginningwith./?Theansweristhata./filenameisevaluatedrelativetotheprocess'scurrentworkingdirectory.ThatdirectoryisusuallydifferentfromthedirectorycontainingtheNode.jsmodulebeingexecuted.ThereforeitismorethanconvenientthattheNode.jsteamhasaddedtheimport.meta.urlfeature.

Thepatternfollowedinmostcasesisthischange:

constmoduleName=require('moduleName');//inCommonJSmodules

importmoduleNamefrom'moduleName';//inES6modules

RememberthatNode.jsusesthesamemodulelookupalgorithminbothES6andCommonJSmodules.ANode.jsrequirestatementissynchronous,meaningthatbythetimerequirefinishes,ithasexecutedthemoduleandisreturningitsmodule.exports.Bycontrast,anES6moduleisasynchronous,meaningthemodulemaynothavefinishedloading,andyoucanimportjusttherequiredbitsofthemodule.

MostofthemoduleimportsshownhereareforregularNode.jsmodulesinstalledinthenode_modulesdirectory,mostofwhichareCommonJSmodules.TheruleforusingimportwithaCommonJSmoduleisthatthemodule.exportsobjectistreatedasifitwerethedefaultexport.The

Page 373: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

importstatementshownearliernamethedefaultexport(orthemodule.exportsobject)asshownintheimportstatement.ForaCommonJSmoduleimportedthisway,youthenuseitasyouwouldinaCommonJScontext,moduleName.functionName().

Theusageofthedebugmoduleiseffectivelythesame,butiscodeddifferently.IntheCommonJScontext,we'retoldtousethatmoduleasfollows:

constdebug=require('debug')('notes:debug');

consterror=require('debug')('notes:error');

Inotherwords,themodule.exportsofthismoduleisafunction,whichweimmediatelyinvoke.Thereisn'tasyntaxforES6modulestousethedebugmoduleinthatfashion.Therefore,wehadtobreakitapartasshown,andexplicitlycallthatfunction.

Thefinalpointofdiscussionistheimportofthetworoutermodules.Itwasfirstattemptedtohavethosemodulesexporttherouterasthedefaultvalue,butExpressthrewanerrorinthatcase.Instead,we'llrewritethesemodulestoexportrouterasanamedexportandthenusethatnamedexportasshownhere.

Page 374: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Rewritingbin/wwwasanES6moduleRememberthatbin/wwwisascriptusedtolaunchtheapplication.ItiswrittenasaCommonJSscript,butbecauseapp.mjsisnowanES6module,bin/wwwalsomustberewrittenasanES6module.ACommonJSmodulecannot,atthetimeofwriting,importanES6module.

Changethefilename:

$mvbin/wwwbin/www.mjs

Then,atthetop,changetherequirestatementstoimportstatements:

importappfrom'../app.mjs';

importDBGfrom'debug';

constdebug=DBG('notes:server-debug');

consterror=DBG('notes:server-error');

importhttpfrom'http';

We'vealreadydiscussedeverythinghereexceptthatapp.mjsexportsitsappobjectasthedefaultexport.Therefore,weuseitasshownhere.

Page 375: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

RewritingmodelscodeasES6modulesThemodelsdirectorycontainstwomodules:Note.jsdefinestheNoteclass,andnotes-memory.jscontainsanin-memorydatamodel.BothareeasytoconverttoES6modules.

Changethefilenames:

$cdmodels

$mvNote.jsNote.mjs

$mvnotes-memory.jsnotes-memory.mjs

InNote.mjs,simplymakethefollowingchange:

exportdefaultclassNote{

...

}

ThismakestheNoteclassthedefaultexport.

Then,innotes-memory.mjs,makethefollowingchange:

importNotefrom'./Note';

varnotes=[];

asyncfunctioncrupdate(key,title,body){

notes[key]=newNote(key,title,body);

returnnotes[key];

}

exportfunctioncreate(key,title,body){returncrupdate(key,title,body);

}

exportfunctionupdate(key,title,body){returncrupdate(key,title,body);

Page 376: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

}

exportasyncfunctionread(key){

if(notes[key])returnnotes[key];

elsethrownewError(`Note${key}doesnotexist`);

}

exportasyncfunctiondestroy(key){

if(notes[key]){

deletenotes[key];

}elsethrownewError(`Note${key}doesnotexist`);

}

exportasyncfunctionkeylist(){returnObject.keys(notes);}

exportasyncfunctioncount(){returnnotes.length;}

exportasyncfunctionclose(){}

Thisisastraightforwardtransliterationofassigningfunctionstomodule.exportstousingnamedexports.

BydefiningtheNoteclassasthedefaultexportoftheNote.mjsmodule,itimportsnicelyintoanymoduleusingthatclass.

Page 377: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

RewritingroutermodulesasES6modulesTheroutesdirectorycontainstworoutermodules.Asitstands,eachroutermodulecreatesarouterobject,addsroutefunctionstothatobject,andthenassignsittothemodule.exportsfield.Thatsuggestsweshouldexporttherouterasthedefaultexport,butaswesaidearlier,thatdidn'tworkoutright.Instead,we'llexportrouterasanamedexport.

Changethefilenames:

$cdroutes

$mvindex.jsindex.mjs

$mvnotes.jsnotes.mjs

Then,atthetopofeach,changetherequirestatementblocktothefollowing:

importutilfrom'util';

importexpressfrom'express';

import*asnotesfrom'../models/notes-memory';

exportconstrouter=express.Router();

Itwillbethesameinbothfiles.Then,atthebottomofeachfile,deletethelineassigningroutertomodule.exports.

Let'sturntoapp.mjsandchangehowtheroutermodulesareimported.

Becauserouterisanamedexport,bydefaultyou'dimporttherouterobject,inapp.mjs,asfollows:

Page 378: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

import{router}from'./routes/index';

Butthenwe'dhaveaconflictsincebothmodulesdefinearouterobject.Instead,wechangedthenameofthisobjectusinganasclause:

import{routerasindex}from'./routes/index';

import{routerasnotes}from'./routes/notes';

Therouterobjectfromeachmoduleishencegivenasuitablename.

Page 379: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

StoringnotesinthefilesystemThefilesystemisanoftenoverlookeddatabaseengine.Whilefilesystemsdon'thavethesortofqueryfeaturessupportedbydatabaseengines,theyareareliableplacetostorefiles.Thenotesschemaissimpleenoughthatthefilesystemcaneasilyserveasitsdatastoragelayer.

Let'sstartbyaddingafunctiontoNote.mjs:

exportdefaultclassNote{

...

getJSON(){

returnJSON.stringify({

key:this.key,title:this.title,body:this.body

});

}

staticfromJSON(json){

vardata=JSON.parse(json);

varnote=newNote(data.key,data.title,data.body);

returnnote;

}

}

JSONisagetter,whichmeansitgetsthevalueoftheobject.Inthiscase,thenote.JSONattribute/getter,noparentheses,willsimplygiveustheJSONrepresentationoftheNote.We'llusethislaterforwritingtoJSONfiles.

fromJSONisastaticfunction,orfactorymethod,toaidinconstructingNoteobjectsifwehaveaJSONstring.ThedifferenceisthatJSONisassociatedwithaninstanceoftheNoteclass,whilefromJSONisassociatedwiththeclassitself.Thetwocanbeusedasfollows:

constnote=newNote("key","title","body");

constjson=note.JSON;//producesJSONtext

constnewnote=Note.fromJSON(json);//producesnewNoteinstance

Page 380: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Now,let'screateanewmodule,models/notes-fs.mjs,toholdthefilesystemmodel:

importfsfrom'fs-extra';

importpathfrom'path';

importutilfrom'util';

importNotefrom'./Note';

importDBGfrom'debug';

constdebug=DBG('notes:notes-fs');

consterror=DBG('notes:error-fs');

asyncfunctionnotesDir(){

constdir=process.env.NOTES_FS_DIR||"notes-fs-data";

awaitfs.ensureDir(dir);

returndir;

}

functionfilePath(notesdir,key){returnpath.join(notesdir,`${key}.json`);

}

asyncfunctionreadJSON(notesdir,key){

constreadFrom=filePath(notesdir,key);

vardata=awaitfs.readFile(readFrom,'utf8');

returnNote.fromJSON(data);

}

ThenotesDirfunctionwillbeusedthroughoutnotes-fstoensurethatthedirectoryexists.Tomakethissimple,we'reusingthefs-extramodulebecauseitaddsPromise-basedfunctionstothefsmodule(https://www.npmjs.com/package/fs-extra).Inthiscase,fs.ensureDirverifieswhetherthenameddirectorystructureexists,and,ifnot,thedirectorypathiscreated.

Theenvironmentvariable,NOTES_FS_DIR,configuresadirectorywithinwhichtostorenotes.We'llhaveonefilepernoteandstorethenoteasJSON.Ifnoenvironmentvariableisspecified,we'llfallbackonusingnotes-fs-dataasthedirectoryname.

Becausewe'readdinganotherdependency:

$npminstallfs-extra--save

Page 381: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Thefilenameforeachdatafileisthekeywith.jsonappended.Thatgivesonelimitationthatfilenamescannotcontainthe/character,sowetestforthatusingthefollowingcode:

asyncfunctioncrupdate(key,title,body){

varnotesdir=awaitnotesDir();

if(key.indexOf('/')>=0)

thrownewError(`key${key}cannotcontain'/'`);

varnote=newNote(key,title,body);

constwriteTo=filePath(notesdir,key);

constwriteJSON=note.JSON;

awaitfs.writeFile(writeTo,writeJSON,'utf8');

returnnote;

}

exportfunctioncreate(key,title,body){returncrupdate(key,title,body);

}

exportfunctionupdate(key,title,body){returncrupdate(key,title,body);

}

Asisthecasewiththenotes-memorymodule,thecreateandupdatefunctionsusetheexactsamecode.ThenotesDirfunctionisusedtoensurethatthedirectoryexists,thenwecreateaNoteobject,andthenwritethedatatoafile.

Noticehowthecodeisverystraightforwardbecauseoftheasyncfunction.Wearen'tcheckingforerrorsbecausethey'llbeautomaticallycaughtbytheasyncfunctionandbubbleouttoourcaller:

exportasyncfunctionread(key){

varnotesdir=awaitnotesDir();

varthenote=awaitreadJSON(notesdir,key);

returnthenote;

}

UsingreadJSON,readthefilefromthedisk.ItalreadygeneratestheNoteobject,soallwehavetodoisreturnthatobject:

exportasyncfunctiondestroy(key){

Page 382: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

varnotesdir=awaitnotesDir();

awaitfs.unlink(filePath(notesdir,key));

}

Thefs.unlinkfunctiondeletesourfile.Becausethismoduleusesthefilesystem,deletingthefileisallthat'snecessarytodeletethenoteobject:

exportasyncfunctionkeylist(){

varnotesdir=awaitnotesDir();

varfilez=awaitfs.readdir(notesdir);

if(!filez||typeoffilez==='undefined')filez=[];

varthenotes=filez.map(asyncfname=>{

varkey=path.basename(fname,'.json');

varthenote=awaitreadJSON(notesdir,key);

returnthenote.key;

});

returnPromise.all(thenotes);

}

ThecontractforkeylististoreturnaPromisethatwillresolvetoanarrayofkeysforexistingnoteobjects.Sincethey'restoredasindividualfilesinthenotesdir,wehavetoreadeveryfileinthatdirectorytoretrieveitskey.

Array.mapconstructsanewarrayfromanexistingarray,namelythearrayoffilenamesreturnedbyfs.readdir.Eachentryintheconstructedarrayistheasyncfunction,whichreadstheNote,returningthekey:

exportasyncfunctioncount(){

varnotesdir=awaitnotesDir();

varfilez=awaitfs.readdir(notesdir);

returnfilez.length;

}

exportasyncfunctionclose(){}

Countingthenumberofnotesissimplyamatterofcountingthenumberoffilesinnotesdir.

Page 383: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

DynamicimportofES6modulesBeforewestartmodifyingtherouterfunctions,wehavetoconsiderhowtoaccountformultiplemodels.Wecurrentlyhavetwomodulesforourdatamodel,notes-memoryandnotes-fs,andwe'llbeimplementingseveralmorebytheendofthischapter.Wewillneedasimplemethodtoselectbetweenthemodelbeingused.

Thereareseveralpossiblewaystodothis.Forexample,inaCommonJSmodule,it'spossibletodothefollowing:

constpath=require('path');

constnotes=require(process.env.NOTES_MODEL

?path.join('..',process.env.NOTES_MODEL)

:'../models/notes-memory');

Thisletsussetanenvironmentvariable,NOTES_MODEL,toselectthemoduletouseforthedatamodel.

Thisapproachdoesnotworkwiththeregularimportstatement,becausethemodulenameinanimportstatementcannotbesuchanexpression.TheDynamicImportfeaturenowinNode.jsdoesofferamechanismsimilartothesnippetjustshown.

Dynamicimportisanimport()functionthatreturnsaPromisethatwillresolvetotheimportedmodule.Asafunction-returning-a-Promise,import()won'tbeusefulastop-levelcodeinthemodule.But,considerthis:

varNotesModule;

Page 384: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

asyncfunctionmodel(){

if(NotesModule)returnNotesModule;

NotesModule=awaitimport(`../models/notes-${process.env.NOTES_MODEL}`);

returnNotesModule;

}

exportasyncfunctioncreate(key,title,body){

return(awaitmodel()).create(key,title,body);

}

exportasyncfunctionupdate(key,title,body){

return(awaitmodel()).update(key,title,body);

}

exportasyncfunctionread(key){return(awaitmodel()).read(key);}

exportasyncfunctiondestroy(key){return(awaitmodel()).destroy(key);}

exportasyncfunctionkeylist(){return(awaitmodel()).keylist();}

exportasyncfunctioncount(){return(awaitmodel()).count();}

exportasyncfunctionclose(){return(awaitmodel()).close();}

Savethatmoduleinafile,models/notes.mjs.ThismoduleimplementsthesameAPIaswe'lluseforallNotesmodelmodules.Themodel()functionisthekeytodynamicallyselectinganotesmodelimplementationbasedonanenvironmentvariable.

ThisisanasyncfunctionandthereforeitsreturnvalueisaPromise.ThevalueofthatPromiseistheselectedmodule,asloadedbyimport().Becauseimport()returnsaPromise,weuseawaittoknowwhetheritloadedcorrectly.

EveryAPImethodfollowsthispattern:

exportasyncfunctionmethodName(args){

return(awaitmodel()).methodName(args);

}

Becausemodel()returnsaPromise,it'smostsuccincttouseanasyncfunctionanduseawaittoresolvethePromise.OncethePromiseisresolved,wesimplycallthemethodNamefunctionandgoaboutourbusiness.Otherwise,thoseAPImethodfunctionswouldbeasfollows:

exportfunctionmethodName(args){

returnmodel().then(notes=>{returnnotes.methodName(args);});

Page 385: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

}

Thetwoimplementationsareequivalent,andit'sclearwhichisthemoresuccinct.

WithallthisawaitingonPromise'sreturnedfromasyncfunctions,it'sworthdiscussingtheoverhead.Theworstcaseisonthefirstcalltomodel(),becausetheselectednotesmodelhasnotbeenloaded.Thefirsttimearound,thecallflowgoesasfollows:

TheAPImethodcallsmodel(),whichcallsimport(),thenawait'sthemoduletofinishloading

TheAPImethodawait'sthePromisereturnedfrommodel(),gettingthemoduleobject,anditthencallstheAPIfunction

Thecallerisalsousingawaittoreceivethefinalresult

Thefirsttimearound,thetimeisdominatedbywaitingonimport()tofinishloadingthemodule.Onsubsequentcalls,themodulehasalreadybeenloadedandthefirststepistosimplyformaresolvedPromisecontainingthemodule.TheAPImethodcanthenquicklygetonwithdelegatingtotheactualAPImethod.

Tousethis,inroutes/index.mjs,andinroutes/notes.mjs,wemakethischange:

importutilfrom'util';

importexpressfrom'express';

import*asnotesfrom'../models/notes';

exportconstrouter=express.Router();

Page 386: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

RunningtheNotesapplicationwithfilesystemstorageInpackage.json,addthistothescriptssection:

"start-fs":"DEBUG=notes:*NOTES_MODEL=fsnode--experimental-modules

./bin/www.mjs",

Whenyouputtheseentriesinpackage.json,makesurethatyouusecorrectJSONsyntax.Inparticular,ifyouleaveacommaattheendofthescriptssection,itwillfailtoparseandnpmwillthrowupanerrormessage.

Withthiscodeinplace,wecannowruntheNotesapplicationasfollows:

$DEBUG=notes:*npmrunstart-fs

>[email protected]/chap07/notes

>NOTES_MODEL=models/notes-fsnode--experimental-modules./bin/www.mjs

notes:serverListeningonport3000+0ms

notes:fs-modelkeylistdir=notes-fs-datafiles=[]+4s

Thenwecanusetheapplicationathttp://localhost:3000asbefore.BecausewedidnotchangeanytemplateorCSSfiles,theapplicationwilllookexactlyasyouleftitattheendofChapter6,ImplementingtheMobile-FirstParadigm.

Becausedebuggingisturnedonfornotes:*,we'llseealogofwhatevertheNotesapplicationisdoing.It'seasytoturnthisoffsimplybynotsettingtheDEBUGvariable.

YoucannowkillandrestarttheNotesapplicationandseetheexactsamenotes.Youcanalsoeditthenotesatthecommandlineusingregulartexteditorssuchasvi.Youcannowstartmultipleserversondifferentportsandseeexactlythesamenotes:

Page 387: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

"server1":"NOTES_MODEL=fsPORT=3001node--experimental-

modules./bin/www.mjs",

"server2":"NOTES_MODEL=fsPORT=3002node--experimental-

modules./bin/www.mjs",

Thenyoustartserver1andserver2inseparatecommandwindowsaswedidinChapter5,YourFirstExpressApplication.Then,visitthetwoserversinseparatebrowserwindows,andyouwillseethatbothbrowserwindowsshowthesamenotes.

Thefinalcheckistocreateanotewherethekeyhasa/character.Rememberthatthekeyisusedtogeneratethefilenamewherewestorethenote,andthereforethekeycannotcontaina/character.Withthebrowseropen,clickonADDNoteandenteranote,ensuringthatyouusea/characterinthekeyfield.OnclickingtheSubmitbutton,you'llseeanerrorsayingthatthisisn'tallowed.

Page 388: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

StoringnoteswiththeLevelUPdatastoreTogetstartedwithactualdatabases,let'slookatanextremelylightweight,small-footprintdatabaseengine:LevelUP.ThisisaNode.js-friendlywrapperaroundtheLevelDBenginedevelopedbyGoogle,whichisnormallyusedinwebbrowsersforlocaldatapersistence.Itisanon-indexed,NoSQLdatastoredesignedoriginallyforuseinbrowsers.TheNode.jsmodule,Level,usestheLevelDBAPI,andsupportsmultiplebackends,includingLevelDOWN,whichintegratestheC++LevelDBdatabaseintoNode.js.

Visithttps://www.npmjs.com/package/levelforinformationonthemodule.Thelevelpackageautomaticallysetsupthelevelupandleveldownpackages.

Toinstallthedatabaseengine,runthiscommand:

[email protected]

Then,startcreatingthemodels/notes-level.mjsmodule:

importfsfrom'fs-extra';

importpathfrom'path';

importutilfrom'util';

importNotefrom'./Note';

importlevelfrom'level';

importDBGfrom'debug';

constdebug=DBG('notes:notes-level');

consterror=DBG('notes:error-level');

vardb;

asyncfunctionconnectDB(){

if(typeofdb!=='undefined'||db)returndb;

db=awaitlevel(

Page 389: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

process.env.LEVELDB_LOCATION||'notes.level',{

createIfMissing:true,

valueEncoding:"json"

});

returndb;

}

Thelevelmodulegivesusadbobjectthroughwhichtointeractwiththedatabase.We'restoringthatobjectasaglobalwithinthemoduleforeaseofuse.Ifthedbobjectisset,wecanjustreturnitimmediately.Otherwise,weopenthedatabaseusingcreateIfMissingtogoaheadandcreatethedatabaseifneeded.

Thelocationofthedatabasedefaultstonotes.levelinthecurrentdirectory.TheenvironmentvariableLEVELDB_LOCATIONcanbeset,asthenameimplies,tospecifythedatabaselocation:

asyncfunctioncrupdate(key,title,body){

constdb=awaitconnectDB();

varnote=newNote(key,title,body);

awaitdb.put(key,note.JSON);

returnnote;

}

exportfunctioncreate(key,title,body){

returncrupdate(key,title,body);

}

exportfunctionupdate(key,title,body){

returncrupdate(key,title,body);

}

Callingdb.puteithercreatesanewdatabaseentry,orreplacesanexistingone.Therefore,bothupdateandcreatearesettobethesamefunction.WeconverttheNotetoJSONsoitcanbeeasilystoredinthedatabase:

exportasyncfunctionread(key){

constdb=awaitconnectDB();

varnote=Note.fromJSON(awaitdb.get(key));

returnnewNote(note.key,note.title,note.body);

}

Page 390: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ReadingaNoteiseasy:justcalldb.getanditretrievesthedata,whichmustbedecodedfromtheJSONrepresentation.

Noticethatdb.getanddb.putdidnottakeacallbackfunction,andthatweuseawaittogettheresultsvalue.Thefunctionsexportedbylevelcantakeacallbackfunction,inwhichthecallbackwillbeinvoked.Alternatively,ifnocallbackfunctionisprovided,thelevelfunctionwillinsteadreturnaPromiseforcompatibilitywithasyncfunctions:

exportasyncfunctiondestroy(key){

constdb=awaitconnectDB();

awaitdb.del(key);

}

Thedb.destroyfunctiondeletesarecordfromthedatabase:

exportasyncfunctionkeylist(){

constdb=awaitconnectDB();

varkeyz=[];

awaitnewPromise((resolve,reject)=>{

db.createKeyStream()

.on('data',data=>keyz.push(data))

.on('error',err=>reject(err))

.on('end',()=>resolve(keyz));

});

returnkeyz;

}

exportasyncfunctioncount(){

constdb=awaitconnectDB();

vartotal=0;

awaitnewPromise((resolve,reject)=>{

db.createKeyStream()

.on('data',data=>total++)

.on('error',err=>reject(err))

.on('end',()=>resolve(total));

});

returntotal;

}

exportasyncfunctionclose(){

var_db=db;

db=undefined;

return_db?_db.close():undefined;

Page 391: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

}

ThecreateKeyStreamfunctionusesanevent-orientedinterfacesimilartotheStreamsAPI.Itwillstreamthrougheverydatabaseentry,emittingeventsasitgoes.Adataeventisemittedforeverykeyinthedatabase,whiletheendeventisemittedattheendofthedatabase,andtheerroreventisemittedonerrors.Theeffectisthatthere'snosimplewaytopresentthisasasimplePromise.Instead,weinvokecreateKeyStream,letitrunitscourse,collectingdataasitgoes.WehavetowrapitinsideaPromiseobject,andcallresolveontheendevent.

Thenweaddthistopackage.jsoninthescriptssection:

"start-level":"DEBUG=notes:*NOTES_MODEL=levelnode--experimental-modules

./bin/www.mjs",

Finally,youcanruntheNotesapplication:

$DEBUG=notes:*npmrunstart-level

>[email protected]/chap07/notes

>node./bin/www

notes:serverListeningonport3000+0ms

Theprintoutintheconsolewillbethesame,andtheapplicationwillalsolookthesame.Youcanputitthroughitspacesandseethateverythingworkscorrectly.

Sinceleveldoesnotsupportsimultaneousaccesstoadatabasefrommultipleinstances,youwon'tbeabletousethemultipleNotesapplicationscenario.Youwill,however,beabletostopandrestarttheapplicationatwillwithoutlosinganynotes.

Page 392: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

StoringnotesinSQLwithSQLite3Togetstartedwithmorenormaldatabases,let'sseehowtouseSQLfromNode.js.First,we'lluseSQLite3,alightweight,simple-to-set-updatabaseengineeminentlysuitableformanyapplications.

Tolearnaboutthatdatabaseengine,visithttp://www.sqlite.org/.

TolearnabouttheNode.jsmodule,visithttps://github.com/mapbox/node-sqlite3/wiki/APIorhttps://www.npmjs.com/package/sqlite3.

TheprimaryadvantageofSQLite3isthatitdoesn'trequireaserver;itisaself-contained,no-set-up-requiredSQLdatabase.

Thefirststepistoinstallthemodule:[email protected]

Page 393: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SQLite3databaseschemaNext,weneedtomakesurethatourdatabaseisconfigured.We'reusingthisSQLtabledefinitionfortheschema(savethisasmodels/schema-sqlite3.sql):CREATETABLEIFNOTEXISTSnotes(notekeyVARCHAR(255),titleVARCHAR(255),bodyTEXT);

Howdoweinitializethisschemabeforewritingsomecode?Onewayistoensurethatthesqlite3packageisinstalledthroughyouroperatingsystempackagemanagementsystem,suchasusingapt-getonUbuntu/Debian,andMacPortsonmacOS.Onceit'sinstalled,youcanrunthefollowingcommand:$sqlite3chap07.sqlite3SQLiteversion3.21.02017-10-2418:55:49Enter".help"forusagehints.sqlite>CREATETABLEIFNOTEXISTSnotes(...>notekeyVARCHAR(255),...>titleVARCHAR(255),...>bodyTEXT...>);sqlite>.schemanotesCREATETABLEnotes(notekeyVARCHAR(255),titleVARCHAR(255),bodyTEXT);sqlite>^D$ls-lchap07.sqlite3-rwx------1davidstaff8192Jan1420:40chap07.sqlite3

Whilewecandothat,theTwelveFactorapplicationmodelsayswemust

Page 394: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

automateanyadministrativeprocessesinthisway.Tothatend,weshouldinsteadwritealittlescripttorunanSQLoperationonSQLite3andusethattoinitializethedatabase.

Fortunately,thesqlite3commandoffersusawaytodothis.Addthefollowingtothescriptssectionofpackage.json:"sqlite3-setup":"sqlite3chap07.sqlite3--initmodels/schema-sqlite3.sql",

Runthesetupscript:

$npmrunsqlite3-setup

>[email protected]/chap07/notes

>sqlite3chap07.sqlite3--initmodels/schema-sqlite3.sql

--Loadingresourcesfrommodels/schema-sqlite3.sql

SQLiteversion3.10.22016-01-2015:27:19

Enter".help"forusagehints.

sqlite>.schemanotes

CREATETABLEnotes(

notekeyVARCHAR(255),

titleVARCHAR(255),

bodyTEXT

);

sqlite>^D

WecouldhavewrittenasmallNode.jsscripttodothis,andit'seasytodoso.However,byusingthetoolsprovidedbythepackage,wehavelesscodetomaintaininourownproject.

Page 395: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SQLite3modelcodeNow,wecanwritecodetousethisdatabaseintheNotesapplication.

Createmodels/notes-sqlite3.mjsfile:

importutilfrom'util';

importNotefrom'./Note';

importsqlite3from'sqlite3';

importDBGfrom'debug';

constdebug=DBG('notes:notes-sqlite3');

consterror=DBG('notes:error-sqlite3');

vardb;//storethedatabaseconnectionhere

asyncfunctionconnectDB(){

if(db)returndb;

vardbfile=process.env.SQLITE_FILE||"notes.sqlite3";

awaitnewPromise((resolve,reject)=>{

db=newsqlite3.Database(dbfile,

sqlite3.OPEN_READWRITE|sqlite3.OPEN_CREATE,

err=>{

if(err)returnreject(err);

resolve(db);

});

});

returndb;

}

ThisservesthesamepurposeastheconnectDBfunctioninnotes-level.mjs:tomanagethedatabaseconnection.Ifthedatabaseisnotopen,it'llgoaheadanddoso,andevenmakesurethatthedatabasefileiscreated(ifitdoesn'texist).Butifitisalreadyopen,itisimmediatelyreturned:

exportasyncfunctioncreate(key,title,body){

vardb=awaitconnectDB();

varnote=newNote(key,title,body);

awaitnewPromise((resolve,reject)=>{

Page 396: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

db.run("INSERTINTOnotes(notekey,title,body)"+

"VALUES(?,?,?);",[key,title,body],err=>{

if(err)returnreject(err);

resolve(note);

});

});

returnnote;

}

exportasyncfunctionupdate(key,title,body){

vardb=awaitconnectDB();

varnote=newNote(key,title,body);

awaitnewPromise((resolve,reject)=>{

db.run("UPDATEnotes"+

"SETtitle=?,body=?WHEREnotekey=?",

[title,body,key],err=>{

if(err)returnreject(err);

resolve(note);

});

});

returnnote;

}

Theseareourcreateandupdatefunctions.Aspromised,wearenowjustifiedindefiningtheNotesmodeltohaveseparatefunctionsforcreateandupdateoperations,becausetheSQLstatementforeachisdifferent.

Callingdb.runexecutesanSQLquery,givingustheopportunitytoinsertparametersintothequerystring.

Thesqlite3moduleusesaparametersubstitutionparadigmthat'scommoninSQLprogramminginterfaces.TheprogrammerputstheSQLqueryintoastring,andthenplacesaquestionmarkineachplacewheretheaimistoinsertavalueintothequerystring.Eachquestionmarkinthequerystringhastomatchavalueinthearrayprovidedbytheprogrammer.Themoduletakescareofencodingthevaluescorrectlysothatthequerystringisproperlyformatted,whilepreventingSQLinjectionattacks.

Thedb.runfunctionsimplyrunstheSQLqueryitisgiven,anddoesnotretrieveanydata.Becausethesqlite3moduledoesn'tproduceanykindofPromise,wehavetowrapfunctioncallsinaPromiseobject:

Page 397: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

exportasyncfunctionread(key){

vardb=awaitconnectDB();

varnote=awaitnewPromise((resolve,reject)=>{

db.get("SELECT*FROMnotesWHEREnotekey=?",[key],(err,row)=>{

if(err)returnreject(err);

constnote=newNote(row.notekey,row.title,row.body);

resolve(note);

});

});

returnnote;

}

Toretrievedatausingthesqlite3module,youusethedb.get,db.all,ordb.eachfunctions.Thedb.getfunctionusedherereturnsonlythefirstrowoftheresultset.Thedb.allfunctionreturnsallrowsoftheresultsetatonce,whichcanbeaproblemforavailablememoryiftheresultsetislarge.Thedb.eachfunctionretrievesonerowatatime,whilestillallowingprocessingoftheentireresultset.

FortheNotesapplication,usingdb.gettoretrieveanoteissufficientbecausethereisonlyonenotepernotekey.Therefore,ourSELECTquerywillreturnatmostonerowanyway.Butwhatifyourapplicationwillseemultiplerowsintheresultset?We'llseewhattodoaboutthatinaminute.

Bytheway,thisreadfunctionhasabuginit.Seeifyoucanspottheerror.We'llreadmoreaboutthisinChapter11,UnitTestingandFunctionalTesting,whenourtestingeffortsuncoverthebug:

exportasyncfunctiondestroy(key){

vardb=awaitconnectDB();

returnawaitnewPromise((resolve,reject)=>{

db.run("DELETEFROMnotesWHEREnotekey=?;",[key],err=>{

if(err)returnreject(err);

resolve();

});

});

}

Todestroyanote,wesimplyexecutetheDELETEFROMstatement:

Page 398: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

exportasyncfunctionkeylist(){

vardb=awaitconnectDB();

varkeyz=awaitnewPromise((resolve,reject)=>{

varkeyz=[];

db.all("SELECTnotekeyFROMnotes",(err,rows)=>{

if(err)returnreject(err);

resolve(rows.map(row=>row.notekey));

});

});

returnkeyz;

}

Thedb.allfunctionretrievesallrowsoftheresultset.

Thecontractforthisfunctionistoreturnanarrayofnotekeys.Therowsobjectisanarrayofresultsfromthedatabasethatcontainsthedatawearetoreturn,butinadifferentformat.Therefore,weusethemapfunctiontoconvertthearrayintotheformatrequiredtofulfillthecontract:

exportasyncfunctioncount(){

vardb=awaitconnectDB();

varcount=awaitnewPromise((resolve,reject)=>{

db.get("selectcount(notekey)ascountfromnotes",(err,row)

=>{

if(err)returnreject(err);

resolve(row.count);

});

});

returncount;

}

exportasyncfunctionclose(){

var_db=db;

db=undefined;

return_db?newPromise((resolve,reject)=>{

_db.close(err=>{

if(err)reject(err);

elseresolve();

});

}):undefined;

}

WecansimplyuseSQLtocountthenumberofnotesforus.Inthiscase,db.getreturnsarowwithasinglecolumn,count,whichisthevaluewewant

Page 399: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

toreturn.

Page 400: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

RunningNoteswithSQLite3Finally,we'rereadytoruntheNotesapplicationwithSQLite3.Addthefollowingcodetothescriptssectionofpackage.json:"start-sqlite3":"SQLITE_FILE=chap07.sqlite3NOTES_MODEL=sqlite3node--experimental-modules./bin/www.mjs",

RuntheNotesapplication:

$DEBUG=notes:*npmrunstart-sqlite3

>[email protected]/chap07/notes

>SQLITE_FILE=chap07.sqlite3NOTES_MODEL=models/notes-sqlite3node

./bin/www.mjs

notes:serverListeningonport3000+0ms

notes:sqlite3-modelOpenedSQLite3databasechap07.sqlite3+5s

Youcannowbrowsetheapplicationathttp://localhost:3000,andrunitthroughitspacesasbefore.

BecauseSQLite3supportssimultaneousaccessfrommultipleinstances,youcanrunthemultiserverexamplebyaddingthistothescriptssectionofpackage.json:"server1-sqlite3":"SQLITE_FILE=chap07.sqlite3NOTES_MODEL=sqlite3PORT=3001node./bin/www.mjs","server2-sqlite3":"SQLITE_FILE=chap07.sqlite3NOTES_MODEL=sqlite3PORT=3002node./bin/www.mjs",

Then,runeachoftheseinseparatecommandWindows,asbefore.

Becausewestillhaven'tmadeanychangestotheViewtemplatesorCSSfiles,theapplicationwilllookthesameasbefore.

Ofcourse,youcanusethesqlitecommand,orotherSQLite3client

Page 401: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

applications,toinspectthedatabase:

$sqlite3chap07.sqlite3

SQLiteversion3.10.22016-01-2015:27:19

Enter".help"forusagehints.

sqlite>select*fromnotes;

hithere|HiThere||hotherewhatthere

himom|HiMom||Thisiswherewesaythanks

Page 402: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

StoringnotestheORMwaywithSequelizeThereareseveralpopularSQLdatabaseengines,suchasPostgreSQL,MySQL(https://www.npmjs.com/package/mysql),andMariaDB(https://www.npmjs.com/package/mariasql).CorrespondingtoeachareNode.jsclientmodulessimilarinnaturetothesqlite3modulethatwejustused.TheprogrammerisclosetotheSQL,whichcanbegoodinthesamewaythatdrivingastickshiftcarisfun.Butwhatifwewantahigher-levelviewofthedatabasesothatwecanthinkintermsofobjectsratherthanrowsofadatabasetable?ObjectRelationMapping(ORM)systemsprovidesuchahigher-levelinterfaceandevenoffertheabilitytousethesamedatamodelwithseveraldatabases.

TheSequelizemodule(http://www.sequelizejs.com/)isPromise-based,offersstrong,well-developedORMfeatures,andcanconnectwithSQLite3,MySQL,PostgreSQL,MariaDB,andMSSQL.BecauseSequelizeisPromise-based,itwillfitnaturallywiththePromise-basedapplicationcodewe'rewriting.

AprerequisitetomostSQLdatabaseenginesishavingaccesstoadatabaseserver.Intheprevioussection,weskirtedaroundthatissuebyusingSQLite3,whichrequiresnodatabaseserversetup.Whileit'spossibletoinstalladatabaseserveronyourlaptop,wewanttoavoidthecomplexityofdoingso,andwilluseSequelizetomanageanSQLite3database.We'llalsoseethatit'ssimplyamatterofaconfigurationfiletorunthesameSequelizecodeagainstahosteddatabasesuchasMySQL.InChapter10,DeployingNode.jsApplications,we'lllearnhowtouseDockertoeasilysetupanyservice,includingdatabaseservers,onourlaptopanddeploytheexactsameconfigurationtoaliveserver.MostwebhostingprovidersofferMySQLorPostgreSQLaspartoftheservice.

Page 403: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Beforewestartonthecode,let'sinstalltwomodules:

[email protected]

[email protected]

ThefirstobviouslyinstallstheSequelizepackage.Thesecond,js-yaml,isinstalledsothatwecanimplementaYAML-formattedfiletostoretheSequelizeconnectionconfiguration.YAMLisahuman-readabledataserializationlanguage,whichsimplymeansYAMLisaneasy-to-usetextfileformattodescribedataobjects.PerhapsthebestplacetolearnaboutYAMLisitsWikipediapageathttps://en.wikipedia.org/wiki/YAML.

Page 404: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SequelizemodelfortheNotesapplicationLet'screateanewfile,models/notes-sequelize.mjs:

importfsfrom'fs-extra';

importutilfrom'util';

importjsyamlfrom'js-yaml';

importNotefrom'./Note';

importSequelizefrom'sequelize';

importDBGfrom'debug';

constdebug=DBG('notes:notes-sequelize');

consterror=DBG('notes:error-sequelize');

varSQNote;

varsequlz;

asyncfunctionconnectDB(){

if(typeofsequlz==='undefined'){

constYAML=awaitfs.readFile(process.env.SEQUELIZE_CONNECT,'utf8');

constparams=jsyaml.safeLoad(YAML,'utf8');

sequlz=newSequelize(params.dbname,params.username,

params.password,params.params);

}

if(SQNote)returnSQNote.sync();

SQNote=sequlz.define('Note',{

notekey:{type:Sequelize.STRING,primaryKey:true,unique:

true},

title:Sequelize.STRING,

body:Sequelize.TEXT

});

returnSQNote.sync();

}

Thedatabaseconnectionisstoredinthesequlzobject,andisestablishedbyreadingaconfigurationfile(we'llgooverthisfilelater),andinstantiatingaSequelizeinstance.Thedatamodel,SQNote,describesourobjectstructuretoSequelizesothatitcandefinecorrespondingdatabasetable(s).

Page 405: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

IfSQNoteisalreadydefined,wesimplyreturnit,otherwisewedefineandreturnSQNote.

TheSequelizeconnectionparametersarestoredinaYAMLfilewespecifyintheSEQUELIZE_CONNECTenvironmentvariable.ThelinenewSequelize(..)opensthedatabaseconnection.Theparametersobviouslycontainanyneededdatabasename,username,password,andotheroptionsrequiredtoconnectwiththedatabase.

Thelinesequlz.defineiswherewedefinethedatabaseschema.InsteadofdefiningtheschemaastheSQLcommandtocreatethedatabasetable,we'regivingahigh-leveldescriptionofthefieldsandtheircharacteristics.Sequelizemapstheobjectattributesintocolumnsintables.

We'retellingSequelizetocallthisschemaNote,butwe'reusingaSQNotevariabletorefertothatschema.That'sbecausewealreadydefinedNoteasaclasstorepresentnotes.Toavoidaclashofnames,we'llkeepusingtheNoteclass,anduseSQNotetointeractwithSequelizeaboutthenotesstoredinthedatabase.

Onlinedocumentationcanbefoundatthefollowinglocations:Sequelizeclass:http://docs.sequelizejs.com/en/latest/api/sequelize/.Definingmodels:http://docs.sequelizejs.com/en/latest/api/model/.

Addthesefunctionstomodels/notes-sequelize.mjs:

exportasyncfunctioncreate(key,title,body){

constSQNote=awaitconnectDB();

constnote=newNote(key,title,body);

awaitSQNote.create({notekey:key,title:title,body:body});

returnnote;

}

exportasyncfunctionupdate(key,title,body){

constSQNote=awaitconnectDB();

constnote=awaitSQNote.find({where:{notekey:key}})

if(!note){thrownewError(`Nonotefoundfor${key}`);}else{

awaitnote.updateAttributes({title:title,body:body});

returnnewNote(key,title,body);

}

}

Page 406: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ThereareseveralwaystocreateanewobjectinstanceinSequelize.Thesimplestistocallanobject'screatefunction(inthiscase,SQNote.create).Thatfunctioncollapsestogethertwootherfunctions,build(tocreatetheobject),andsave(towriteittothedatabase).

Updatinganobjectinstanceisalittledifferent.First,wemustretrieveitsentryfromthedatabaseusingthefindoperation.Thefindoperationisgivenanobjectspecifyingthequery.Usingfind,weretrieveoneinstance,whereasthefindAlloperationretrievesallmatchinginstances.

FordocumentationonSequelizequeries,visithttp://docs.sequelizejs.com/en/latest/docs/querying/.

LikemostorallotherSequelizefunctions,SQNote.findreturnsaPromise.Therefore,insideanasyncfunction,weawaittheresultoftheoperation.

Theupdateoperationrequirestwosteps,thefirstbeingtofindthecorrespondingobjecttoreaditinfromthedatabase.Oncetheinstanceisfound,wecanupdateitsvaluessimplywiththeupdateAttributesfunction:

exportasyncfunctionread(key){

constSQNote=awaitconnectDB();

constnote=awaitSQNote.find({where:{notekey:key}})

if(!note){thrownewError(`Nonotefoundfor${key}`);}else{

returnnewNote(note.notekey,note.title,note.body);

}

}

Toreadanote,weusethefindoperationagain.Thereisthepossibilityofanemptyresult,andwehavetothrowanerrortomatch.

ThecontractforthisfunctionistoreturnaNoteobject.ThatmeanstakingthefieldsretrievedusingSequelizeandusingthattocreateaNoteobject:

exportasyncfunctiondestroy(key){

constSQNote=awaitconnectDB();

constnote=awaitSQNote.find({where:{notekey:key}})

returnnote.destroy();

Page 407: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

}

Todestroyanote,weusethefindoperationtoretrieveitsinstance,andthencallitsdestroy()method:

exportasyncfunctionkeylist(){

constSQNote=awaitconnectDB();

constnotes=awaitSQNote.findAll({attributes:['notekey']});

returnnotes.map(note=>note.notekey);

}

BecausethekeylistfunctionactsonallNoteobjects,weusethefindAlloperation.Wequeryforthenotekeyattributeonallnotes.We'regivenanarrayofobjectswithafieldnamednotekey,andweusethe.mapfunctiontoconvertthisintoanarrayofthenotekeys:

exportasyncfunctioncount(){

constSQNote=awaitconnectDB();

constcount=awaitSQNote.count();

returncount;

}

exportasyncfunctionclose(){

if(sequlz)sequlz.close();

sequlz=undefined;

SQNote=undefined;

}

Forthecountfunction,wecanjustusethecount()methodtocalculatetheneededresult.

Page 408: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ConfiguringaSequelizedatabaseconnectionSequelizesupportsthesameAPIonseveralSQLdatabaseengines.ThedatabaseconnectionisinitializedusingparametersontheSequelizeconstructor.TheTwelveFactorApplicationmodelsuggeststhatconfigurationdatasuchasthisshouldbekeptoutsidethecodeandinjectedusingenvironmentvariablesorasimilarmechanism.Whatwe'lldoisuseaYAML-formattedfiletostoretheconnectionparameters,specifyingthefilenamewithanenvironmentvariable.

TheSequelizelibrarydoesnotdefineanysuchfileforstoringconnectionparameters.Butit'ssimpleenoughtodevelopsuchafile.Let'sdoso.

TheAPIfortheSequelizeconstructoris:constructor(database:String,username:String,password:String,options:Object).

IntheconnectDBfunction,wewrotetheconstructorasfollows:

sequlz=newSequelize(params.dbname,params.username,params.password,

params.params);

Thisfile,namedmodels/sequelize-sqlite.yaml,provideswithusasimplemappingthatlookslikethisforanSQLite3database:

dbname:notes

username:

password:

params:

dialect:sqlite

storage:notes-sequelize.sqlite3

TheYAMLfileisadirectmappingtotheSequelizeconstructor

Page 409: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

parameters.Thedbname,username,andpasswordfieldsinthisfilecorresponddirectlytotheconnectioncredentials,andtheparamsobjectgivesadditionalparameters.Therearemany,many,possibleattributestouseintheparamsfield,andyoucanreadaboutthemintheSequelizedocumentationathttp://docs.sequelizejs.com/manual/installation/usage.html.

ThedialectfieldtellsSequelizewhatkindofdatabasetouse.ForanSQLitedatabase,thedatabasefilenameisgiveninthestoragefield.

Let'sfirstuseSQLite3,becausenofurthersetupisrequired.Afterthat,we'llgetadventurousandreconfigureourSequelizemoduletouseMySQL.

Ifyoualreadyhaveadifferentdatabaseserveravailable,it'ssimpletocreateacorrespondingconfigurationfile.ForaplausibleMySQLdatabaseonyourlaptop,createanewfile,suchasmodels/sequelize-mysql.yaml,containingsomethinglikethefollowingcode:

dbname:notes

username:..username

password:..password

params:

host:localhost

port:3306

dialect:mysql

Thisisstraightforward.Theusernameandpasswordmustcorrespondtothedatabasecredentials,whilehostandportwillspecifywherethedatabaseishosted.Setthedatabasedialectandotherconnectioninformation,andyou'regoodtogo.

TouseMySQL,youwillneedtoinstallthebaseMySQLdriversothatSequelizecanuseMySQL:

[email protected]

RunningwithSequelizeagainstotherdatabasesitsupports,suchas

Page 410: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

PostgreSQL,isjustassimple.Justcreateaconfigurationfile,installtheNode.jsdriver,andinstall/configurethedatabaseengine.

Page 411: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

RunningtheNotesapplicationwithSequelizeNowwecangetreadytoruntheNotesapplicationusingSequelize.WecanrunthisagainstbothSQLite3andMySQL,butlet'sstartwithSQLite.Addthisentrytothescriptsentryinpackage.json:

"start-sequelize":"SEQUELIZE_CONNECT=models/sequelize-sqlite.yaml

NOTES_MODEL=sequelizenode--experimental-modules./bin/www.mjs"

Thenrunitasfollows:

$DEBUG=notes:*npmrunstart-sequelize

>[email protected]/chap07/notes

>SEQUELIZE_CONNECT=models/sequelize-sqlite.yamlNOTES_MODEL=sequelizenode-

-experimental-modules./bin/www.mjs

notes:serverListeningonport3000+0ms

Asbefore,theapplicationlooksexactlythesamebecausewe'venotchangedtheViewtemplatesorCSSfiles.Putitthroughitspacesandeverythingshouldwork.

WithSequelize,multipleNotesapplicationinstancesisassimpleasaddingtheselinestothescriptssectionofpackage.json,andthenstartingbothinstancesasbefore:

"server1-sequelize":"SEQUELIZE_CONNECT=models/sequelize-sqlite.yaml

NOTES_MODEL=sequelizePORT=3001node--experimental-modules./bin/www.mjs",

"server2-sequelize":"SEQUELIZE_CONNECT=models/sequelize-sqlite.yaml

NOTES_MODEL=sequelizePORT=3002node--experimental-modules./bin/www.mjs",

Page 412: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Youwillbeabletostartbothinstances,useseparatebrowserwindowstovisitbothinstances,andseethattheyshowthesamesetofnotes.

ToreiterateusingtheSequelize-basedmodelonagivendatabaseserver:

1. Installandprovisionthedatabaseserverinstance,orelsegettheconnectionparametersforanalready-provisioneddatabaseserver.

2. InstallthecorrespondingNode.jsdriver.3. WriteaYAMLconfigurationfilecorrespondingtotheconnection

parameters.4. Createnewscriptsentriesinpackage.jsontoautomatestartingNotes

againstthatdatabase.

Page 413: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

StoringnotesinMongoDBMongoDBiswidelyusedwithNode.jsapplications,asignofwhichisthepopularMEANacronym:MongoDB(orMySQL),Express,Angular,andNode.js.MongoDBisoneoftheleadingNoSQLdatabases.Itisdescribedasascalable,high-performance,opensource,document-orienteddatabase.ItusesJSON-styledocumentswithnopredefined,rigidschemaandalargenumberofadvancedfeatures.Youcanvisittheirwebsiteformoreinformationanddocumentationathttp://www.mongodb.org.

DocumentationontheNode.jsdriverforMongoDBcanbefoundathttps://www.npmjs.com/package/mongodbandhttp://mongodb.github.io/node-mongodb-native/.

MongooseisapopularORMforMongoDB(http://mongoosejs.com/).Inthissection,we'llusethenativeMongoDBdriverinstead,butMongooseisaworthyalternative.

YouwillneedarunningMongoDBinstance.Thecompose.io(https://www.compose.io/)andScaleGrid.io(https://scalegrid.io/)hostedserviceprovidersofferhostedMongoDBservices.Nowadays,itisstraightforwardtohostMongoDBasaDockercontaineraspartofasystembuiltofotherDockercontainers.We'lldothisinChapter11,UnitTestingandFunctionalTesting.

It'spossibletosetupatemporaryMongoDBinstancefortestingon,say,yourlaptop.Itisavailableinalltheoperatingsystempackagemanagementsystems,andtheMongoDBwebsitehasinstructions(https://docs.mongodb.org/manual/installation/).

Onceinstalled,it'snotnecessarytosetupMongoDBasabackgroundservice.Instead,youcanrunacoupleofsimplecommandstogetaMongoDBinstancerunningintheforegroundofacommandwindow,whichyoucankillandrestartanytimeyoulike.

Page 414: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Inonecommandwindow,runthefollowing:

$mkdirdata

$mongod--dbpathdata

Inanothercommandwindow,youcantestitasfollows:

$mongo

MongoDBshellversion:3.0.8

connectingto:test

WelcometotheMongoDBshell.

Forinteractivehelp,type"help".

Formorecomprehensivedocumentation,see

http://docs.mongodb.org/

Questions?Trythesupportgroup

http://groups.google.com/group/mongodb-user

>db.foo.save({a:1});

WriteResult({"nInserted":1})

>db.foo.find();

{"_id":ObjectId("56c0c98673f65b7988a96a77"),"a":1}

>

bye

Thissavesadocumentinthecollectionnamedfoo.Thesecondcommandfindsalldocumentsinfoo,printingthemoutforyou.The_idfieldisaddedbyMongoDBandservesasadocumentidentifier.Thisisusefulfortestinganddebugging.Forarealdeployment,yourMongoDBservermustbeproperlyinstalledonaserver.SeetheMongoDBdocumentationfortheseinstructions.

Page 415: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

MongoDBmodelfortheNotesapplicationNowthatyou'veprovedyouhaveaworkingMongoDBserver,let'sgettowork.

InstallingtheNode.jsdriverisassimpleasrunningthefollowingcommand:

[email protected]

Nowcreateanewfile,models/notes-mongodb.mjs:

importutilfrom'util';

importNotefrom'./Note';

importmongodbfrom'mongodb';

constMongoClient=mongodb.MongoClient;

importDBGfrom'debug';

constdebug=DBG('notes:notes-mongodb');

consterror=DBG('notes:error-mongodb');

varclient;

asyncfunctionconnectDB(){

if(!client)client=awaitMongoClient.connect(process.env.MONGO_URL);

return{

db:client.db(process.env.MONGO_DBNAME),

client:client

};

}

TheMongoClientclassisusedtoconnectwithaMongoDBinstance.TherequiredURL,whichwillbespecifiedthroughanenvironmentvariable,usesastraightforwardformat:mongodb://localhost/.Thedatabasenameisspecifiedviaanotherenvironmentvariable.

Page 416: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Documentationforthecorrespondingobjectscanbefoundathttp://mongodb.github.io/node-mongodb-native/2.2/api/MongoClient.html

forMongoClientandhttp://mongodb.github.io/node-mongodb-native/2.2/api/Db.htmlforDb

Thiscreatesthedatabaseclient,andthenopensthedatabaseconnection.BothobjectsarereturnedfromconnectDBinananonymousobject.ThegeneralpatternforMongoDBoperationsisasfollows:

(async()=>{

constclient=awaitMongoClient.connect(process.env.MONGO_URL);

constdb=client.db(process.env.MONGO_DBNAME);

//performdatabaseoperationsusingdbobject

client.close();

})();

Therefore,ourmodelmethodsrequirebothclientanddbobjects,becausetheywilluseboth.Let'sseehowthat'sdone:

exportasyncfunctioncreate(key,title,body){

const{db,client}=awaitconnectDB();

constnote=newNote(key,title,body);

constcollection=db.collection('notes');

awaitcollection.insertOne({notekey:key,title,body});

returnnote;

}

exportasyncfunctionupdate(key,title,body){

const{db,client}=awaitconnectDB();

constnote=newNote(key,title,body);

constcollection=db.collection('notes');

awaitcollection.updateOne({notekey:key},{$set:{title,body}});

returnnote;

}

Weretrievedbandclientintoindividualvariablesusingadestructuringassignment.

MongoDBstoresalldocumentsincollections.Acollectionisagroupofrelateddocuments,andacollectionisanalogoustoatableinarelationaldatabase.ThismeanscreatinganewdocumentorupdatinganexistingonestartsbyconstructingitasaJavaScriptobject,andthenaskingMongoDBtosavethatobjecttothedatabase.MongoDBautomaticallyencodesthe

Page 417: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

objectintoitsinternalrepresentation.

Thedb.collectionmethodgivesusaCollectionobjectwithwhichwecanmanipulatethenamedcollection.Seeitsdocumentationathttp://mongodb.github.io/node-mongodb-native/2.2/api/Collection.html.

Asthemethodnameimplies,insertOneinsertsonedocumentintothecollection.Likewise,theupdateOnemethodfirstfindsadocument(inthiscase,bylookingupthedocumentwiththematchingnotekeyfield),andthenchangesfieldsinthedocumentasspecified.

You'llseethatthesemethodsreturnaPromise.ThemongodbdriversupportsbothcallbacksandPromises.Manymethodswillinvokethecallbackfunctionifoneisprovided,otherwiseitreturnsaPromisethatwilldelivertheresultsorerrors.And,ofcourse,sincewe'reusingasyncfunctions,theawaitkeywordmakesthissoclean.

Furtherdocumentationcanbefoundatthefollowinglinks:Insert:https://docs.mongodb.org/getting-started/node/insert/.Update:https://docs.mongodb.org/getting-started/node/update/.

Next,let'slookatreadinganotefromMongoDB:

exportasyncfunctionread(key){

const{db,client}=awaitconnectDB();

constcollection=db.collection('notes');

constdoc=awaitcollection.findOne({notekey:key});

constnote=newNote(doc.notekey,doc.title,doc.body);

returnnote;

}

Themongodbdriversupportsseveralvariantsoffindoperations.Inthiscase,theNotesapplicationensuresthatthereisexactlyonedocumentmatchingagivenkey.Therefore,wecanusethefindOnemethod.Asthenameimplies,findOnewillreturnthefirstmatchingdocument.

TheargumenttofindOneisaquerydescriptor.Thissimplequerylooksfordocumentswhosenotekeyfieldmatchestherequestedkey.Anemptyquerywill,ofcourse,matchalldocumentsinthecollection.Youcanmatch

Page 418: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

againstotherfieldsinasimilarway,andthequerydescriptorcandomuchmore.Fordocumentationonqueries,visithttps://docs.mongodb.org/getting-started/node/query/.

TheinsertOnemethodweusedearlieralsotookthesamekindofquerydescriptor.

Inordertosatisfythecontractforthisfunction,wecreateaNoteobjectandthenreturnittothecaller.Hence,wecreateaNoteusingthedataretrievedfromthedatabase:

exportasyncfunctiondestroy(key){

const{db,client}=awaitconnectDB();

constcollection=db.collection('notes');

awaitcollection.findOneAndDelete({notekey:key});

}

OneofthefindvariantsisfindOneAndDelete.Asthenameimplies,itfindsonedocumentmatchingthequerydescriptor,andthendeletesthatdocument:

exportasyncfunctionkeylist(){

const{db,client}=awaitconnectDB();

constcollection=db.collection('notes');

constkeyz=awaitnewPromise((resolve,reject)=>{

varkeyz=[];

collection.find({}).forEach(

note=>{keyz.push(note.notekey);},

err=>{

if(err)reject(err);

elseresolve(keyz);

}

);

});

returnkeyz;

}

Here,we'reusingthebasefindoperationandgivingitanemptyquerysothatitmatcheseverydocument.Whatwe'retoreturnisanarraycontainingthenotekeyforeverydocument.

Page 419: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

AllofthefindoperationsreturnaCursorobject.Thedocumentationcanbefoundathttp://mongodb.github.io/node-mongodb-native/2.1/api/Cursor.html.

TheCursorobjectis,asthenameimplies,apointerintoaresultsetfromaquery.Ithasanumberofusefulfunctionsrelatedtooperatingonaresultset.Forexample,youcanskipthefirstfewitemsintheresults,orlimitthesizeoftheresultset,orperformthefilterandmapoperations.

TheCursor.forEachmethodtakestwocallbackfunctions.Thefirstiscalledoneveryelementintheresultset.Inthiscase,wecanusethattorecordjustthenotekeyinanarray.Thesecondcallbackiscalledafterallelementsintheresultsethavebeenprocessed.Weusethistoindicatesuccessorfailure,andtoreturnthekeyzarray.

BecauseforEachusesthispattern,itdoesnothaveanoptionforsupplyingaPromise,andwehavetocreatethePromiseourselves,asshownhere:

exportasyncfunctioncount(){

const{db,client}=awaitconnectDB();

constcollection=db.collection('notes');

constcount=awaitcollection.count({});

returncount;

}

exportasyncfunctionclose(){

if(client)client.close();

client=undefined;

}

Thecountmethodtakesaquerydescriptorand,asthenameimplies,countsthenumberofmatchingdocuments.

Page 420: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

RunningtheNotesapplicationwithMongoDBNowthatwehaveourMongoDBmodel,wecangetreadytorunNoteswithit.

Bynowyouknowthedrill;addthistothescriptssectionofpackage.json:"start-mongodb":"MONGO_URL=mongodb://localhost/MONGO_DBNAME=chap07NOTES_MODEL=mongodbnode--experimental-modules./bin/www.mjs",

TheMONGO_URLenvironmentvariableistheURLtoconnectwithyourMongoDBdatabase.

YoucanstarttheNotesapplicationasfollows:

$DEBUG=notes:*npmrunstart-mongodb

>[email protected]/chap07/notes

>MONGO_URL=mongodb://localhost/MONGO_DBNAME=chap07NOTES_MODEL=mongodbnode

--experimental-modules./bin/www

notes:serverListeningonport3000+0ms

Youcanbrowsetheapplicationathttp://localhost:3000andputitthroughitspaces.Youcankillandrestarttheapplication,andyournoteswillstillbethere.

Addthistothescriptssectionofpackage.json:"server1-mongodb":"MONGO_URL=mongodb://localhost/MONGO_DBNAME=chap07NOTES_MODEL=mongodbPORT=3001node--experimental-modules./bin/www.mjs","server2-mongodb":"MONGO_URL=mongodb://localhost/MONGO_DBNAME=chap07NOTES_MODEL=mongodbPORT=3002

Page 421: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

node--experimental-modules./bin/www.mjs",

YouwillbeabletostarttwoinstancesoftheNotesapplication,andseethatbothsharethesamesetofnotes.

Page 422: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SummaryInthischapter,wewentthrougharealwhirlwindofdifferentdatabasetechnologies.Whilewelookedatthesamesevenfunctionsoverandover,it'susefultobeexposedtothevariousdatastoragemodelsandwaysofgettingthingsdone.Evenso,weonlytouchedthesurfaceofoptionsforaccessingdatabasesanddatastorageenginesfromNode.js.

Byabstractingthemodelimplementationscorrectly,wewereabletoeasilyswitchdatastorageengineswhilenotchangingtherestoftheapplication.Wedidskiparoundtheissueofsettingupdatabaseservers.Aspromised,we'llgettothatinChapter10,DeployingNode.jsApplications,whenweexploreproductiondeploymentofNode.jsapplications.

Byfocusingthemodelcodeonthepurposeofstoringdata,boththemodelsandtheapplicationshouldbeeasiertotest.Theapplicationcanbetestedwithamockdatamodulethatprovidesknownpredictablenotesthatcanbecheckedpredictably.We'lllookatthisinmoredepthinChapter11,UnitTestingandFunctionalTesting.

Inthenextchapter,we'llfocusonauthenticatingourusersusingOAuth2.

Page 423: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

MultiuserAuthenticationtheMicroserviceWayNowthatourNotesapplicationcansaveitsdatainadatabase,wecanthinkaboutthenextphaseofmakingthisarealapplication,namelyauthenticatingourusers.

It'ssonaturaltologintoawebsitetouseitsservices.Wedoiteveryday,andweeventrustbankingandinvestmentorganizationstosecureourfinancialinformationthroughloginproceduresonawebsite.HTTPisastatelessprotocol,andawebapplicationcannottellmuchaboutoneHTTPrequestversusanother.BecauseHTTPisstateless,HTTPrequestsdonotnativelyknowwhethertheuserdrivingthewebbrowserisloggedin,theuser'sidentity,orevenwhethertheHTTPrequestwasinitiatedbyahumanbeing.

Thetypicalmethodforuserauthenticationistosendacookietothebrowsercontainingatokentocarryuseridentity.Thecookieneedstocontaindataidentifyingthebrowserandwhetherthatbrowserisloggedin.Thecookiewillthenbesentwitheveryrequest,lettingtheapplicationtrackwhichuseraccountisassociatedwiththebrowser.

WithExpress,thebestwaytodothisiswiththeexpress-sessionmiddleware.Itstoresdataasacookieandlooksforthatdataoneverybrowserrequest.Itiseasytoconfigure,butisnotacompletesolutionforuserauthentication.Thereareseveraladd-onmodulesthathandleuserauthentication,andsomeevensupportauthenticatingusersagainstthird-partywebsites,suchasFacebookorTwitter.

Onepackageappearstobeleadingthepackinuserauthentication–Passport(http://passportjs.org/).Itsupportsalonglistofservicesagainstwhichtoauthenticate,makingiteasytodevelopawebsitethatletsusers

Page 424: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

signupwithcredentialsfromanotherwebsite,forexample,Twitter.Another,express-authentication(https://www.npmjs.com/package/express-authentication),billsitselfastheopinionatedalternativetoPassport.

WewillusePassporttoauthenticateusersagainstbothalocallystoredusercredentialsdatabaseandusingOAuth2toauthenticateagainstaTwitteraccount.We'llalsotakethisasanopportunitytoexploreREST-basedmicroserviceimplementationwithNode.js.

Inthischapter,we'lldiscussthefollowingthreeaspectsofthisphase:

Creatingamicroservicetostoreuserprofile/authenticationdata.

Userauthenticationwithalocallystoredpassword.

UsingOAuth2tosupportauthenticationviathird-partyservices.Specifically,we'lluseTwitterasathird-partyauthenticationservice.

Let'sgetstarted!

Thefirstthingtodoisduplicatethecodeusedforthepreviouschapter.Forexample,ifyoukeptthatcodeinchap07/notes,createanewdirectory,chap08/notes.

Page 425: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

CreatingauserinformationmicroserviceWecouldimplementuserauthenticationandaccountsbysimplyaddingausermodel,andafewroutesandviewstotheexistingNotesapplication.Whileitwouldbeaccomplishable,isthiswhatwewoulddoinareal-worldproductionapplication?

Considerthehighvalueofuseridentityinformation,andthesuper-strongneedforrobustandreliableuserauthentication.Websiteintrusionshappenregularly,anditseemstheitemmostfrequentlystolenisuseridentities.

Canyoudesignandbuildauserauthenticationsystemwiththerequiredlevelofsecurity?Onethatisprobablysafeagainstallkindsofintruders?

Aswithsomanyothersoftwaredevelopmentproblems,it'sbesttouseapre-existingauthenticationlibrary,preferablyonewithalongtrackrecord,wheresignificantbugshavebeenfixedalready.

Anotherissueisarchitecturalchoicestopromotesecurity.Bugswilloccurandthetalentedmiscreantswillbreakin.Wallingofftheuserinformationdatabaseisanexcellentideatolimittherisk.

Keepingauserinformationdatabaseenablesyoutoauthenticateyourusers,presentuserprofiles,helpusersconnectwitheachother,andsoforth.Thoseareusefulservicestooffertowebsiteusers,buthowcanyoulimittheriskthatdatawillfallintothewronghands?

Inthischapter,we'lldevelopauserauthenticationmicroservice.Theplanistoeventuallysegregatethatserviceintoawell-protectedbarricadedarea.Thismimicsanarchitecturalchoicemadebysomesites,tostrictlycontrolAPIandevenphysicalaccesstotheuserinformationdatabase,

Page 426: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

implementingasmanytechnologicalbarriersaspossibleagainstunapprovedaccess.

Microservicesare,ofcourse,notapanacea,meaningweshouldn'ttrytoforce-fiteveryapplicationintothemicroservicebox.Byanalogy,microservicesareliketheUnixphilosophyofsmalltoolseachdoingonethingwell,whichwemix/match/combineintolargertools.Anotherwordforthisiscomposability.Whilewecanbuildalotofusefulsoftwaretoolswiththatphilosophy,doesitworkforapplicationssuchasPhotoshoporLibreOffice?Whilecomposingasystemoutofsingle-purposetoolsishighlyflexible,onelosestheadvantagesgainedbytightintegrationofcomponents.

ThefirstquestioniswhethertouseaREST-serviceorientedframework,codetheRESTapplicationonbareNode.js,orwhat?YoucouldimplementRESTservicesonthebuilt-inhttpmodule.Theadvantageofusinganapplicationframeworkistheframeworkauthorswillhavealreadybaked-inalotofbestpracticesandbugfixingandsecuritymeasures.Express,forexample,iswidelyused,verypopular,andcaneasilybeusedforRESTservices.ThereareotherframeworksmorealignedwithdevelopingRESTservices,andwe'lluseoneofthem–Restify(http://restify.com/).

Theuserauthenticationserverwillrequiretwomodules:

UsingRestify,implementingtheRESTinterface

AdatamodelusingSequelizetostoreuserdataobjectsinanSQLdatabase

Totesttheservice,we'llwriteacoupleofsimplescriptsforadministeringuserinformationinthedatabase.Wewon'tbeimplementinganadministrativeuserinterfaceintheNotesapplication,andwillrelyonthescriptstoadministertheusers.Asasideeffect,we'llhaveatooltorunacoupleofsimpletestsagainsttheuserservice.

Page 427: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Afterthisserviceisfunctioningcorrectly,we'llsetaboutmodifyingtheNotesapplicationtoaccessuserinformationfromtheservice,whileusingPassporttohandleauthentication.

ThefirststepiscreatinganewdirectorytoholdtheUserInformationmicroservice.ThisshouldbeasiblingdirectorytotheNotesapplication.Ifyoucreatedadirectorynamedchap08/notestoholdtheNotesapplication,thencreateadirectorynamedchap08/userstoholdthemicroservice.

Thenrunthefollowingcommands:

$cdusers

$npminit

..answerquestions

..name-user-auth-server

$npminstalldebug@^2.6.xfs-extra@^5.xjs-yaml@^3.10.x\

restify@^6.3.xrestify-clients@^1.5.xsequelize@^4.31.x\

sqlite3@^3.1.x--save

Thisgetsusreadytostartcoding.We'llusethedebugmoduleforloggingmessages,js-yamltoreadtheSequelizeconfigurationfile,restifyforitsRESTframework,andsequelize/mysql/sqlite3fordatabaseaccess.

Page 428: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

UserinformationmodelWe'llbestoringtheuserinformationusingaSequelize-basedmodelinanSQLdatabase.Aswegothroughthis,ponderaquestion:shouldweintegratethedatabasecodedirectlyintotheRESTAPIimplementation?Doingsowouldreducetheuserinformationmicroservicetoonemodule,withdatabasequeriesmingledwithRESThandlers.ByseparatingtheRESTservicefromthedatastoragemodel,wehavethefreedomtoadoptotherdatastoragesystemsbesidesSequelize/SQL.Further,thedatastoragemodelcouldconceivablybeusedinwaysotherthantheRESTservice.

Createanewfilenamedusers-sequelize.mjsinusers,containingthefollowing:

importSequelizefrom"sequelize";

importjsyamlfrom'js-yaml';

importfsfrom'fs-extra';

importutilfrom'util';

importDBGfrom'debug';

constlog=DBG('users:model-users');

consterror=DBG('users:error');

varSQUser;

varsequlz;

asyncfunctionconnectDB(){

if(SQUser)returnSQUser.sync();

constyamltext=awaitfs.readFile(process.env.SEQUELIZE_CONNECT,

'utf8');

constparams=awaitjsyaml.safeLoad(yamltext,'utf8');

if(!sequlz)sequlz=newSequelize(params.dbname,params.username,

params.password,

params.params);

Page 429: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

//ThesefieldslargelycomefromthePassport/PortableContacts

schema.

//Seehttp://www.passportjs.org/docs/profile

//

//TheemailsandphotosfieldsarearraysinPortableContacts.

//We'dneedtosetupadditionaltablesforthose.

//

//ThePortableContacts"id"fieldmapstothe"username"field

here

if(!SQUser)SQUser=sequlz.define('User',{

username:{type:Sequelize.STRING,unique:true},

password:Sequelize.STRING,

provider:Sequelize.STRING,

familyName:Sequelize.STRING,

givenName:Sequelize.STRING,

middleName:Sequelize.STRING,

emails:Sequelize.STRING(2048),

photos:Sequelize.STRING(2048)

});

returnSQUser.sync();

}

AswithourSequelize-basedmodelforNotes,weuseaYAMLfiletostoreconnectionconfiguration.We'reevenusingthesameenvironmentvariable,SEQUELIZE_CONNECT.

Whatisthebeststorageserviceforuserauthenticationdata?ByusingSequelize,wehaveourpickofSQLdatabasestochoosefrom.WhileNoSQLdatabasesarealltherage,isthereanyadvantagetousingonetostoreuserauthenticationdata?Nope.AnSQLserverwilldothejobjustfine,andSequelizeallowsusthefreedomofchoice.

It'stemptingtosimplifytheoverallsystembyusingthesamedatabaseinstancetostorenotesanduserinformation,andtouseSequelizeforboth.Butwe'vechosentosimulateasecuredserverforuserdata.Thatcallsforthedatatobeinseparatedatabaseinstances,preferablyonseparateservers.Ahighlysecureapplicationdeploymentmightputtheuserinformationserviceoncompletelyseparateservers,perhapsinaphysicallyisolateddatacenter,withcarefullyconfiguredfirewalls,andtheremightevenbearmedguardsatthedoor.

Theuserprofileschemashownhereisderivedfromthenormalizedprofile

Page 430: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

providedbyPassport;refertohttp://www.passportjs.org/docs/profileformoreinformation.Passportwillharmonizeinformationgivenbythird-partyservicesintoasingleobjectdefinition.Tosimplifyourcode,we'resimplyusingtheschemadefinedbyPassport:

exportasyncfunctioncreate(username,password,provider,familyName,

givenName,middleName,emails,photos){

constSQUser=awaitconnectDB();

returnSQUser.create({

username,password,provider,

familyName,givenName,middleName,

emails:JSON.stringify(emails),photos:JSON.stringify(photos)

});

}

exportasyncfunctionupdate(username,password,provider,familyName,

givenName,middleName,emails,photos){

constuser=awaitfind(username);

returnuser?user.updateAttributes({

password,provider,

familyName,givenName,middleName,

emails:JSON.stringify(emails),

photos:JSON.stringify(photos)

}):undefined;

}

Ourcreateandupdatefunctionstakeuserinformationandeitheraddanewrecordorupdateanexistingrecord:

exportasyncfunctionfind(username){

constSQUser=awaitconnectDB();

constuser=awaitSQUser.find({where:{username:username}});

constret=user?sanitizedUser(user):undefined;

returnret;

}

Thisletsuslookupauserinformationrecord,andwereturnasanitizedversionofthatdata.

RememberthatSequelizereturnsaPromiseobject.Becausethisisexecutedinsideanasyncfunction,theawaitkeywordwillresolvethePromise,causinganyerrortobethrownorresultstobeprovidedasthereturnvalue.Inturn,asyncfunctionsreturnaPromisetothecaller.

Page 431: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Becausewe'resegregatingtheuserdatafromtherestoftheNotesapplication,wewanttoreturnasanitizedobjectratherthantheactualSQUserobject.WhatiftherewassomeinformationleakagebecausewesimplysenttheSQUserobjectbacktothecaller?ThesanitizedUserfunction,shownlater,createsananonymousobjectwithexactlythefieldswewantexposedtotheothermodules:

exportasyncfunctiondestroy(username){

constSQUser=awaitconnectDB();

constuser=awaitSQUser.find({where:{username:username}});

if(!user)thrownewError('Didnotfindrequested'+username+'to

delete');

user.destroy();

}

Thisletsussupportdeletinguserinformation.WedothisaswedidfortheNotesSequelizemodel,byfirstfindingtheuserobjectandthencallingitsdestroymethod:

exportasyncfunctionuserPasswordCheck(username,password){

constSQUser=awaitconnectDB();

constuser=awaitSQUser.find({where:{username:username}});

if(!user){

return{check:false,username:username,message:"Couldnot

finduser"};

}elseif(user.username===username&&user.password===

password){

return{check:true,username:user.username};

}else{

return{check:false,username:username,message:"Incorrect

password"};

}

}

Thisletsussupportthecheckingofuserpasswords.Thethreeconditionstohandleareasfollows:

Whetherthere'snosuchuser

Whetherthepasswordsmatched

Page 432: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Whethertheydidnotmatch

Theobjectwereturnletsthecallerdistinguishbetweenthosecases.Thecheckfieldindicateswhethertoallowthisusertobeloggedin.Ifcheckisfalse,there'ssomereasontodenytheirrequesttologin,andthemessageiswhatshouldbedisplayedtotheuser:

exportasyncfunctionfindOrCreate(profile){

constuser=awaitfind(profile.id);

if(user)returnuser;

returnawaitcreate(profile.id,profile.password,profile.provider,

profile.familyName,profile.givenName,

profile.middleName,

profile.emails,profile.photos);

}

Thiscombinestwoactionsinonefunction:first,toverifywhetherthenameduserexistsand,ifnot,tocreatethatuser.Primarily,thiswillbeusedwhileauthenticatingagainstthird-partyservices:

exportasyncfunctionlistUsers(){

constSQUser=awaitconnectDB();

constuserlist=awaitSQUser.findAll({});

returnuserlist.map(user=>sanitizedUser(user));

}

Listtheexistingusers.ThefirststepisusingfindAlltogiveusthelistoftheusersasanarrayofSQUserobjects.Thenwesanitizethatlistsowedon'texposeanydatathatwedon'twantexposed:

exportfunctionsanitizedUser(user){

varret={

id:user.username,username:user.username,

provider:user.provider,

familyName:user.familyName,givenName:user.givenName,

middleName:user.middleName,

emails:JSON.parse(user.emails),

photos:JSON.parse(user.photos)

};

try{

Page 433: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ret.emails=JSON.parse(user.emails);

}catch(e){ret.emails=[];}

try{

ret.photos=JSON.parse(user.photos);

}catch(e){ret.photos=[];}

returnret;

}

Thisisourutilityfunctiontoensureweexposeacarefullycontrolledsetofinformationtothecaller.Withthisservice,we'reemulatingasecureduserinformationservicethat'swalledofffromotherapplications.Aswesaidearlier,thisfunctionreturnsananonymoussanitizedobjectwhereweknowexactlywhat'sintheobject.

It'sveryimportanttodecodetheJSONstringweputintothedatabase.RememberthatwestoredtheemailsandphotosdatausingJSON.stringifyinthedatabase.UsingJSON.parse,wedecodethosevalues,justlikeaddinghotwatertoinstantcoffeeproducesadrinkablebeverage.

Page 434: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ARESTserverforuserinformationWearebuildingourwaytowardsintegratinguserinformationandauthenticationintotheNotesapplication.ThenextstepistowraptheuserdatamodelwejustcreatedintoaRESTserver.Afterthat,we'llcreateacoupleofscriptssothatwecanaddsomeusers,performotheradministrativetasks,andgenerallyverifythattheserviceworks.Finally,we'llextendtheNotesapplicationwithloginandlogoutsupport.

Inthepackage.jsonfile,changethemaintagtothefollowinglineofcode:

"main":"user-server.mjs",

Thencreateafilenameduser-server.mjs,containingthefollowingcode:

importrestifyfrom'restify';

importutilfrom'util';

importDBGfrom'debug';

constlog=DBG('users:service');

consterror=DBG('users:error');

import*asusersModelfrom'./users-sequelize';

varserver=restify.createServer({

name:"User-Auth-Service",

version:"0.0.1"

});

server.use(restify.plugins.authorizationParser());

server.use(check);

server.use(restify.plugins.queryParser());

server.use(restify.plugins.bodyParser({

mapParams:true

}));

Page 435: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ThecreateServermethodcantakealonglistofconfigurationoptions.Thesetwomaybeusefulforidentifyinginformation.

AswithExpressapplications,theserver.usecallsinitializewhatExpresswouldcallmiddlewarefunctions,butwhichRestifycallshandlerfunctions.ThesearecallbackfunctionswhoseAPIisfunction(req,res,next).AswithExpress,thesearetherequestandresponseobjects,andnextisafunctionwhich,whencalled,carriesexecutiontothenexthandlerfunction.

UnlikeExpress,everyhandlerfunctionmustcallthenextfunction.InordertotellRestifytostopprocessingthroughhandlers,thenextfunctionmustbecalledasnext(false).Callingnextwithanerrorobjectalsocausestheexecutiontoend,andtheerrorissentbacktotherequestor.

Thehandlerfunctionslistedheredotwothings:authorizerequestsandhandleparsingparametersfromboththeURLandthepostrequestbody.TheauthorizationParserfunctionlooksforHTTPbasicauthheaders.ThecheckfunctionisshownlaterandemulatestheideaofanAPItokentocontrolaccess.

Refertohttp://restify.com/docs/plugins-api/formoreinformationonthebuilt-inhandlersavailableinRestify.

Addthistouser-server.mjs:

//Createauserrecord

server.post('/create-user',async(req,res,next)=>{

try{

varresult=awaitusersModel.create(

req.params.username,req.params.password,

req.params.provider,

req.params.familyName,req.params.givenName,

req.params.middleName,

req.params.emails,req.params.photos);

res.send(result);

next(false);

}catch(err){res.send(500,err);next(false);}

});

Page 436: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

AsforExpress,theserver.VERBfunctionsletusdefinethehandlersforspecificHTTPactions.ThisroutehandlesaPOSTon/create-user,and,asthenameimplies,thiswillcreateauserbycallingtheusersModel.createfunction.

AsaPOSTrequest,theparametersarriveinthebodyoftherequestratherthanasURLparameters.BecauseofthemapParamsflagonthebodyParamshandler,theargumentspassedintheHTTPbodyareaddedtoreq.params.

WesimplycallusersModel.createwiththeparameterssenttous.Whencompleted,theresultobjectshouldbeauserobject,whichwesendbacktotherequestorusingres.send:

//Updateanexistinguserrecord

server.post('update-user:username',async(req,res,next)=>{

try{

varresult=awaitusersModel.update(

req.params.username,req.params.password,

req.params.provider,

req.params.familyName,req.params.givenName,

req.params.middleName,

req.params.emails,req.params.photos);

res.send(usersModel.sanitizedUser(result));

next(false);

}catch(err){res.send(500,err);next(false);}

});

The/update-userrouteishandledinasimilarway.However,wehaveputtheusernameparameterontheURL.LikeExpress,RestifyletsyouputnamedparametersintheURLlikeasfollows.Suchnamedparametersarealsoaddedtoreq.params.

WesimplycallusersModel.updatewiththeparameterssenttous.That,too,returnsanobjectwesendbacktothecallerwithres.send:

//Findauser,ifnotfoundcreateonegivenprofileinformation

server.post('/find-or-create',async(req,res,next)=>{

log('find-or-create'+util.inspect(req.params));

try{

Page 437: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

varresult=awaitusersModel.findOrCreate({

id:req.params.username,username:req.params.username,

password:req.params.password,provider:

req.params.provider,

familyName:req.params.familyName,givenName:

req.params.givenName,

middleName:req.params.middleName,

emails:req.params.emails,photos:req.params.photos

});

res.send(result);

next(false);

}catch(err){res.send(500,err);next(false);}

});

ThishandlesourfindOrCreateoperation.Wesimplydelegatethistothemodelcode,asdonepreviously.

Asthenameimplies,we'lllooktoseewhetherthenameduseralreadyexistsand,ifso,simplyreturnthatuser,otherwiseitwillbecreated:

//Findtheuserdata(doesnotreturnpassword)

server.get('find:username',async(req,res,next)=>{

try{

varuser=awaitusersModel.find(req.params.username);

if(!user){

res.send(404,newError("Didnotfind"+

req.params.username));

}else{

res.send(user);

}

next(false);

}catch(err){res.send(500,err);next(false);}

});

Here,wesupportlookinguptheuserobjectfortheprovidedusername.

Iftheuserwasnotfound,thenwereturna404statuscodebecauseitindicatesaresourcethatdoesnotexist.Otherwise,wesendtheobjectthatwasretrieved:

//Delete/destroyauserrecord

server.del('destroy:username',async(req,res,next)=>{

try{

Page 438: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

awaitusersModel.destroy(req.params.username);

res.send({});

next(false);

}catch(err){res.send(500,err);next(false);}

});

ThisishowwedeleteauserfromtheNotesapplication.TheDELHTTPverbismeanttobeusedtodeletethingsonaserver,makingitthenaturalchoiceforthisfunctionality:

//Checkpassword

server.post('/passwordCheck',async(req,res,next)=>{

try{

awaitusersModel.userPasswordCheck(

req.params.username,req.params.password);

res.send(check);

next(false);

}catch(err){res.send(500,err);next(false);}

});

Thisisanotheraspectofkeepingthepasswordsolelywithinthisserver.Thepasswordcheckisperformedbythisserver,ratherthanintheNotesapplication.WesimplycalltheusersModel.userPasswordCheckfunctionshownearlierandsendbacktheobjectitreturns:

//Listusers

server.get('/list',async(req,res,next)=>{

try{

varuserlist=awaitusersModel.listUsers();

if(!userlist)userlist=[];

res.send(userlist);

next(false);

}catch(err){res.send(500,err);next(false);}

});

Then,finally,ifrequired,wesendalistofNotesapplicationusersbacktotherequestor.Incasenolistofusersisavailable,weatleastsendanemptyarray:

server.listen(process.env.PORT,"localhost",function(){

log(server.name+'listeningat'+server.url);

Page 439: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

});

//MimicAPIKeyauthentication.

varapiKeys=[{

user:'them',

key:'D4ED43C0-8BD6-4FE2-B358-7C0E230D11EF'

}];

functioncheck(req,res,next){

if(req.authorization){

varfound=false;

for(letauthofapiKeys){

if(auth.key===req.authorization.basic.password

&&auth.user===req.authorization.basic.username){

found=true;

break;

}

}

if(found)next();

else{

res.send(401,newError("Notauthenticated"));

next(false);

}

}else{

res.send(500,newError('NoAuthorizationKey'));

next(false);

}

}

AswiththeNotesapplication,welistentotheportnamedinthePORTenvironmentvariable.Byexplicitlylisteningonlyonlocalhost,we'lllimitthescopeofsystemsthatcanaccesstheuserauthenticationserver.Inarealdeployment,wemighthavethisserverbehindafirewallwithatightlistofhostsystemsallowedtohaveaccess.

Thislastfunction,check,implementsauthenticationfortheRESTAPIitself.Thisisthehandlerfunctionweaddedearlier.

ItrequiresthecallertoprovidecredentialsontheHTTPrequestusingthebasicauthheaders.TheauthorizationParserhandlerlooksforthisandgivesittousonthereq.authorization.basicobject.Thecheckfunctionsimplyverifiesthatthenameduserandpasswordcombinationexistsinthelocalarray.

Page 440: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ThisismeanttomimicassigninganAPIkeytoanapplication.Thereareseveralwaysofdoingso;thisisjustone.

ThisapproachisnotlimitedtojustauthenticatingusingHTTPbasicauth.TheRestifyAPIletsuslookatanyheaderintheHTTPrequest,meaningwecouldimplementanykindofsecuritymechanismwelike.Thecheckfunctioncouldimplementsomeothersecuritymethod,withtherightcode.

Becauseweaddedcheckwiththeinitialsetofserver.usehandlers,itiscalledoneveryrequest.Therefore,everyrequesttothisservermustprovidetheHTTPbasicauthcredentialsrequiredbythischeck.

ThisstrategyisgoodifyouwanttocontrolaccesstoeverysinglefunctioninyourAPI.Fortheuserauthenticationservice,that'sprobablyagoodidea.SomeRESTservicesintheworldhavecertainAPIfunctionsthatareopentotheworldandothersprotectedbyanAPItoken.Toimplementthat,thecheckfunctionshouldnotbeconfiguredamongtheserver.usehandlers.Instead,itshouldbeaddedtotheappropriateroutehandlersasfollows:

server.get('requesturl',authHandler,(req,res,next)=>{

..

});

SuchanauthHandlerwouldbecodedsimilarlytoourcheckfunction.Afailuretoauthenticateisindicatedbysendinganerrorcodeandusingnext(false)toendtheroutingfunctionchain.

Wenowhavethecompletecodefortheuserauthenticationserver.ItdefinesseveralrequestURLs,andforeach,thecorrespondingfunctionintheusermodeliscalled.

NowweneedaYAMLfiletoholdthedatabasecredentials,socreatesequelize-sqlite.yaml,containingthefollowingcode:

dbname:users

username:

Page 441: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

password:

params:

dialect:sqlite

storage:users-sequelize.sqlite3

SincethisisSequelize,it'seasytoswitchtootherdatabaseenginessimplybysupplyingadifferentconfigurationfile.RememberthatthefilenameofthisconfigurationfilemustappearintheSEQUELIZE_CONNECTenvironmentvariable.

Finally,package.jsonshouldlookasfollows:

{

"name":"user-auth-server",

"version":"0.0.1",

"description":"",

"main":"user-server.js",

"scripts":{

"start":"DEBUG=users:*PORT=3333SEQUELIZE_CONNECT=sequelize-sqlite.yaml

node--experimental-modulesuser-server"

},

"author":"",

"license":"ISC",

"engines":{

"node":">=8.9"

},

"dependencies":{

"debug":"^2.6.9",

"fs-extra":"^5.x",

"js-yaml":"^3.10.x",

"mysql":"^2.15.x",

"restify":"^6.3.x",

"restify-clients":"^1.5.x",

"sqlite3":"^3.1.x",

"sequelize":"^4.31.x"

}

}

Weconfigurethisservertolistenonport3333usingthedatabasecredentialswejustgaveandwithdebuggingoutputfortheservercode.

Youcannowstarttheuserauthenticationserver:

Page 442: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

$npmstart

>[email protected]/chap08/users

>DEBUG=users:*PORT=3333SEQUELIZE_CONNECT=sequelize-mysql.yamlnodeuser-

server

users:serverUser-Auth-Servicelisteningathttp://127.0.0.1:3333+0ms

Butwedon'thaveanywaytointeractwiththisserver,yet.

Page 443: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ScriptstotestandadministertheuserauthenticationserverTogiveourselvesassurancethattheuserauthenticationserverworks,let'swriteacoupleofscriptstoexercisetheAPI.Becausewe'renotgoingtotakethetimetowriteanadministrativebackendtotheNotesapplication,thesescriptswillletusaddanddeleteuserswhoareallowedaccesstoNotes.Thesescriptswilllivewithintheuserauthenticationserverpackagedirectory.

TheRestifypackagesupportscodingRESTservers.FortheRESTclients,we'reusingacompanionlibrary,restify-clients,whichhasbeenspunoutofRestify.

Createafilenamedusers-add.js,containingthefollowingcode:

'usestrict';

constutil=require('util');

constrestify=require('restify-clients');

varclient=restify.createJsonClient({

url:'http://localhost:'+process.env.PORT,

version:'*'

});

client.basicAuth('them','D4ED43C0-8BD6-4FE2-B358-7C0E230D11EF');

client.post('/create-user',{

username:"me",password:"w0rd",provider:"local",

familyName:"Einarrsdottir",givenName:"Ashildr",middleName:"",

emails:[],photos:[]

},

(err,req,res,obj)=>{

if(err)console.error(err.stack);

elseconsole.log('Created'+util.inspect(obj));

});

Page 444: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ThisisthebasicstructureofaRestifyclient.WecreatetheClientobject–wehaveachoicebetweentheJsonClient,asusedhere,theStringClient,andtheHttpClient.TheHTTPbasicAuthcredentialsareeasytoset,asshownhere.

Thenwemaketherequest,inthiscaseaPOSTrequeston/create-user.BecauseitisaPOSTrequest,theobjectwespecifyhereisformattedbyRestifyintoHTTPPOSTbodyparameters.Aswesawearlier,theserverhasthebodyParserhandlerfunctionconfigured,whichconvertsthosebodyparametersintothereq.paramobject.

IntheRestifyclient,asfortheRestifyserver,weusethevariousHTTPmethodsbycallingclient.METHOD.BecauseitisaPOSTrequest,weuseclient.post.Whentherequestfinishes,thecallbackfunctionisinvoked.

Beforerunningthesescripts,starttheauthenticationserverinonewindowusingthefollowingcommand:

$npmstart

Nowrunthetestscriptusingthefollowingcommand:

$PORT=3333nodeusers-add.js

Created{id:1,username:'me',password:'w0rd',provider:'local',

familyName:'Einarrsdottir',givenName:'Ashildr',

middleName:'',

emails:'[]',photos:'[]',

updatedAt:'2016-02-24T02:34:41.661Z',

createdAt:'2016-02-24T02:34:41.661Z'}

Wecaninspectourhandiworkusingthefollowingcommand:

$sqlite3users-sequelize.sqlite3

SQLiteversion3.10.22016-01-2015:27:19

Enter".help"forusagehints.

sqlite>.schemausers

CREATETABLE`Users`(`id`INTEGERPRIMARYKEYAUTOINCREMENT,`username`

Page 445: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

VARCHAR(255)UNIQUE,`password`VARCHAR(255),`provider`VARCHAR(255),

`familyName`VARCHAR(255),`givenName`VARCHAR(255),`middleName`

VARCHAR(255),`emails`VARCHAR(2048),`photos`VARCHAR(2048),`createdAt`

DATETIMENOTNULL,`updatedAt`DATETIMENOTNULL,UNIQUE(`username`));

sqlite>select*fromusers;

2|me|w0rd|local|Einarrsdottir|Ashildr||[]|[]|2018-01-2105:34:56.629

+00:00|2018-01-2105:34:56.629+00:00

sqlite>^D

Nowlet'swriteascript,users-find.js,tolookupagivenuser:

'usestrict';

constutil=require('util');

constrestify=require('restify-clients');

varclient=restify.createJsonClient({

url:'http://localhost:'+process.env.PORT,

version:'*'

});

client.basicAuth('them','D4ED43C0-8BD6-4FE2-B358-7C0E230D11EF');

client.get('find'+process.argv[2],

(err,req,res,obj)=>{

if(err)console.error(err.stack);

elseconsole.log('Found'+util.inspect(obj));

});

Thissimplycallsthe/findURL,specifyingtheusernamethattheusersuppliesasacommand-lineargument.Notethatthegetoperationdoesnottakeanobjectfullofparameters.Instead,anyparameterswouldbeaddedtotheURL.

It'srunasfollows:

$PORT=3333nodeusers-find.jsme

Found{username:'me',provider:'local',

familyName:'Einarrsdottir',givenName:'Ashildr',

middleName:'',

emails:'[]',photos:'[]'}

Similarly,wecanwritescriptsagainsttheotherRESTfunctions.Butwe

Page 446: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

needtogetonwiththerealgoalofintegratingthisintotheNotesapplication.

Page 447: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

LoginsupportfortheNotesapplicationNowthatwehaveprovedthattheuserauthenticationserviceisworking,wecansetuptheNotesapplicationtosupportuserlogins.We'llbeusingPassporttosupportlogin/logout,andtheauthenticationservertostoretherequireddata.

Amongtheavailablepackages,Passportstandsoutforsimplicityandflexibility.ItintegratesdirectlywiththeExpressmiddlewarechain,andthePassportcommunityhasdevelopedhundredsofso-calledStrategymodulestohandleauthenticationagainstalonglistofthird-partyservices.Seehttp://www.passportjs.org/forinformationanddocumentation.

Page 448: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

AccessingtheuserauthenticationRESTAPIThefirststepistocreateauserdatamodelfortheNotesapplication.Ratherthanretrievingdatafromdatafilesoradatabase,itwilluseRESTtoquerytheserverwejustcreated.Wecouldhavecreatedusermodelcodethatdirectlyaccessesthedatabasebut,forreasonsalreadydiscussed,we'vedecidedtosegregateuserauthenticationintoaseparateservice.

LetusnowturntotheNotesapplication,whichyoumayhavestoredaschap08/notes.We'llbemodifyingtheapplication,firsttoaccesstheuserauthenticationRESTAPI,andthentousePassportforauthorizationandauthentication.

Forthetest/adminscriptsthatwecreatedearlier,weusedtherestify-clientsmodule.Thatpackageisacompaniontotherestifylibrary,whererestifysupportstheserversideoftheRESTprotocolandrestify-clientssupportstheclientside.Theirnamesmightgiveawaythepurpose.

Howevernicetherestify-clientslibraryis,itdoesn'tsupportaPromise-orientedAPI,asisrequiredtoplaywellwithasyncfunctions.Anotherlibrary,superagent,doessupportaPromise-orientedAPI,playswellinasyncfunctions,andthereisacompaniontothatpackage,Supertest,that'susefulinunittesting.We'lluseSupertestinChapter11,UnitTestingandFunctionalTesting,whenwetalkaboutunittesting.Fordocumentation,seehttps://www.npmjs.com/package/superagent:

$npminstallsuperagent@^3.8.x

Createanewfile,models/users-superagent.mjs,containingthefollowingcode:

Page 449: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

importrequestfrom'superagent';

importutilfrom'util';

importurlfrom'url';

constURL=url.URL;

importDBGfrom'debug';

constdebug=DBG('notes:users-superagent');

consterror=DBG('notes:error-superagent');

functionreqURL(path){

constrequrl=newURL(process.env.USER_SERVICE_URL);

requrl.pathname=path;

returnrequrl.toString();

}

ThereqURLfunctionreplacestheconnectXYZZYfunctionsthatwewroteinearliermodules.Withsuperagent,wedon'tleaveaconnectionopentotheservice,butopenanewconnectiononeachrequest.ThecommonthingtodoistoformulatetherequestURL.TheuserisexpectedtoprovideabaseURL,suchashttp://localhost:3333/,intheUSER_SERVICE_URLenvironmentvariable.ThisfunctionmodifiesthatURL,usingthenewWHATWGURLsupportinNode.js,touseagivenURLpath:

exportasyncfunctioncreate(username,password,

provider,familyName,givenName,middleName,

emails,photos){

varres=awaitrequest

.post(reqURL('/create-user'))

.send({

username,password,provider,

familyName,givenName,middleName,emails,photos

})

.set('Content-Type','application/json')

.set('Acccept','application/json')

.auth('them','D4ED43C0-8BD6-4FE2-B358-7C0E230D11EF');

returnres.body;

}

exportasyncfunctionupdate(username,password,

provider,familyName,givenName,middleName,

emails,photos){

varres=awaitrequest

.post(reqURL(`/update-user/${username}`))

.send({

username,password,provider,

familyName,givenName,middleName,emails,photos

Page 450: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

})

.set('Content-Type','application/json')

.set('Acccept','application/json')

.auth('them','D4ED43C0-8BD6-4FE2-B358-7C0E230D11EF');

returnres.body;

}

Theseareourcreateandupdatefunctions.Ineachcase,theytakethedataprovided,constructananonymousobject,andPOSTittotheserver.

ThesuperagentlibraryusesanAPIstylewhereonechainstogethermethodcallstoconstructarequest.Thechainofmethodcallscanendina.thenor.endclause,eitherofwhichtakeacallbackfunction.Butleaveoffboth,anditwillreturnaPromise.

Allthroughthislibrary,we'llusethe.authclausetosetuptherequiredauthenticationkey.

Theseanonymousobjectsarealittledifferentthanusual.We'reusinganewES-2015featureherethatwehaven'tdiscussedsofar.RatherthanspecifyingtheobjectfieldsusingthefieldName:fieldValuenotation,ES-2015givesustheoptiontoshortenthiswhenthevariablenameusedforfieldValuematchesthedesiredfieldName.Inotherwords,wecanjustlistthevariablenames,andthefieldnamewillautomaticallymatchthevariablename.

Inthiscase,we'vepurposelychosenvariablenamesfortheparameterstomatchfieldnamesoftheobjectwithparameternamesusedbytheserver.Bydoingso,wecanusethisshortenednotationforanonymousobjects,andourcodeisalittlecleanerbyusingconsistentvariablenamesfrombeginningtoend:

exportasyncfunctionfind(username){

varres=awaitrequest

.get(reqURL(`/find/${username}`))

.set('Content-Type','application/json')

.set('Acccept','application/json')

.auth('them','D4ED43C0-8BD6-4FE2-B358-7C0E230D11EF');

returnres.body;

}

Ourfindoperationletsuslookupuserinformation:

Page 451: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

exportasyncfunctionuserPasswordCheck(username,password){

varres=awaitrequest

.post(reqURL(`/passwordCheck`))

.send({username,password})

.set('Content-Type','application/json')

.set('Acccept','application/json')

.auth('them','D4ED43C0-8BD6-4FE2-B358-7C0E230D11EF');

returnres.body;

}

We'resendingtherequesttocheckpasswordstotheserver.

Apointaboutthismethodisusefultonote.ItcouldhavetakentheparametersintheURL,insteadoftherequestbodyasisdonehere.ButsincerequestURLareroutinelyloggedtofiles,puttingtheusernameandpasswordparametersintheURLmeansuseridentityinformationwouldbeloggedtofilesandpartofactivityreports.Thatwouldobviouslybeaverybadchoice.Puttingthoseparametersintherequestbodynotonlyavoidsthatbadresult,butifanHTTPSconnectiontotheservicewereused,thetransactionwouldbeencrypted:

exportasyncfunctionfindOrCreate(profile){

varres=awaitrequest

.post(reqURL('/find-or-create'))

.send({

username:profile.id,password:profile.password,

provider:profile.provider,

familyName:profile.familyName,

givenName:profile.givenName,

middleName:profile.middleName,

emails:profile.emails,photos:profile.photos

})

.set('Content-Type','application/json')

.set('Acccept','application/json')

.auth('them','D4ED43C0-8BD6-4FE2-B358-7C0E230D11EF');

returnres.body;

}

ThefindOrCreatefunctioneitherdiscoverstheuserinthedatabase,orcreatesanewuser.TheprofileobjectwillcomefromPassport,buttakecarefulnoteofwhatwedowithprofile.id.ThePassportdocumentationsaysitwillprovidetheusernameintheprofile.idfield.Butwewantto

Page 452: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

storeitasusername,instead:

exportasyncfunctionlistUsers(){

varres=awaitrequest

.get(reqURL('/list'))

.set('Content-Type','application/json')

.set('Acccept','application/json')

.auth('them','D4ED43C0-8BD6-4FE2-B358-7C0E230D11EF');

returnres.body;

}

Finally,wecanretrievealistofusers.

Page 453: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

LoginandlogoutroutingfunctionsWhatwe'vebuiltsofarisauserdatamodel,withaRESTAPIwrappingthatmodeltocreateourauthenticationinformationservice.Then,withintheNotesapplication,wehaveamodulethatrequestsuserdatafromthisserver.Asofyet,nothingintheNotesapplicationknowsthatthisusermodelexists.Thenextstepistocreatearoutingmoduleforlogin/logoutURLsandtochangetherestofNotestouseuserdata.

Theroutingmoduleiswhereweusepassporttohandleuserauthentication.Thefirsttaskistoinstalltherequiredmodules:

$npminstallpassport@^[email protected]

Thepassportmodulegivesustheauthenticationalgorithms.Tosupportdifferentauthenticationmechanisms,thepassportauthorshavedevelopedseveralstrategyimplementations.Theauthenticationmechanisms,orstrategies,correspondtothevariousthird-partyservicesthatsupportauthentication,suchasusingOAuth2toauthenticateagainstservicessuchasFacebook,Twitter,orGitHub.

TheLocalStrategyauthenticatessolelyusingdatastoredlocaltotheapplication,forexample,ouruserauthenticationinformationservice.

Let'sstartbycreatingtheroutingmodule,routes/users.mjs:

importpathfrom'path';

importutilfrom'util';

importexpressfrom'express';

importpassportfrom'passport';

importpassportLocalfrom'passport-local';

Page 454: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

constLocalStrategy=passportLocal.Strategy;

import*asusersModelfrom'../models/users-superagent';

import{sessionCookieName}from'../app';

exportconstrouter=express.Router();

importDBGfrom'debug';

constdebug=DBG('notes:router-users');

consterror=DBG('notes:error-users');

Thisbringsinthemodulesweneedforthe/usersrouter.ThisincludesthetwopassportmodulesandtheREST-baseduserauthenticationmodel.

Inapp.mjs,wewillbeaddingsessionsupportsoouruserscanloginandlogout.Thatreliesonstoringacookieinthebrowser,andthecookienameisfoundinthisvariableexportedfromapp.mjs.We'llbeusingthatcookieinamoment:

exportfunctioninitPassport(app){

app.use(passport.initialize());

app.use(passport.session());

}

exportfunctionensureAuthenticated(req,res,next){

try{

//req.userissetbyPassportinthedeserializefunction

if(req.user)next();

elseres.redirect('userslogin');

}catch(e){next(e);}

}

TheinitPassportfunctionwillbecalledfromapp.mjs,anditinstallsthePassportmiddlewareintotheExpressconfiguration.We'lldiscusstheimplicationsofthislaterwhenwegettoapp.mjschanges,butPassportusessessionstodetectwhetherthisHTTPrequestisauthenticatedornot.Itlooksateveryrequestcomingintotheapplication,looksforcluesaboutwhetherthisbrowserisloggedinornot,andattachesdatatotherequestobjectasreq.user.

TheensureAuthenticatedfunctionwillbeusedbyotherroutingmodulesandistobeinsertedintoanyroutedefinitionthatrequiresanauthenticated

Page 455: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

logged-inuser.Forexample,editingordeletinganoterequirestheusertobeloggedin,andthereforethecorrespondingroutesinroutes/notes.mjsmustuseensureAuthenticated.Iftheuserisnotloggedin,thisfunctionredirectsthemtousersloginsothattheycandoso:

outer.get('/login',function(req,res,next){

try{

res.render('login',{title:"LogintoNotes",user:req.user,});

}catch(e){next(e);}

});

router.post('/login',

passport.authenticate('local',{

successRedirect:'',/SUCCESS:Gotohomepage

failureRedirect:'login',//FAIL:Gotouserlogin

})

);

Becausethisrouterismountedon/users,alltheserouteswillhave/userprepended.Theusersloginroutesimplyshowsaformrequestingausernameandpassword.Whenthisformissubmitted,welandinthesecondroutedeclaration,withaPOSTonuserslogin.IfpassportdeemsthisasuccessfulloginattemptusingLocalStrategy,thenthebrowserisredirectedtothehomepage.Otherwise,itisredirectedtotheusersloginpage:

router.get('/logout',function(req,res,next){

try{

req.session.destroy();

req.logout();

res.clearCookie(sessionCookieName);

res.redirect('/');

}catch(e){next(e);}

});

WhentheuserrequeststologoutofNotes,theyaretobesenttouserslogout.We'llbeaddingabuttontotheheadertemplateforthispurpose.Thereq.logoutfunctioninstructsPassporttoerasetheirlogincredentials,andtheyarethenredirectedtothehomepage.

Thisfunctiondeviatesfromwhat'sinthePassportdocumentation.There,

Page 456: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

wearetoldtosimplycallreq.logout.Butcallingonlythatfunctionsometimesresultsintheusernotbeingloggedout.It'snecessarytodestroythesessionobject,andtoclearthecookie,inordertoensurethattheuserisloggedout.Thecookienameisdefinedinapp.mjs,andweimportedsessionCookieNameforthisfunction:

passport.use(newLocalStrategy(

async(username,password,done)=>{

try{

varcheck=awaitusersModel.userPasswordCheck(username,

password);

if(check.check){

done(null,{id:check.username,username:check.username});

}else{

done(null,false,check.message);

}

}catch(e){done(e);}

}

));

HereiswherewedefineourimplementationofLocalStrategy.Inthecallbackfunction,wecallusersModel.userPasswordCheck,whichmakesaRESTcalltotheuserauthenticationservice.Rememberthatthisperformsthepasswordcheckandthenreturnsanobjectindicatingwhetherthey'reloggedinornot.

Asuccessfulloginisindicatedwhencheck.checkistrue.Forthiscase,wetellPassporttouseanobjectcontainingtheusernameinthesessionobject.Otherwise,wehavetwowaystotellPassportthattheloginattemptwasunsuccessful.Inonecase,weusedone(null,false)toindicateanerrorloggingin,andpassalongtheerrormessageweweregiven.Intheothercase,we'llhavecapturedanexception,andpassalongthatexception.

You'llnoticethatPassportusesacallback-styleAPI.Passportprovidesadonefunction,andwearetocallthatfunctionwhenweknowwhat'swhat.Whileweuseanasyncfunctiontomakeacleanasynchronouscalltothebackendservice,Passportdoesn'tknowhowtogrokthePromisethatwouldbereturned.Therefore,wehavetothrowatry/catcharoundthefunctionbodytocatchanythrownexception:

Page 457: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

passport.serializeUser(function(user,done){

try{

done(null,user.username);

}catch(e){done(e);}

});

passport.deserializeUser(async(username,done)=>{

try{

varuser=awaitusersModel.find(username);

done(null,user);

}catch(e){done(e);}

});

Theprecedingfunctionstakecareofencodinganddecodingauthenticationdataforthesession.Allweneedtoattachtothesessionistheusername,aswedidinserializeUser.ThedeserializeUserobjectiscalledwhileprocessinganincomingHTTPrequestandiswherewelookuptheuserprofiledata.Passportwillattachthistotherequestobject.

Page 458: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Login/logoutchangestoapp.jsWehaveafewchangesrequiredinapp.mjs,someofwhichwe'vealreadytouchedon.WedidcarefullyisolatethePassportmoduledependenciestoroutes/users.mjs.Thechangesrequiredinapp.mjssupportthecodeinroutes/users.mjs.

It'snowtimetouncommentalinewetoldyoutocommentoutwaybackinChapter5,YourFirstExpressApplication.Theimportsfortheroutingmoduleswillnowlookasfollows:

import{routerasindex}from'./routes/index';

import{routerasusers,initPassport}from'./routes/users';

import{routerasnotes}from'./routes/notes';

TheUserroutersupportsthe/loginand/logoutURL'saswellasusingPassportforauthentication.WeneedtocallinitPassportforalittlebitofinitialization:

importsessionfrom'express-session';

importsessionFileStorefrom'session-file-store';

constFileStore=sessionFileStore(session);

exportconstsessionCookieName='notescookie.sid';

BecausePassportusessessions,weneedtoenablesessionsupportinExpress,andthesemodulesdoso.Thesession-file-storemodulesavesoursessiondatatodisksothatwecankillandrestarttheapplicationwithoutlosingsessions.It'salsopossibletosavesessionstodatabaseswithappropriatemodules.AfilesystemsessionstoreissuitableonlywhenallNotesinstancesarerunningonthesameservercomputer.Foradistributeddeploymentsituation,you'llneedtouseasessionstorethatrunsonanetwork-wideservice,suchasadatabase.

Page 459: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

We'redefiningsessionCookieNameheresoitcanbeusedinmultipleplaces.Bydefault,express-sessionusesacookienamedconnect.sidtostorethesessiondata.Asasmallmeasureofsecurity,it'susefulwhenthere'sapublisheddefaulttouseadifferentnon-defaultvalue.Anytimeweusethedefaultvalue,it'spossiblethatanattackermightknowasecurityflawdependingonthatdefault.

Usethefollowingcommandtoinstallthemodules:

[email protected]@1.2.x--save

ExpressSessionsupport,includingallthevariousSessionStoreimplementations,isdocumentedonitsGitHubprojectpageathttps://github.com/expressjs/session.

Addthisinapp.mjs:

app.use(session({

store:newFileStore({path:"sessions"}),

secret:'keyboardmouse',

resave:true,

saveUninitialized:true,

name:sessionCookieName

}));

initPassport(app);

Hereweinitializethesessionsupport.ThefieldnamedsecretisusedtosignthesessionIDcookie.Thesessioncookieisanencodedstringthatisencryptedinpartusingthissecret.IntheExpressSessiondocumentation,theysuggestthestringkeyboardcatforthesecret.But,intheory,whatifExpresshasavulnerability,suchthatknowingthissecretcanmakeiteasiertobreakthesessionlogiconyoursite?Hence,wechoseadifferentstringforthesecretjusttobealittledifferentandperhapsalittlemoresecure.

Similarly,thedefaultcookienameusedbyexpress-sessionisconnect.sid.Here'swherewechangethecookienametoanon-defaultname.

Page 460: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TheFileStorewillstoreitssessiondatarecordsinadirectorynamedsessions.Thisdirectorywillbeauto-createdasneeded:

app.use('',index);

app.use('users',users);

app.use('/notes',notes);

TheprecedingarethethreeroutersusedintheNotesapplication.

Page 461: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Login/logoutchangesinroutes/index.mjsThisroutermodulehandlesthehomepage.Itdoesnotrequiretheusertobeloggedin,butwewanttochangethedisplayalittleiftheyareloggedin:

router.get('/',async(req,res,next)=>{

try{

letkeylist=awaitnotes.keylist();

letkeyPromises=keylist.map(key=>{returnnotes.read(key)});

letnotelist=awaitPromise.all(keyPromises);

res.render('index',{

title:'Notes',notelist:notelist,

user:req.user?req.user:undefined

});

}catch(e){next(e);}

});

Rememberthatweensuredthatreq.userhastheuserprofiledata,whichwedidindeserializeUser.Wesimplycheckforthisandmakesuretoaddthatdatawhenrenderingtheviewstemplate.

We'llbemakingsimilarchangestomostoftheotherroutedefinitions.Afterthat,we'llgooverthechangestotheviewtemplatesinwhichweusereq.usertoshowthecorrectbuttonsoneachpage.

Page 462: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Login/logoutchangesrequiredinroutes/notes.mjsThechangesrequiredherearemoresignificant,butstillstraightforward:

import{ensureAuthenticated}from'./users';

WeneedtousetheensureAuthenticatedfunctiontoprotectcertainroutesfrombeingusedbyuserswhoarenotloggedin.NoticehowES6modulesletusimportjustthefunction(s)werequire.Sincethatfunctionisintheuserroutermodule,weneedtoimportitfromthere:

router.get('/add',ensureAuthenticated,(req,res,next)=>{

try{

res.render('noteedit',{

title:"AddaNote",

docreate:true,notekey:"",

user:req.user,note:undefined

});

}catch(e){next(e);}

});

ThefirstthingweaddedistocallusersRouter.ensureAuthenticatedintheroutedefinition.Iftheuserisnotloggedin,they'llredirectto/users/login,thankstothatfunction.

Becausewe'veensuredthattheuserisauthenticated,weknowthatreq.userwillalreadyhavetheirprofileinformation.Wecanthensimplypassittotheviewtemplate.

Fortheotherroutes,weneedtomakesimilarchanges:

router.post('/save',ensureAuthenticated,(req,res,next)=>{

Page 463: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

..

});

The/saverouterequiresonlythischangetocallensureAuthenticatedtomakesurethattheuserisloggedin:

router.get('/view',(req,res,next)=>{

try{

varnote=awaitnotes.read(req.query.key);

res.render('noteview',{

title:note?note.title:"",

notekey:req.query.key,

user:req.user?req.user:undefined,

note:note

});

}catch(e){next(e);}

});

Forthisroute,wedon'trequiretheusertobeloggedin.Wedoneedtheuser'sprofileinformation,ifany,senttotheviewtemplate:

router.get('/edit',ensureAuthenticated,(req,res,next)=>{

try{

varnote=awaitnotes.read(req.query.key);

res.render('noteedit',{

title:note?("Edit"+note.title):"AddaNote",

docreate:false,

notekey:req.query.key,

user:req.user?req.user:undefined,

note:note

});

}catch(e){next(e);}

});

router.get('/destroy',ensureAuthenticated,(req,res,next)=>{

try{

varnote=awaitnotes.read(req.query.key);

res.render('notedestroy',{

title:note?`Delete${note.title}`:"",

notekey:req.query.key,

user:req.user?req.user:undefined,

note:note

});

}catch(e){next(e);}

});

router.post('/destroy/confirm',ensureAuthenticated,(req,res,next)=>{

Page 464: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

..

});

Fortheseroutes,werequiretheusertobeloggedin.Inmostcases,weneedtosendthereq.uservaluetotheviewtemplate.

Page 465: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Viewtemplatechangessupportinglogin/logoutSofar,we'vecreatedabackenduserauthenticationservice,aRESTmodule,toaccessthatservice,aroutermoduletohandleroutesrelatedtologginginandoutofthewebsite,andchangesinapp.mjstousethosemodules.We'realmostready,butwe'vegotanumberofoutstandingchangestomakeinthetemplates.We'repassingthereq.userobjecttoeverytemplatebecauseeachonemustbechangedtoaccommodatewhethertheuserisloggedinornot.

Inpartials/header.hbs,makethefollowingadditions:

...

{{#ifuser}}

<divclass="collapsenavbar-collapse"

id="navbarSupportedContent">

<spanclass="navbar-texttext-darkcol">{{title}}</span>

<aclass="btnbtn-darkcol-auto"href="userslogout">

LogOut<spanclass="badgebadge-light">{{user.username}}

</span></a>

<aclass="nav-itemnav-linkbtnbtn-darkcol-auto"

href='notesadd'>

ADDNote</a>

</div>

{{else}}

<divclass="collapsenavbar-collapse"id="navbarLogIn">

<aclass="btnbtn-primary"href="userslogin">Login</a>

</div>

{{/if}}

...

Whatwe'redoinghereiscontrollingwhichbuttonstodisplayatthetopofthescreendependingonwhethertheuserisloggedinornot.Theearlierchangesensurethattheuservariablewillbeundefinediftheuserisloggedout,otherwiseitwillhavetheuserprofileobject.Therefore,it'ssufficient

Page 466: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

tochecktheuservariableasshownheretorenderdifferentuserinterfaceelements.

Alogged-outuserdoesn'tgettheADDNotebutton,andgetsaLoginbutton.Otherwise,theusergetsanADDNotebuttonandaLogOutbutton.TheLoginbuttontakestheusertouserslogin,whiletheLogOutbuttontakesthemtouserslogout.Bothofthosearehandledinroutes/users.js,andperformtheexpectedfunction.

TheLogOutbuttonhasaBootstrapbadgecomponentdisplayingtheusername.Thisaddsalittlevisualsplotch,inwhichwe'llputtheusernamethat'sloggedin.Aswe'llseelater,itwillserveasavisualcuetotheuserastotheiridentity.

Weneedtocreateviews/login.hbs:

<divclass="container-fluid">

<divclass="row">

<divclass="col-12btn-group-vertical"role="group">

<formmethod='POST'action='userslogin'>

<divclass="form-group">

<labelfor="username">Username:</label>

<inputclass="form-control"type='text'id='username'

name='username'value=''placeholder='UserName'/>

</div>

<divclass="form-group">

<labelfor="password">Password:</label>

<inputclass="form-control"type='password'id='password'

name='password'value=''placeholder='Password'/>

</div>

<buttontype="submit"class="btnbtn-default">Submit</button>

</form>

</div>

</div>

</div>

ThisisasimpleformdecoratedwithBootstrapgoodnesstoaskfortheusernameandpassword.Whensubmitted,itcreatesaPOSTrequesttouserslogin,whichinvokesthedesiredhandlertoverifytheloginrequest.

Page 467: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ThehandlerforthatURLwillstartthePassport'sprocesstodecidewhethertheuserisauthenticatedornot.

Inviews/notedestroy.hbs,wewanttodisplayamessageiftheuserisnotloggedin.Normally,theformtocausethenotetobedeletedisdisplayed,butiftheuserisnotloggedin,wewanttoexplainthesituation:

<formmethod='POST'action='notesdestroy/confirm'>

<divclass="container-fluid">

{{#ifuser}}

<inputtype='hidden'name='notekey'value='{{#ifnote}}{{notekey}}

{{/if}}'>

<pclass="form-text">Delete{{note.title}}?</p>

<divclass="btn-group">

<buttontype="submit"value='DELETE'

class="btnbtn-outline-dark">DELETE</button>

<aclass="btnbtn-outline-dark"

href="notesview?key={{#ifnote}}{{notekey}}{{/if}}"

role="button">Cancel</a>

</div>

{{else}}

{{>not-logged-in}}

{{/if}}

</div>

</form>

That'sstraightforward;iftheuserisloggedin,displaytheform,otherwisedisplaythemessageinpartials/not-logged-in.hbs.Wedetermineourapproachbasedontheuservariable.

Wecouldputsomethinglikethisinpartials/not-logged-in.hbs:

<divclass="jumbotron">

<h1>NotLoggedIn</h1>

<p>Youarerequiredtobeloggedinforthisaction,butyouarenot.

Youshouldnotseethismessage.It'sabugifthismessageappears.

</p>

<p><aclass="btnbtn-primary"href="userslogin">Login</a></p>

</div>

Inviews/noteedit.hbs,weneedasimilarchange:

Page 468: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

..

<divclass="row"><divclass="col-xs-12">

{{#ifuser}}

..

{{else}}

{{>not-logged-in}}

{{/if}}

</div></div>

..

Thatis,atthebottomweaddasegmentthat,fornon-logged-inusers,pullsinthenot-logged-inpartial.

TheBootstrapjumbotroncomponentmakesaniceandlargetextdisplaythatstandsoutnicely,andwillcatchtheviewer'sattention.However,theusershouldneverseethisbecauseeachofthosetemplatesisusedonlywhenwe'vepreverifiedthattheuserisloggedin.

Amessagesuchasthisisusefulasacheckagainstbugsinyourcode.Supposethatweslippedupandfailedtoproperlyensurethattheseformsweredisplayedonlytologged-inusers.Supposethatwehadotherbugsthatdidn'tchecktheformsubmissiontoensureit'srequestedonlybyalogged-inuser.Fixingthetemplateinthiswayisanotherlayerofpreventionagainstdisplayingformstouserswhoarenotallowedtousethatfunctionality.

Page 469: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

RunningtheNotesapplicationwithuserauthenticationNowwe'rereadytoruntheNotesapplicationandtryourhandatlogginginandout.

Weneedtochangethescriptssectionofpackage.jsonasfollows:

"scripts":{

"start":"DEBUG=notes:*SEQUELIZE_CONNECT=models/sequelize-sqlite.yaml

NOTES_MODEL=sequelizeUSER_SERVICE_URL=http://localhost:3333node--

experimental-modules./bin/www.mjs",

"dl-minty":"mkdir-pminty&&npmrundl-minty-css&&npmrundl-

minty-min-css",

"dl-minty-css":"wgethttps://bootswatch.com/4/minty/bootstrap.css

-Ominty/bootstrap.css",

"dl-minty-min-css":"wget

https://bootswatch.com/4/minty/bootstrap.min.css-Ominty/bootstrap.min.css"

},

Inthepreviouschapters,webuiltupquiteafewcombinationsofmodelsanddatabasesforrunningtheNotesapplication.Thisleavesuswithone,configuredtousetheSequelizemodelforNotes,usingtheSQLite3database,andtousethenewuserauthenticationservicethatwewroteearlier.Wecansimplifythescriptssectionbydeletingthoseotherconfigurations.AlltheotherNotesdatamodelsarestillavailablejustbysettingtheenvironmentvariablesappropriately.

TheUSER_SERVICE_URLneedstomatchtheportnumberthatwedesignatedforthatservice.

Inonewindow,starttheuserauthenticationserviceasfollows:

$cdusers

Page 470: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

$npmstart

>[email protected]/chap08/users

>DEBUG=users:*PORT=3333SEQUELIZE_CONNECT=sequelize-sqlite.yamlnodeuser-

server

users:serverUser-Auth-Servicelisteningathttp://127.0.0.1:3333

+0ms

Then,inanotherwindow,starttheNotesapplication:

$cdnotes

$DEBUG=notes:*npmstart

>[email protected]/chap08/notes

>SEQUELIZE_CONNECT=models/sequelize-sqlite.yamlNOTES_MODEL=models/notes-

sequelizeUSERS_MODEL=models/users-rest

USER_SERVICE_URL=http://localhost:3333node./bin/www

notes:serverListeningonport3000+0ms

You'llbegreetedwiththefollowing:

Noticethenewbutton,Login,andthelackofanADDNotebutton.We'renotloggedin,andthereforepartials/header.hbsisriggedtoshowonlytheLoginbutton.

ClickontheLoginbutton,andyouwillseetheloginscreen:

Page 471: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Thisisourloginformfromviews/login.hbs.Youcannowlogin,createanoteorthree,andyoumightendupwiththefollowingonthehomepage:

YounowhavebothLogOutandADDNotebuttons.

You'llnoticethattheLogOutbuttonhastheusername(me)shown.Aftersomethoughtandconsideration,thisseemedthemostcompactwaytoshowwhethertheuserisloggedinornot,andwhichuserisloggedin.

Page 472: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Thismightdrivetheuserexperienceteamnuts,andyouwon'tknowwhetherthisuserinterfacesdesignworksuntilit'stestedwithusers,butit'sgoodenoughforourpurposeatthemoment.

Page 473: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TwitterloginsupportfortheNotesapplicationIfyouwantyourapplicationtohitthebigtime,it'sagreatideatoallowuserstoregisterusingthird-partycredentials.WebsitesallovertheinternetallowyoutologinusingFacebook,Twitter,oraccountsfromotherservices.Doingsoremoveshurdlestoprospectiveuserssigningupforyourservice.Passportmakesitextremelyeasytodothis.

SupportingTwitterrequiresinstallingTwitterStrategy,registeringanewapplicationwithTwitter,andaddingacoupleofroutesintoroutes/user.mjsandasmallchangeinpartials/header.hbs.Integratingotherthird-partyservicesrequiressimilarsteps.

Page 474: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

RegisteringanapplicationwithTwitterTwitter,aswitheveryotherthird-partyservice,usesOAuthtohandleauthenticationandrequiresanauthenticationkeytowritesoftwareusingtheirAPI.It'stheirservice,soyouhavetoplaybytheirrules,ofcourse.

ToregisteranewapplicationwithTwitter,gotohttps://apps.twitter.com/.ThenyouclickontheCreateNewAppbutton.Sincewehaven'tdeployedtheNotesapplicationtoaregularserverand,moreimportantly,thereisn'tavaliddomainnamefortheapplication,wehavetogiveTwittertheconfigurationrequiredfortestingonourlocallaptop.

EveryserviceofferingOAuth2authenticationhasanadministrativebackendforregisteringnewapplications.Thecommonpurposeistodescribetheapplicationtotheservicesothattheservicecancorrectlyrecognizetheapplicationwhenrequestsaremadeusingtheauthenticationtokens.Thenormalsituationisthattheapplicationisdeployedtoaregularserver,andisaccessedthroughadomainnamesuchasMyNotes.info.We'vedoneneitherasofthismoment.

Atthetimeofwriting,therearefourpiecesofinformationrequestedbytheTwittersign-upprocess:

Name:Thisistheapplicationname,anditcanbeanythingyoulike.ItwouldbegoodformtousetestinthenameincaseTwitter'sstaffdecidetodosomevalidation.

Description:Descriptivephrase,andagainitcanbeanythingyoulike.Again,itwouldbegoodformto,atthistime,describeitasa

Page 475: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

testapplication.

Website:Thiswouldbeyourdesireddomainname.Here,thehelptexthelpfullysuggestsIfyoudon'thaveaURLyet,justputaplaceholderherebutremembertochangeitlater.

CallbackURL:ThisistheURLtoreturntoaftersuccessfulauthentication.Sincewedon'thaveapublicURLtosupply,thisiswherewespecifyavaluereferringtoyourlaptop.It'sbeenfoundthathttp://localhost:3000worksjustfine.macOSusershaveanotheroptionbecauseofthe.localdomainname,whichisautomaticallyassignedtotheirlaptop.Allalong,wecouldhaveusedaURLsimilartothistoaccesstheNotesapplicationathttp://MacBook-Pro-2.local:3000/.

ItwasfoundbyattemptingthisprocedurewithdifferentservicesthatFacebook(andother)servicesarenotlenientabouttestapplicationshostedonlaptops.AtleastTwitteriskeenfordeveloperstoconfigureatestapplicationontheirlaptop.Passport'sotherOAuth-basedstrategieswillworksimilarlyenoughtoTwitter,sotheknowledgewe'regainingwilltransfertothoseotherauthenticationstrategies.

Thelastthingtonoticeistheextremelysensitivenatureoftheauthenticationkeys.It'sbadformtochecktheseintoasourcecoderepositoryorotherwiseputtheminaplacewhereanybodycanaccessthekey.We'lltacklethisissueinChapter12,Security.

Twitterdoeschangethesignuppagefromtimetotime,butitshouldlooksomethinglikethefollowing:

Page 476: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you
Page 477: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ImplementingTwitterStrategyAswithmanywebapplications,wehavedecidedtoallowouruserstologinusingTwittercredentials.TheOAuth2protocoliswidelyusedforthispurposeandisthebasisforauthenticatingononewebsiteusingcredentialsmaintainedbyanotherwebsite.

Theapplicationregistrationprocessyoujustfollowedatapps.twitter.comgeneratedforyouapairofAPIkeys,aconsumerkey,and,consumersecret.ThesekeysarepartoftheOAuthprotocol,andwillbesuppliedbyanyOAuthserviceyouregisterwith,andthekeysshouldbetreatedwiththeutmostcare.ThinkofthemastheusernameandpasswordyourserviceusestoaccesstheOAuth-basedservice(Twitteretal).Themorepeoplewhocanseethesekeys,themorelikelyamiscreantcanseethemandthencausetrouble.AnybodywiththosesecretscanwriteaccesstheserviceAPIasiftheyareyou.

DozensofStrategypackagesforvariousthird-partyservicesareavailablewithinthePassportecosystem.Let'sinstallthepackagerequiredtouseTwitterStrategy:

[email protected]

Inroutes/users.mjs,let'sstartmakingsomechanges:

importpassportTwitterfrom'passport-twitter';

constTwitterStrategy=passportTwitter.Strategy;

Tobringinthepackagewejustinstalled,addthefollowing:

consttwittercallback=process.env.TWITTER_CALLBACK_HOST

?process.env.TWITTER_CALLBACK_HOST

Page 478: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

:"http://localhost:3000";

passport.use(newTwitterStrategy({

consumerKey:process.env.TWITTER_CONSUMER_KEY,

consumerSecret:process.env.TWITTER_CONSUMER_SECRET,

callbackURL:`${twittercallback}/usersauthtwitter/callback`

},

asyncfunction(token,tokenSecret,profile,done){

try{

done(null,awaitusersModel.findOrCreate({

id:profile.username,username:profile.username,password:"",

provider:profile.provider,familyName:profile.displayName,

givenName:"",middleName:"",

photos:profile.photos,emails:profile.emails

}));

}catch(err){done(err);}

}));

ThisregistersTwitterStrategywithpassport,arrangingtocalltheuserauthenticationserviceasusersregisterwiththeNotesapplication.ThiscallbackfunctioniscalledwhenuserssuccessfullyauthenticateusingTwitter.

WedefinedtheusersModel.findOrCreatefunctionspecificallytohandleuserregistrationfromthird-partyservicessuchasTwitter.Itstaskistolookfortheuserdescribedintheprofileobjectand,ifthatuserdoesnotexist,toautocreatethatuseraccountinNotes.

TheconsumerKeyandconsumerSecretvaluesaresuppliedbyTwitter,afteryou'veregisteredyourapplication.ThesesecretsareusedintheOAuthprotocolasproofofidentitytoTwitter.

ThecallbackURLsettingintheTwitterStrategyconfigurationisaholdoverfromTwitter'sOAuth1-basedAPIimplementation.InOAuth1,thecallbackURLwaspassedaspartoftheOAuthrequest.SinceTwitterStrategyusesTwitter'sOAuth1service,wehavetosupplytheURLhere.We'llseeinamomentwherethatURLisimplementedinNotes.

ThecallbackURL,consumerKey,andconsumerSecretareallinjectedusingenvironmentvariables.Itistempting,becauseoftheconvenience,tojustputthosekeysinthesourcecode.But,howwidelydistributedisyour

Page 479: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

sourcecode?IntheSlackAPIdocumentation(https://api.slack.com/docs/oauth-safety),we'rewarnedDonotdistributeclientsecretsinemail,distributednativeapplications,client-sideJavaScript,orpubliccoderepositories.

InChapter10,DeployingNode.jsApplications,we'llputthesekeysintoaDockerfile.That'snotentirelysecurebecausetheDockerfilewillalsobecommittedtoasourcerepositorysomewhere.

ItwasfoundwhiledebuggingthattheprofileobjectsuppliedbytheTwitterStrategydidnotmatchthedocumentationonthepassportwebsite.Therefore,wehavemappedtheobjectactuallysuppliedbypassportintosomethingthatNotescanuse:

router.get('authtwitter',passport.authenticate('twitter'));

TostarttheuserlogginginwithTwitter,we'llsendthemtothisURL.RememberthatthisURLisreally/usersauthtwitter,and,inthetemplates,we'llhavetousethatURL.Whenthisiscalled,thepassportmiddlewarestartstheuserauthenticationandregistrationprocessusingTwitterStrategy.

Oncetheuser'sbrowservisitsthisURL,theOAuthdancebegins.It'scalledadancebecausetheOAuthprotocolinvolvescarefullydesignedredirectsbetweenseveralwebsites.PassportsendsthebrowserovertothecorrectURLatTwitter,whereTwitteraskstheuserwhethertheyagreetoauthenticateusingTwitter,andthenTwitterredirectstheuserbacktoyourcallbackURL.Alongtheway,specifictokensarepassedbackandforthinaverycarefullydesigneddancebetweenwebsites.

OncetheOAuthdanceconcludes,thebrowserlandshere:

router.get('authtwitter/callback',

passport.authenticate('twitter',{successRedirect:'/',

failureRedirect:'userslogin'}));

ThisroutehandlesthecallbackURL,anditcorrespondstothecallbackURL

Page 480: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

settingconfiguredearlier.Dependingonwhetheritindicatesasuccessfulregistrationornot,passportwillredirectthebrowsertoeitherthehomepageorbacktotheusersloginpage.

Becauserouterismountedon/user,thisURLisactually/userauthtwitter/callback.Therefore,thefullURLtouseinconfiguringtheTwitterStrategy,andtosupplytoTwitter,ishttp://localhost:3000/userauthtwitter/callback

IntheprocessofhandlingthecallbackURL,Passportwillinvokethecallbackfunctionshownearlier.BecauseourcallbackusestheusersModel.findOrCreatefunction,theuserwillbeautomaticallyregisteredifnecessary.

We'realmostready,butweneedtomakeacoupleofsmallchangeselsewhereinNotes.

Inpartials/header.hbs,makethefollowingchangestothecode:

...

{{else}}

<divclass="collapsenavbar-collapse"id="navbarLogIn">

<spanclass="navbar-texttext-darkcol"></span>

<aclass="nav-itemnav-linkbtnbtn-darkcol-auto"href="userslogin">

Login</a>

<aclass="nav-itemnav-linkbtnbtn-darkcol-auto"

href="/usersauthtwitter">

<imgwidth="15px"

src="assetsvendor/twitter/Twitter_Social_Icon_Rounded_Square_Color.png"/>

LoginwithTwitter</a>

</div>

{{/if}}

Thisaddsanewbuttonthat,whenclicked,takestheuserto/usersauthtwitter,which,ofcourse,kicksofftheTwitterauthenticationprocess.

TheimagebeingusedisfromtheofficialTwitterbrandassetspageathttps://about.twitter.com/company/brand-assets.Twitterrecommendsusingthese

Page 481: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

brandingassetsforaconsistentlookacrossallservicesusingTwitter.Downloadthewholesetandthenpickoneyoulike.FortheURLshownhere,placethechosenimageinadirectorynamedpublicassetsvendor/twitter.Noticethatweforcethesizetobesmallenoughforthenavigationbar.

Withthesechanges,we'rereadytotrylogginginwithTwitter.

StarttheNotesapplicationserverasdonepreviously:

$npmstart

>[email protected]/chap08/notes

>DEBUG=notes:*SEQUELIZE_CONNECT=models/sequelize-sqlite.yaml

NOTES_MODEL=sequelizeUSER_SERVICE_URL=http://localhost:3333node--

experimental-modules./bin/www.mjs

(node:42095)ExperimentalWarning:TheESMmoduleloaderisexperimental.

notes:server-debugListeningonport3000+0ms

Thenuseabrowsertovisithttp://localhost:3000:

Noticethenewbutton.Itlooksaboutright,thankstohavingusedtheofficialTwitterbrandingimage.Thebuttonisalittlelarge,somaybeyouwanttoconsultadesigner.Obviously,adifferentdesignisrequiredifyou'regoingtosupportdozensofauthenticationservices.

Page 482: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Clickingonthisbuttontakesthebrowserto/usersauthtwitter,whichstartsPassportrunningtheOAuth2protocoltransactionsnecessarytoauthenticate.Andthen,onceyou'reloggedinwithTwitter,you'llseesomethinglikethefollowingscreenshot:

We'renowloggedin,andnoticethatourNotesusernameisthesameasourTwitterusername.Youcanbrowsearoundtheapplicationandcreate,edit,ordeletenotes.Infact,youcandothistoanynoteyoulike,evenonescreatedbyothers.That'sbecausewedidnotcreateanysortofaccesscontrolorpermissionssystem,andthereforeeveryuserhascompleteaccesstoeverynote.That'safeaturetoputonthebacklog.

Byusingmultiplebrowsersorcomputers,youcansimultaneouslyloginasdifferentusers,oneuserperbrowser.

YoucanrunmultipleinstancesoftheNotesapplicationbydoingwhatwedidearlier:

"scripts":{

"start":"SEQUELIZE_CONNECT=models/sequelize-sqlite.yaml

NOTES_MODEL=models/notes-sequelizeUSERS_MODEL=models/users-rest

USER_SERVICE_URL=http://localhost:3333node./bin/www",

"start-server1":"SEQUELIZE_CONNECT=models/sequelize-sqlite.yaml

NOTES_MODEL=models/notes-sequelizeUSERS_MODEL=models/users-rest

USER_SERVICE_URL=http://localhost:3333PORT=3000node./bin/www",

"start-server2":"SEQUELIZE_CONNECT=models/sequelize-sqlite.yaml

NOTES_MODEL=models/notes-sequelizeUSERS_MODEL=models/users-rest

Page 483: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

USER_SERVICE_URL=http://localhost:3333PORT=3002node./bin/www",

"dl-minty":"mkdir-pminty&&npmrundl-minty-css&&npmrundl-minty-

min-css",

"dl-minty-css":"wgethttps://bootswatch.com/4/minty/bootstrap.css-O

minty/bootstrap.css",

"dl-minty-min-css":"wget

https://bootswatch.com/4/minty/bootstrap.min.css-Ominty/bootstrap.min.css"

},

Then,inonecommandwindow,runthefollowingcommand:

$npmrunstart-server1

>[email protected]/chap08/notes

>DEBUG=notes:*SEQUELIZE_CONNECT=models/sequelize-sqlite.yaml

NOTES_MODEL=sequelizeUSER_SERVICE_URL=http://localhost:3333PORT=3000node-

-experimental-modules./bin/www.mjs

(node:43591)ExperimentalWarning:TheESMmoduleloaderisexperimental.

notes:server-debugListeningonport3000+0ms

Inanothercommandwindow,runthefollowingcommand:

$npmrunstart-server2

>[email protected]/chap08/notes

>DEBUG=notes:*SEQUELIZE_CONNECT=models/sequelize-sqlite.yaml

NOTES_MODEL=sequelizeUSER_SERVICE_URL=http://localhost:3333PORT=3002node-

-experimental-modules./bin/www.mjs

(node:43755)ExperimentalWarning:TheESMmoduleloaderisexperimental.

notes:server-debugListeningonport3002+0ms

Aspreviously,thisstartstwoinstancesoftheNotesserver,eachwithadifferentvalueinthePORTenvironmentvariable.Inthiscase,eachinstancewillusethesameuserauthenticationservice.Asshownhere,you'llbeabletovisitthetwoinstancesathttp://localhost:3000andhttp://localhost:3002.And,aspreviously,you'llbeabletostartandstoptheserversasyouwish,seethesamenotesineach,andseethatthenotesareretainedafterrestartingtheserver.

Anotherthingtotryistofiddlewiththesessionstore.Oursessiondatais

Page 484: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

beingstoredinthesessionsdirectory.Thesearejustfilesinthefilesystem,andwecantakealook:

$ls-lsessions/

total32

-rw-r--r--1davidwheel139Jan2519:28-QOS7eX8ZBAfmK9CCV8Xj8v-

3DVEtaLK.json

-rw-r--r--1davidwheel139Jan2521:30

T7VT4xt3_e9BiU49OMC6RjbJi6xB7VqG.json

-rw-r--r--1davidwheel223Jan2519:27ermh-

7ijiqY7XXMnA6zPzJvsvsWUghWm.json

-rw-r--r--1davidwheel139Jan2521:23

uKzkXKuJ8uMN_ROEfaRSmvPU7NmBc3md.json

$catsessions/T7VT4xt3_e9BiU49OMC6RjbJi6xB7VqG.json

{"cookie":

{"originalMaxAge":null,"expires":null,"httpOnly":true,"path":"/"},"__lastAcce

ss":1516944652270,"passport":{"user":"7genblogger"}}

ThisisafterlogginginusingaTwitteraccount;youcanseethattheTwitteraccountnameisstoredhereinthesessiondata.

Whatifyouwanttoclearasession?It'sjustafileinthefilesystem.Deletingthesessionfileerasesthesession,andtheuser'sbrowserwillbeforcefullyloggedout.

Thesessionwilltimeoutiftheuserleavestheirbrowseridleforlongenough.Oneofthesession-file-storeoptions,ttl,controlsthetimeoutperiod,whichdefaultsto3,600seconds(anhour).Withatimed-outsession,theapplicationrevertstoalogged-outstate.

Page 485: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SecurelykeepingsecretsandpasswordsWe'vecautionedseveraltimesabouttheimportanceofsafelyhandlinguseridentificationinformation.Theintentiontosafelyhandlethatdataisonething,butitisimportanttofollowthroughandactuallydoso.Whilewe'reusingafewgoodpracticessofar,asitstands,theNotesapplicationwouldnotwithstandanykindofsecurityaudit:

Userpasswordsarekeptincleartextinthedatabase

TheauthenticationtokensforTwitteretal,areinthesourcecodeincleartext

TheauthenticationserviceAPIkeyisnotacryptographicallysecureanything,it'sjustacleartextUUID

Ifyoudon'trecognizethephrasecleartext,itsimplymeansunencrypted.Anyonecouldreadthetextofuserpasswordsortheauthenticationtokens.It'sbesttokeepbothencryptedtoavoidinformationleakage.

Keepthisissueinthebackofyourmindbecausewe'llrevisittheseandothersecurityissuesinChapter12,Security.

Page 486: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TheNotesapplicationstackDidyounoticeearlierwhenwesaidruntheNotesapplicationstack?It'stimetoexplaintothemarketingteamwhat'smeantbythatphrase.They'llperhapsneedtoputanarchitecturediagramonmarketingbrochuresandthelike.It'salsousefulfordeveloperslikeustotakeastepbackanddrawapictureofwhatwe'vecreated,orareplanningtocreate.

Here'sthesortofdiagramthatanengineermightdrawtoshowthemarketingteamthesystemdesign.Themarketingteamwill,ofcourse,hireagraphicsartisttocleanitup:

Page 487: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TheboxlabeledNotesApplicationisthepublic-facingcodeimplementedbythetemplatesandtheroutermodules.Ascurrentlyconfigured,it'svisiblefromourlaptoponport3000.Itcanuseoneofseveraldatastorageservices.ItcommunicateswiththebackendUserAuthenticationServiceoverport3333(ascurrentlyconfigured).InChapter10,DeployingNode.jsApplications,we'llbeexpandingthispictureabitaswelearnhowtodeployonarealserver.

Page 488: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SummaryYou'vecoveredalotofgroundinthischapter,lookingatnotonlyuserauthenticationinExpressapplications,butalsomicroservicedevelopment.

Specifically,youcoveredsessionmanagementinExpress,usingPassportforuserauthentication,includingTwitter/OAuth,usingroutermiddlewaretolimitaccess,creatingaRESTservicewithRestify,andwhentocreateamicroservice.

Inthenextchapter,we'lltaketheNotesapplicationtoanewlevel-semi-real-timecommunicationbetweenapplicationusers.Todothis,we'llwritesomebrowser-sideJavaScriptandexplorehowtheSocket.iopackagecanletussendmessagesbetweenusers.

Page 489: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

DynamicClient/ServerInteractionwithSocket.IOTheoriginaldesignmodelofthewebissimilartothewaythatmainframesworkedinthe1970s.Bothold-schooldumbterminals,suchastheIBM3270,andwebbrowsers,followarequest-responseparadigm.Theusersendsarequestandthefar-offcomputersendsaresponsescreen.Whilewebbrowserscanshowmorecomplexinformationthanold-schooldumbterminals,theinteractionpatterninbothcasesisabackandforthofuserrequests,eachresultinginascreenofdatasentbytheserverscreenafterscreenor,inthecaseofwebbrowsers,pageafterpage.

Incaseyou'rewonderingwhatthishistorylessonisabout,thatrequest-responseparadigmisevidentintheNode.jsHTTPServerAPI,asshowninthefollowingcode:

http.createServer(function(request,response){

...handlerequest

}).listen();

Theparadigmcouldn'tbemoreexplicitthanthis.Therequestandtheresponsearerightthere.

Thefirstwebbrowserswereanadvancementovertext-baseduserinterfaces,withHTMLmixingimages,andtextwithvaryingcolors,fonts,andsizes.AsCSScamealong,HTMLimproved,iframesallowedembeddedmediaofallkinds,andJavaScriptimproved,sowehaveaquitedifferentparadigm.Thewebbrowserisstillsendingrequestsandreceivingapageofdata,butthatdatacanbequitecomplexand,moreimportantly,JavaScriptaddsinteractivity.

Onenewtechniqueiskeepinganopenconnectiontotheserverfor

Page 490: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

continualdataexchangebetweenserverandclient.Thischangeinthewebapplicationmodeliscalled,bysome,thereal-timeweb.Insomecases,websiteskeepanopenconnectiontothewebbrowser,withreal-timeupdatestowebpagesbeingonegoal.

Someobservethattraditionalwebapplicationscanuntruthfullydisplaytheirdata;thatis,iftwopeoplearelookingatapage,andonepersoneditsthatpage,thatperson'sbrowserwillupdatewiththecorrectcopyofthepage,whiletheotherbrowserisnotupdated.Thetwobrowsersshowdifferentversionsofthepage,oneofwhichisuntruthful.Thesecondbrowsercanevenshowapagethatnolongerexists,iftheuseratthefirstbrowserdeletesthatpage.Somethinkitwouldbebetteriftheotherperson'sbrowserisrefreshedtoshowthenewcontentassoonasthepageisedited.

Thisisonepossibleroleofthereal-timeweb;pagesthatupdatethemselvesaspagecontentchanges.Allkindsofsystemssupportreal-timeinteractivitybetweenfolksonthesamewebsite.Whetherit'sseeingFacebookcommentspopupasthey'rewritten,orcollaborativelyediteddocuments,there'sanewinteractivityparadigmontheweb.

We'reabouttoimplementthisbehaviorintheNotesapplication.

OneoftheoriginalpurposesforinventingNode.jswastosupportthereal-timeweb.TheCometapplicationarchitecture(CometisrelatedtoAJAX,andbothhappentobenamesofhouseholdcleaningproducts)involvesholdingtheHTTPconnectionopenforalongtime,withdataflowingbackandforthbetweenbrowserandserveroverthatchannel.ThetermCometwasintroducedbyAlexRussellinhisblogin2006(http://infrequently.org/2006/03/comet-low-latency-data-for-the-browser/)asageneraltermforthearchitecturalpatterntoimplementthisreal-time,two-waydataexchangebetweenclientandserver.ThatblogpostcalledforthedevelopmentofaprogrammingplatformverysimilartoNode.js.

Tosimplifythetask,we'llleanontheSocket.IOlibrary(http://socket.io/).Thislibrarysimplifiestwo-waycommunicationbetweenthebrowserandserver,andcansupportavarietyofprotocolswithfallbacktoold-school

Page 491: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

webbrowsers.

We'llbecoveringthefollowingtopics:

Real-timecommunicationsinmodernwebbrowsers

TheSocket.IOlibrary

IntegratingSocket.IOwithanExpressapplicationtosupportreal-timecommunication

Userexperienceforreal-timecommunication

Page 492: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

IntroducingSocket.IOTheaimofSocket.IOistomakereal-timeappspossibleineverybrowserandmobiledevice.Itsupportsseveraltransportprotocols,choosingthebestoneforthespecificbrowser.

IfyouweretoimplementyourapplicationwithWebSockets,itwouldbelimitedtothemodernbrowserssupportingthatprotocol.BecauseSocket.IOfallsbackonsomanyalternateprotocols(WebSockets,Flash,XHR,andJSONP),itsupportsawiderangeofwebbrowsers,includingsomeoldcruftybrowsers.

Astheapplicationauthor,youdon'thavetoworryaboutthespecificprotocolSocket.IOusesinagivenbrowser.Instead,youcanimplementthebusinesslogicandthelibrarytakescareofthedetailsforyou.

Socket.IOrequiresthataclientlibrarymakeitswayintothebrowser.Thatlibraryisprovided,andiseasytoinstantiate.You'llbewritingcodeonboththebrowsersideandserversideusingsimilarSocket.IOAPIsateachend.

ThemodelthatSocket.IOprovidesissimilartotheEventEmitterobject.Theprogrammerusesthe.onmethodtolistenforeventsandthe.emitmethodtosendthem.TheemittedeventsaresentbetweenthebrowserandtheserverwiththeSocket.IOlibrarytakingcareofsendingthembackandforth.

InformationaboutSocket.IOisavailableathttps://socket.io/.

Page 493: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

InitializingSocket.IOwithExpressSocket.IOworksbywrappingitselfaroundanHTTPServerobject.ThinkbacktoChapter4,HTTPServersandClients,wherewewroteamodulethathookedintoHTTPServermethodssothatwecouldspyonHTTPtransactions.TheHTTPSnifferattachesalistenertoeveryHTTPeventtoprintouttheevents.Butwhatifyouusedthatideatodorealwork?Socket.IOusesasimilarconcept,listeningtoHTTPrequestsandrespondingtospecificonesbyusingtheSocket.IOprotocoltocommunicatewithclientcodeinthebrowser.

Togetstarted,let'sfirstmakeaduplicateofthecodefromthepreviouschapter.Ifyoucreatedadirectorynamedchap08forthatcode,createanewdirectorynamedchap09andcopythesourcetreethere.

Wewon'tmakechangestotheuserauthenticationmicroservice,butwewilluseitforuserauthentication,ofcourse.

IntheNotessourcedirectory,installthesenewmodules:

[email protected]@3.7.x--save

Wewillincorporateuserauthenticationwiththepassportmodule,usedinChapter8,MultiuserAuthenticationtheMicroserviceWay,intosomeofthereal-timeinteractionswe'llimplement.

ToinitializeSocket.IO,wemustdosomemajorsurgeryonhowtheNotesapplicationisstarted.Sofar,weusedthebin/www.mjsscriptalongwithapp.mjs,witheachscripthostingdifferentstepsoflaunchingNotes.Socket.IOinitializationrequiresthatthesestepsoccurinadifferentordertowhatwe'vebeendoing.Therefore,wemustmergethesetwoscriptsinto

Page 494: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

one.Whatwe'lldoiscopythecontentofthebin/www.mjsscriptintoappropriatesectionsofapp.mjs,andfromthere,we'lluseapp.mjstolaunchNotes.

Atthebeginningofapp.mjs,addthistotheimportstatements:

importhttpfrom'http';

importpassportSocketIofrom'passport.socketio';

importsessionfrom'express-session';

importsessionFileStorefrom'session-file-store';

constFileStore=sessionFileStore(session);

exportconstsessionCookieName='notescookie.sid';

constsessionSecret='keyboardmouse';

constsessionStore=newFileStore({path:"sessions"});

Thepassport.socketiomoduleintegratesSocket.IOwithPassportJS-baseduserauthentication.We'llconfigurethissupportshortly.TheconfigurationforsessionmanagementisnowsharedbetweenSocket.IO,Express,andPassport.Theselinescentralizethatconfigurationtooneplaceinapp.mjs,sowecanchangeitoncetoaffecteveryplaceit'sneeded.

UsethistoinitializetheHTTPServerobject:

constapp=express();

exportdefaultapp;

constserver=http.createServer(app);

importsocketiofrom'socket.io';

constio=socketio(server);

io.use(passportSocketIo.authorize({

cookieParser:cookieParser,

key:sessionCookieName,

secret:sessionSecret,

store:sessionStore

}));

Thismovestheexportdefaultapplinefromthebottomofthefiletothislocation.Doesn'tthislocationmakemoresense?

Page 495: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TheioobjectisourentrypointintotheSocket.IOAPI.WeneedtopassthisobjecttoanycodethatneedstousethatAPI.Itwon'tbeenoughtosimplyrequirethesocket.iomoduleinothermodulesbecausetheioobjectiswhatwrapstheserverobject.Instead,we'llbepassingtheioobjectintowhatevermodulesaretouseit.

Theio.usefunctioninstallsinSocket.IOfunctionssimilartoExpressmiddleware.Inthiscase,weintegratePassportauthenticationintoSocket.IO:

varport=normalizePort(process.env.PORT||'3000');

app.set('port',port);

server.listen(port);

server.on('error',onError);

server.on('listening',onListening);

Thiscodeiscopiedfrombin/www.mjs,andsetsuptheporttolistento.Itreliesonthreefunctionsthatwillalsobecopiedintoapp.mjsfrombin/www.mjs:

app.use(session({

store:sessionStore,

secret:sessionSecret,

resave:true,

saveUninitialized:true,

name:sessionCookieName

}));

initPassport(app);

ThischangestheconfigurationofExpresssessionsupporttomatchtheconfigurationvariableswesetupearlier.It'sthesamevariablesusedwhensettinguptheSocket.IOsessionintegration,meaningthey'rebothonthesamepage.

UsethistoinitializeSocket.IOcodeintheroutermodules:

app.use('/',index);

app.use('/users',users);

Page 496: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

app.use('/notes',notes);

indexSocketio(io);

//notesSocketio(io);

Thisiswherewepasstheioobjectintomodulesthatmustuseit.ThisissothattheNotesapplicationcansendmessagestothewebbrowsersaboutchangesinNotes.Whatthatmeanswillbeclearerinasecond.What'srequiredisanalogoustotheExpressrouterfunctions,andthereforethecodetosend/receivemessagesfromSocket.IOclientswillalsobelocatedintheroutermodules.

Wehaven'twritteneitherofthesefunctionsyet(havepatience).Tosupportthis,weneedtomakeachangeinanimportstatementatthetop:

import{socketioasindexSocketio,routerasindex}from'./routes/index';

Eachroutermodulewillexportafunctionnamedsocketio,whichwe'llhavetorenameasshownhere.Thisfunctioniswhatwillreceivetheioobject,andhandleanySocket.IO-basedcommunications.Wehaven'twrittenthesefunctionsyet.

Then,attheendofapp.mjs,we'llcopyintheremainingcodefrombin/www.mjssotheHTTPServerstartslisteningonourselectedport:

functionnormalizePort(val){

varport=parseInt(val,10);

if(isNaN(port)){//namedpipe

returnval;

}

if(port>=0){//portnumber

returnport;

}

returnfalse;

}

/**

*EventlistenerforHTTPserver"error"event.

*/

functiononError(error){

Page 497: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

if(error.syscall!=='listen'){throwerror;}

varbind=typeofport==='string'?'Pipe'+port:'Port'+

port;

//handlespecificlistenerrorswithfriendlymessages

switch(error.code){

case'EACCES':

console.error(bind+'requireselevatedprivileges');

process.exit(1);

break;

case'EADDRINUSE':

console.error(bind+'isalreadyinuse');

process.exit(1);

break;

default:

throwerror;

}

}

/**

*EventlistenerforHTTPserver"listening"event.

*/

functiononListening(){

varaddr=server.address();

varbind=typeofaddr==='string'?'pipe'+addr:'port'+addr.port;

debug('Listeningon'+bind);

}

Then,inpackage.json,wemuststartapp.mjsratherthanbin/www.mjs:

"scripts":{

"start":"DEBUG=notes:*SEQUELIZE_CONNECT=models/sequelize-sqlite.yaml

NOTES_MODEL=sequelizeUSER_SERVICE_URL=http://localhost:3333node--

experimental-modules./app",

"start-server1":"DEBUG=notes:*SEQUELIZE_CONNECT=models/sequelize-

sqlite.yamlNOTES_MODEL=sequelizeUSER_SERVICE_URL=http://localhost:3333

PORT=3000node--experimental-modules./app",

"start-server2":"DEBUG=notes:*SEQUELIZE_CONNECT=models/sequelize-

sqlite.yamlNOTES_MODEL=sequelizeUSER_SERVICE_URL=http://localhost:3333

PORT=3002node--experimental-modules./app",

...

},

Atthispoint,youcandeletebin/www.mjsifyoulike.Youcanalsotry

Page 498: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

startingtheserver,butit'llfailbecausetheindexSocketiofunctiondoesnotexistyet.

Page 499: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Real-timeupdatesontheNoteshomepageThegoalwe'reworkingtowardsisfortheNoteshomepagetoautomaticallyupdatethelistofnotes,asnotesareeditedordeleted.Whatwe'vedonesofaristorestructuretheapplicationstartupsothatSocket.IOisinitializedintheNotesapplication.There'snochangeofbehavioryet,exceptthatitwillcrashduetoamissingfunction.

TheapproachisfortheNotesmodelclassestosendmessageswheneveranoteiscreated,updated,ordeleted.Intherouterclasses,we'lllistentothosemessages,thensendalistofnotetitlestoallbrowsersattachedtotheNotesapplication.

WheretheNotesmodelsofarhasbeenapassiverepositoryofdocuments,itnowneedstoemiteventstoanyinterestedparties.Thisisthelistenerpatternand,intheory,therewillbecodethatisinterestedinknowingwhennotesarecreated,edited,ordestroyed.Atthemoment,we'llusethatknowledgetoupdatetheNoteshomepage,buttherearemanypotentialotherusesofthatknowledge.

Page 500: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TheNotesmodelasanEventEmitterclassTheEventEmitteristheclassthatimplementslistenersupport.Let'screateanewmodule,models/notes-events.mjs,containingthefollowing:

importEventEmitterfrom'events';

classNotesEmitterextendsEventEmitter{

noteCreated(note){this.emit('notecreated',note);}

noteUpdate(note){this.emit('noteupdate',note);}

noteDestroy(data){this.emit('notedestroy',data);}

}

exportdefaultnewNotesEmitter();

ThismodulemaintainsthelistenerstoNotes-relatedeventsforus.We'vecreatedasubclassofEventEmitterbecauseitalreadyknowshowtomanagethelisteners.Aninstanceofthatobjectisexportedasthedefaultexport.

Let'snowupdatemodels/notes.mjstousenotes-eventstoemitevents.Becausewehaveasinglemodule,notes.mjs,todispatchcallstotheindividualNotesmodels,thismoduleprovidesakeypointatwhichwecanintercepttheoperationsandsendevents.Otherwise,we'dhavetointegratetheevent-sendingcodeintoeveryNotesmodel.

import_eventsfrom'./notes-events';

exportconstevents=_events;

Weneedtoimportthismoduleforusehere,butalsoexportitsothatothermodulescanalsoemitNotesevents.Bydoingthis,anothermodulethatimportsNotescancallnotes.events.functiontousethenotes-eventsmodule.

Thistechniqueiscalledre-exporting.Sometimes,youneedtoexporta

Page 501: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

functionfrommoduleAthatisactuallydefinedinmoduleB.ModuleAthereforeimportsthefunctionfrommoduleB,addingittoitsexports.

Thenwedoalittlerewritingofthesefunctions:

exportasyncfunctioncreate(key,title,body){

constnote=awaitmodel().create(key,title,body);

_events.noteCreated(note);

returnnote;

}

exportasyncfunctionupdate(key,title,body){

constnote=awaitmodel().update(key,title,body);

_events.noteUpdate(note);

returnnote;

}

exportasyncfunctiondestroy(key){

awaitmodel().destroy(key);

_events.noteDestroy({key});

returnkey;

}

ThecontractfortheNotesmodelfunctionsisthattheyreturnaPromise,andthereforeourcallerwillbeusingawaittoresolvethePromise.Therearethreesteps:

1. Callthecorrespondingfunctioninthecurrentmodelclass,andawaititsresult

2. Sendthecorrespondingmessagetoourlisteners3. Returnthevalue,andbecausethisisanasyncfunction,thevalue

willbereceivedasaPromisethatfulfillsthecontractforthesefunctions

Page 502: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Real-timechangesintheNoteshomepageTheNotesmodelnowsendseventsasNotesarecreated,updated,ordestroyed.Forthistobeuseful,theeventsmustbedisplayedtoourusers.Makingtheeventsvisibletoourusersmeansthecontrollerandviewportionsoftheapplicationmustconsumethoseevents.

Let'sstartmakingchangestoroutes/index.mjs:

router.get('/',async(req,res,next)=>{

try{

letnotelist=awaitgetKeyTitlesList();

res.render('index',{

title:'Notes',notelist:notelist,

user:req.user?req.user:undefined

});

}catch(e){next(e);}

});

Weneedtoreusepartoftheoriginalroutingfunction,touseitinanotherfunction.Therefore,we'vepulledcodethatusedtobeinthisblockintoanewfunction,getKeyTitlesList:

asyncfunctiongetKeyTitlesList(){

constkeylist=awaitnotes.keylist();

varkeyPromises=keylist.map(key=>{

returnnotes.read(key).then(note=>{

return{key:note.key,title:note.title};

});

});

returnPromise.all(keyPromises);

};

Thisportionoftheoriginalroutingfunctionisnowitsownfunction.It

Page 503: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

generatesanarrayofitemscontainingthekeyandtitleforallexistingNotes,usingPromise.alltomanagetheprocessofreadingeverything.

exportfunctionsocketio(io){

varemitNoteTitles=async()=>{

constnotelist=awaitgetKeyTitlesList()

io.of('/home').emit('notetitles',{notelist});

};

notes.events.on('notecreated',emitNoteTitles);

notes.events.on('noteupdate',emitNoteTitles);

notes.events.on('notedestroy',emitNoteTitles);

};

Hereisthesocketiofunctionwediscussedwhilemodifyingapp.mjs.Wereceivetheioobject,thenuseittoemitanotestitleseventtoallconnectedbrowsers.

Theio.of('/namespace')methodrestrictswhateverfollowstothegivennamespace.Inthiscase,we'reemittinganotestitlemessagetothe/homenamespace.

Theio.ofmethoddefineswhatSocket.IOcallsanamespace.NamespaceslimitthescopeofmessagessentthroughSocket.IO.Thedefaultnamespaceis/,andnamespaceslooklikepathnames,inthatthey'reaseriesofslash-separatednames.Aneventemittedintoanamespaceisdeliveredtoanysocketlisteningtothatnamespace.

Thecode,inthiscase,isfairlystraightforward.Itlistenstotheeventswejustimplemented,notecreated,noteupdate,andnotedestroy.Foreachoftheseevents,itemitsanevent,notetitles,containingthelistofnotekeysandtitles.

That'sit!

AsNotesarecreated,updated,anddestroyed,weensurethatthehomepagewillberefreshedtomatch.Thehomepagetemplate,views/index.hbs,willrequirecodetoreceivethateventandrewritethepagetomatch.

Page 504: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ChangingthehomepageandlayouttemplatesSocket.IOrunsonbothclientandserver,withthetwocommunicatingbackandforthovertheHTTPconnection.ThisrequiresloadingtheclientJavaScriptlibraryintotheclientbrowser.EachpageoftheNotesapplicationinwhichweseektoimplementSocket.IOservicesmustloadtheclientlibraryandhavecustomclientcodeforourapplication.

EachpageinNoteswillrequireadifferentSocket.IOclientimplementation,sinceeachpagehasdifferentrequirements.ThisaffectshowweloadJavaScriptcodeinNotes.

Initially,wesimplyputJavaScriptcodeatthebottomoflayout.hbs,becauseeverypagerequiredthesamesetofJavaScriptmodules.Butnowwe'veidentifiedtheneedforadifferentsetofJavaScriptoneachpage.Furthermore,someoftheJavaScriptneedstobeloadedfollowingtheJavaScriptcurrentlyloadedatthebottomoflayout.hbs.Specifically,jQueryisloadedcurrentlyinlayout.hbs,butwewanttousejQueryintheSocket.IOclientstoperformDOMmanipulationsoneachpage.Therefore,sometemplaterefactoringisrequired.

Createafile,partials/footerjs.hbs,containing:

<!--jQueryfirst,thenPopper.js,thenBootstrapJS-->

<scriptsrc="assetsvendor/jquery/jquery.min.js"></script>

<scriptsrc="assetsvendor/popper.js/umd/popper.min.js"></script>

<scriptsrc="assetsvendor/bootstrap/js/bootstrap.min.js"></script>

<scriptsrc="assetsvendor/feather-icons/feather.js"></script>

<script>

feather.replace()

</script>

Page 505: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Thishadbeenatthebottomofviews/layout.hbs.Wenowneedtomodifythatfileasfollows:

<body>

{{>header}}

{{{body}}}

</body>

Then,atthebottomofeverytemplate(error.hbs,index.hbs,login.hbs,notedestroy.hbs,noteedit.hbs,andnoteview.hbs),addthisline:

{{>footerjs}}

Sofar,thishasn'tchangedwhatwillbeloadedinthepages,becausefooterjscontainsexactlywhatwasalreadyatthebottomoflayout.hbs.ButitgivesusthefreedomtoloadSocket.IOclientcodeafterthescriptsinfooterjsareloaded.

Inviews/index.hbsaddthisatthebottom,afterthefooterjspartial:

{{>footerjs}}

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

<script>

$(document).ready(function(){

varsocket=io('/home');

socket.on('notetitles',function(data){

varnotelist=data.notelist;

$('#notetitles').empty();

for(vari=0;i<notelist.length;i++){

notedata=notelist[i];

$('#notetitles')

.append('<aclass="btnbtn-lgbtn-blockbtn-outline-dark"

href="/notes/view?key='+

notedata.key+'">'+notedata.title+'</a>');

}

});

});

</script>

Page 506: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ThefirstlineiswhereweloadtheSocket.IOclientlibrary.You'llnoticethatweneversetupanyExpressroutetohandlethe/socket.ioURL.Instead,theSocket.IOlibrarydidthatforus.

Becausewe'vealreadyloadedjQuery(tosupportBootstrap),wecaneasilyensurethatthiscodeisexecutedoncethepageisfullyloadedusing$(document).ready.

Thiscodefirstconnectsasocketobjecttothe/homenamespace.ThatnamespaceisbeingusedforeventsrelatedtotheNoteshomepage.Wethenlistenforthenotetitlesevents,forwhichsomejQueryDOMmanipulationerasesthecurrentlistofNotesandrendersanewlistonthescreen.

That'sit.Ourcodeinroutes/index.mjslistenedtovariouseventsfromtheNotesmodel,and,inresponse,sentanotetitleseventtothebrowser.Thebrowsercodetakesthatlistofnoteinformationandredrawsthescreen.

Youmightnoticethatourbrowser-sideJavaScriptisnotusingES-2015/2016/2017features.Thiscodewould,ofcourse,becleanerifweweretodoso.Howcanweknowwhetherourvisitorsuseabrowsermodernenoughforthoselanguagefeatures?WecoulduseBabeltotranspileES-2015/2016/2017codeintoES5codecapableofrunningonanybrowser.However,itmaybeausefultrade-offtostillwriteES5codeinthebrowser.

Page 507: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

RunningNoteswithreal-timehomepageupdatesWenowhaveenoughimplementedtoruntheapplicationandseesomereal-timeaction.

Asyoudidearlier,starttheuserinformationmicroserviceinonewindow:

$npmstart

>[email protected]/chap09/users

>DEBUG=users:*PORT=3333SEQUELIZE_CONNECT=sequelize-sqlite.yamlnode--

experimental-modulesuser-server

(node:11866)ExperimentalWarning:TheESMmoduleloaderisexperimental.

users:serviceUser-Auth-Servicelisteningathttp://127.0.0.1:3333+0ms

Then,inanotherwindow,starttheNotesapplication:

$npmstart

>[email protected]/chap09/notes

>DEBUG=notes:*SEQUELIZE_CONNECT=models/sequelize-sqlite.yaml

NOTES_MODEL=sequelizeUSER_SERVICE_URL=http://localhost:3333node--

experimental-modules./app

(node:11998)ExperimentalWarning:TheESMmoduleloaderisexperimental.

notes:debug-INDEXListeningonport3000+0ms

Then,inabrowserwindow,gotohttp://localhost:3000andlogintotheNotesapplication.Toseethereal-timeeffects,openmultiplebrowserwindows.IfyoucanuseNotesfrommultiplecomputers,thendothataswell.

Page 508: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Inonebrowserwindow,startcreatinganddeletingnotes,whileleavingtheotherbrowserwindowsviewingthehomepage.Createanote,anditshouldshowupimmediatelyonthehomepageintheotherbrowserwindows.Deleteanoteanditshoulddisappearimmediatelyaswell.

Page 509: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Real-timeactionwhileviewingnotesIt'scoolhowwecannowseereal-timechangesinapartoftheNotesapplication.Let'sturntothenotesviewpagetoseewhatwecando.Whatcomestomindisthisfunctionality:

Updatethenoteifsomeoneelseeditsit

Redirecttheviewertothehomepageifsomeoneelsedeletesthenote

Allowuserstoleavecommentsonthenote

Forthefirsttwofeatures,wecanrelyontheexistingeventscomingfromtheNotesmodel.Thethirdfeaturewillrequireamessagingsubsystem,sowe'llgettothatlaterinthischapter.

Inroutes/notes.mjs,addthistotheendofthemodule:

exportfunctionsocketio(io){

notes.events.on('noteupdate',newnote=>{

io.of('view').emit('noteupdate',newnote);

});

notes.events.on('notedestroy',data=>{

io.of('view').emit('notedestroy',data);

});

};

Atthetopofapp.mjs,makethischange:

import{socketioasindexSocketio,routerasindex}from'./routes/index';

Page 510: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

import{routerasusers,initPassport}from'./routes/users';

import{socketioasnotesSocketio,routerasnotes}from'./routes/notes';

Uncommentthatlineofcodeinapp.mjsbecausewe'venowimplementedthefunctionwesaidwe'dgettolater:

indexSocketio(io);

notesSocketio(io);

ThissetsuptheNotesapplicationtosendnoteupdateandnotedestroymessageswhennotesareupdatedordestroyed.Thedestinationisthe/viewnamespace.We'llneedtomakeacorrespondingmodificationtothenoteviewtemplatesoitdoestherightthing.Thismeansanybrowserviewinganynoteintheapplicationwillconnecttothisnamespace.Everysuchbrowserwillreceiveeventsaboutanynotebeingchanged,eventhosenotesthatarenotbeingviewed.Thismeansthattheclientcodewillhavetocheckthekey,andonlytakeactioniftheeventreferstothenotebeingdisplayed.

Page 511: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Changingthenoteviewtemplateforreal-timeactionAswedidearlier,inordertomaketheseeventsvisibletotheuser,wemustnotonlyaddclientcodetothetemplate,views/noteview.hbs;weneedacoupleofsmallchangestothetemplate:

<divclass="container-fluid">

<divclass="row"><divclass="col-xs-12">

{{#ifnote}}<h3id="notetitle">{{note.title}}</h3>{{/if}}

{{#ifnote}}<divid="notebody">{{note.body}}</div>{{/if}}

<p>Key:{{notekey}}</p>

</div></div>

{{#ifuser}}

{{#ifnotekey}}

<divclass="row"><divclass="col-xs-12">

<divclass="btn-group">

<aclass="btnbtn-outline-dark"

href="notesdestroy?key={{notekey}}"

role="button">Delete</a>

<acletemplate,views/noteview.hb

ass="btnbtn-outline-dark"

href="notesedit?key={{notekey}}"

role="button">Edit</a>

</div>

</div></div>

{{/if}}

{{/if}}

</div>

{{>footerjs}}

{{#ifnotekey}}

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

<script>

$(document).ready(function(){

io('/view').on('noteupdate',function(note){

if(note.key==="{{notekey}}"){

$('h3#notetitle').empty();

$('h3#notetitle').text(note.title);

Page 512: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

$('#notebody').empty();

$('#notebody').text(note.body);

}

});

io('/view').on('notedestroy',function(data){

if(data.key==="{{notekey}}"){

window.location.href="/";

}

});

});

</script>

{{/if}}

Weconnecttothe/viewnamespacewherethemessagesaresent.Asnoteupdateornotedestroymessagesarrive,wecheckthekeytoseewhetheritmatchesthekeyforthenotebeingdisplayed.

Atechniqueisusedherethat'simportanttounderstand.WehavemixedJavaScriptexecutedontheserver,withJavaScriptexecutedinthebrowser.Wemustcomparethenotekeyreceivedbytheclientcodeagainstthenotekeyforthenotebeingviewedbythispage.Thelatternotekeyvalueisknownontheserver,whiletheformerisknownintheclient.

Rememberthatcodewithinthe{{..}}delimitersisinterpretedbytheHandlebarstemplateengineontheserver.Considerthefollowing:

if(note.key==="{{notekey}}"){

..

}

Thiscomparisonisbetweenthenotekeyvalueinthebrowser,whicharrivedinsidethemessagefromtheserver,andthenotekeyvariableontheserver.Thatvariablecontainsthekeyofthenotebeingdisplayed.Therefore,inthiscase,weareabletoensurethesecodesnippetsareexecutedonlyforthenotebeingshownonthescreen.

Forthenoteupdateevent,wetakethenewnotecontentanddisplayitonthescreen.Forthistowork,wehadtoaddid=attributestotheHTMLsowecouldusejQueryselectorsinmanipulatingtheDOM.

Page 513: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Forthenotedestroyevent,wesimplyredirectthebrowserwindowbacktothehomepage.Thenotebeingviewedhasbeendeleted,andthere'snopointtheusercontinuingtolookatanotethatnolongerexists.

Page 514: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

RunningNoteswithreal-timeupdateswhileviewinganoteAtthispoint,youcannowreruntheNotesapplicationandtrythisout.

LaunchtheuserauthenticationserverandtheNotesapplicationasbefore.Then,inthebrowser,openmultiplewindowsontheNotesapplication.Thistime,haveoneviewingthehomepage,andtwoviewinganote.Inoneofthosewindows,editthenotetomakeachange,andseethetextchangeonboththehomepageandthepageviewingthenote.

Thendeletethenote,andwatchitdisappearfromthehomepage,andthebrowserwindowthathadviewedthenoteisnowonthehomepage.

Page 515: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Inter-userchatandcommentingforNotesThisiscool!Wenowhavereal-timeupdatesinNotesasweeditdeleteorcreatenotes.Let'snowtakeittothenextlevelandimplementsomethingakintointer-userchatting.

It'spossibletopivotourNotesapplicationconceptandtakeitinthedirectionofasocialnetwork.Inthemajorityofsuchnetworks,userspostthings(notes,pictures,videos,andsoon),andotheruserscommentonthosethings.Donewell,thesebasicelementscandevelopalargecommunityofpeoplesharingnoteswitheachother.WhiletheNotesapplicationiskindofatoy,it'snottooterriblyfarfrombeingabasicsocialnetwork.Commentingthewaywewilldonowisatinystepinthatdirection.

Oneachnotepage,we'llhaveanareatodisplaymessagesfromNotesusers.Eachmessagewillshowtheusername,atimestamp,andtheirmessage.We'llalsoneedamethodforuserstopostamessage,andwe'llalsoallowuserstodeletemessages.

Eachofthoseoperationswillbeperformedwithoutrefreshingthescreen.Instead,coderunninginsidethewebpagewillsendcommandsto/fromtheserverandtakeactionsdynamically.

Let'sgetstarted.

Page 516: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

DatamodelforstoringmessagesWeneedtostartbyimplementingadatamodelforstoringmessages.ThebasicfieldsrequiredareauniqueID,theusernameofthepersonsendingthemessage,thenamespacethemessageissentto,theirmessage,andfinallyatimestampforwhenthemessagewassent.Asmessagesarereceivedordeleted,eventsmustbeemittedfromthemodelsowecandotherightthingonthewebpage.

ThismodelimplementationwillbewrittenforSequelize.Ifyoupreferadifferentstoragesolution,youcan,byallmeans,re-implementthesameAPIonotherdatastoragesystems.

Createanewfile,models/messages-sequelize.mjs,containingthefollowing:

importSequelizefrom'sequelize';

importjsyamlfrom'js-yaml';

importfsfrom'fs-extra';

importutilfrom'util';

importEventEmitterfrom'events';

classMessagesEmitterextendsEventEmitter{}

importDBGfrom'debug';

constdebug=DBG('notes:model-messages');

consterror=DBG('notes:error-messages');

varSQMessage;

varsequlz;

exportconstemitter=newMessagesEmitter();

ThissetsupthemodulesbeingusedandalsoinitializestheEventEmitterinterface.We'realsoexportingtheEventEmitterasemittersoothermodules

Page 517: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

canuseit:

asyncfunctionconnectDB(){

if(typeofsequlz==='undefined'){

constyamltext=await

fs.readFile(process.env.SEQUELIZE_CONNECT,'utf8');

constparams=jsyaml.safeLoad(yamltext,'utf8');

sequlz=newSequelize(params.dbname,

params.username,params.password,params.params);

}

if(SQMessage)returnSQMessage.sync();

SQMessage=sequlz.define('Message',{

id:{type:Sequelize.INTEGER,autoIncrement:true,primaryKey:

true},

from:Sequelize.STRING,

namespace:Sequelize.STRING,

message:Sequelize.STRING(1024),

timestamp:Sequelize.DATE

});

returnSQMessage.sync();

}

Thisdefinesourmessageschemainthedatabase.We'llusethesamedatabasethatweusedforNotes,butthemessageswillbestoredintheirowntable.

Theidfieldwon'tbesuppliedbythecaller;instead,itwillbeautogenerated.BecauseitisanautoIncrementfield,eachmessagethat'saddedwillbeassignedanewidnumberbythedatabase:

exportasyncfunctionpostMessage(from,namespace,message){

constSQMessage=awaitconnectDB();

constnewmsg=awaitSQMessage.create({

from,namespace,message,timestamp:newDate()

});

vartoEmit={

id:newmsg.id,from:newmsg.from,

namespace:newmsg.namespace,message:newmsg.message,

timestamp:newmsg.timestamp

};

emitter.emit('newmessage',toEmit);

Page 518: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

}

Thisistobecalledwhenauserpostsanewcomment/message.Wefirststoreitinthedatabase,andthenweemitaneventsayingthemessagewascreated:

exportasyncfunctiondestroyMessage(id,namespace){

constSQMessage=awaitconnectDB();

constmsg=awaitSQMessage.find({where:{id}});

if(msg){

msg.destroy();

emitter.emit('destroymessage',{id,namespace});

}

}

Thisistobecalledwhenauserrequeststhatamessageshouldbedeleted.WithSequelize,wemustfirstfindthemessageandthendeleteitbycallingitsdestroymethod.Oncethat'sdone,weemitamessagesayingthemessagewasdestroyed:

exportasyncfunctionrecentMessages(namespace){

constSQMessage=awaitconnectDB();

constmessages=SQMessage.findAll({

where:{namespace},order:['timestamp'],limit:20

});

returnmessages.map(message=>{

return{

id:message.id,from:message.from,

namespace:message.namespace,message:message.message,

timestamp:message.timestamp

};

});

}

Whilethisismeanttobecalledwhenviewinganote,itisgeneralizedtoworkforanySocket.IOnamespace.Itfindsthemostrecent20messagesassociatedwiththegivennamespaceandreturnsacleaned-uplisttothecaller.

Page 519: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

AddingmessagestotheNotesrouterNowthatwecanstoremessagesinthedatabase,let'sintegratethisintotheNotesroutermodule.

Inroutes/notes.mjs,addthistotheimportstatements:

import*asmessagesfrom'../models/messages-sequelize';

Ifyouwishtoimplementadifferentdatastoragemodelformessages,you'llneedtochangethisimportstatement.Youshouldconsiderusinganenvironmentvariabletospecifythemodulename,aswe'vedoneelsewhere:

//Saveincomingmessagetomessagepool,thenbroadcastit

router.post('/make-comment',ensureAuthenticated,async(req,res,next)=>{

try{

awaitmessages.postMessage(req.body.from,

req.body.namespace,req.body.message);

res.status(200).json({});

}catch(err){

res.status(500).end(err.stack);

}

});

//Deletetheindicatedmessage

router.post('/del-message',ensureAuthenticated,async(req,res,next)=>{

try{

awaitmessages.destroyMessage(req.body.id,req.body.namespace);

res.status(200).json({});

}catch(err){

res.status(500).end(err.stack);

}

});

Page 520: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Thispairofroutes,notesmake-commentandnotesdel-message,isusedtopostanewcommentordeleteanexistingone.Eachcallsthecorrespondingdatamodelfunctionandthensendsanappropriateresponsebacktothecaller.

RememberthatpostMessagestoresamessageinthedatabase,andthenitturnsaroundandemitsthatmessagetootherbrowsers.Likewise,destroyMessagedeletesthemessagefromthedatabase,thenemitsamessagetootherbrowserssayingthatthemessagehasbeendeleted.Finally,theresultsfromrecentMessageswillreflectthecurrentsetofmessagesinthedatabase.

BothofthesewillbecalledbyAJAXcodeinthebrowser:

module.exports.socketio=function(io){

io.of('/view').on('connection',function(socket){

//'cb'isafunctionsentfromthebrowser,towhichwe

//sendthemessagesforthenamednote.

socket.on('getnotemessages',(namespace,cb)=>{

messages.recentMessages(namespace).then(cb)

.catch(err=>console.error(err.stack));

});

});

messages.emitter.on('newmessage',newmsg=>{

io.of('/view').emit('newmessage',newmsg);

});

messages.emitter.on('destroymessage',data=>{

io.of('/view').emit('destroymessage',data);

});

..

};

ThisistheSocket.IOgluecode,whichwewilladdtothecodewelookedatearlier.

ThegetnotemessagesmessagefromthebrowserrequeststhelistofmessagesforthegivenNote.ThiscallstherecentMessagesfunctioninthemodel.ThisusesafeatureofSocket.IOwheretheclientcanpassacallbackfunction,andserver-sideSocket.IOcodecaninvokethatcallback,givingitsomedata.

Page 521: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Wealsolistentothenewmessageanddestroymessagemessagesemittedbythemessagesmodel,sendingcorrespondingmessagestothebrowser.Thesearesentusingthemethoddescribedearlier.

Page 522: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ChangingthenoteviewtemplateformessagesWeneedtodivebackintoviews/noteview.hbswithmorechangessothatwecanview,create,anddeletemessages.Thistime,wewilladdalotofcode,includingusingaBootstrapmodalpopuptogetthemessage,severalAJAXcallstocommunicatewiththeserver,and,ofcourse,moreSocket.IOstuff.

Page 523: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

UsingaModalwindowtocomposemessagesTheBootstrapframeworkhasaModalcomponentthatservesasimilarpurposetoModaldialogsindesktopapplications.YoupopuptheModal,itpreventsinteractionwithotherpartsofthewebpage,youenterstuffintofieldsintheModal,andthenclickabuttontomakeitclose.

ThisnewsegmentofcodereplacestheexistingsegmentdefiningtheEditandDeletebuttons,inviews/noteview.hbs:

{{#ifuser}}

{{#ifnotekey}}

<divclass="row"><divclass="col-xs-12">

<divclass="btn-group">

<aclass="btnbtn-outline-dark"href="notesdestroy?key=

{{notekey}}"

role="button">Delete</a>

<aclass="btnbtn-outline-dark"href="notesedit?key=

{{notekey}}"

role="button">Edit</a>

<buttontype="button"class="btnbtn-outline-dark"

data-toggle="modal"

data-target="#notes-comment-modal">Comment</button>

</div>

</div></div>

<divid="noteMessages"></div>

{{/if}}

{{/if}}

Thisaddssupportforpostingcommentsonanote.TheuserwillseeaModalpop-upwindowinwhichtheywritetheircomment.We'llshowthecodefortheModallater.

WeaddedanewbuttonlabeledCommentthattheuserwillclicktostart

Page 524: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

theprocessofpostingamessage.ThisbuttonisconnectedtotheModalbywayoftheelementIDspecifiedinthedata-targetattribute.TheIDwillmatchtheoutermostdivwrappingtheModal.ThisstructureofdivelementsandclassnamesarefromtheBootstrapwebsiteathttp://getbootstrap.com/docs/4.0/components/modal/.

Let'saddthecodefortheModalatthebottomofviews/noteview.hbs.

{{>footerjs}}

{{#ifnotekey}}

{{#ifuser}}

<divclass="modalfade"id="notes-comment-modal"tabindex="-1"

role="dialog"aria-labelledby="noteCommentModalLabel"aria-hidden="true">

<divclass="modal-dialogmodal-dialog-centered"role="document">

<divclass="modal-content">

<divclass="modal-header">

<buttontype="button"class="close"data-dismiss="modal"aria-

label="Close">

<spanaria-hidden="true">&times;</span>

</button>

<h4class="modal-title"id="noteCommentModalLabel">Leavea

Comment</h4>

</div>

<divclass="modal-body">

<formmethod="POST"id="submit-comment"class="well"data-async

data-target="#rating-modal"action="notesmake-comment">

<inputtype="hidden"name="from"value="{{user.id}}">

<inputtype="hidden"name="namespace"value="/view-

{{notekey}}">

<inputtype="hidden"name="key"value="{{notekey}}">

<fieldset>

<divclass="form-group">

<labelfor="noteCommentTextArea">

YourExcellentThoughts,Please</label>

<textareaid="noteCommentTextArea"name="message"

class="form-control"rows="3"></textarea>

</div>

<divclass="form-group">

<divclass="col-sm-offset-2col-sm-10">

<buttonid="submitNewComment"type="submit"class="btn

btn-default">

MakeComment</button>

</div>

</div>

Page 525: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

</fieldset>

</form>

</div>

</div>

</div>

</div>

{{/if}}

{{/if}}

ThekeyportionofthisistheHTMLformcontainedwithinthediv.modal-bodyelement.It'sastraightforward,normalBootstrap,augmentedformwithanormalSubmitbuttonatthebottom.Afewhiddeninputelementsareusedtopassextrainformationinsidetherequest.

WiththeHTMLsetupthisway,BootstrapwillensurethatthisModalistriggeredwhentheuserclicksontheCommentbutton.TheusercanclosetheModalbyclickingontheClosebutton.Otherwise,it'suptoustoimplementcodetohandletheformsubmissionusingAJAXsothatitdoesn'tcausethepagetoreload.

Page 526: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Sending,displaying,anddeletingmessagesNotethatthesecodesnippetsarewrappedwith{{#if}}statements,sothatcertainuserinterfaceelementsaredisplayedonlytosufficientlyprivilegedusers.Auserthatisn'tloggedincertainlyshouldn'tbeabletopostamessage.

NowwehavealotofSocket.IOcodetoadd:

{{#ifnotekey}}

{{#ifuser}}

<script>

$(document).ready(function(){...});

{{/if}}

{{/if}}

There'sanothercodesectiontohandlethenoteupdateandnotedestroymessages.Thisnewsectionhastodowithmessagesthatmanagethecomments.

Weneedtohandletheformsubmissionforpostinganewcomment,gettherecentmessageswhenfirstviewinganote,listenforeventsfromtheserveraboutnewmessagesordeletedmessages,renderthemessagesonthescreen,andhandlerequeststodeleteamessage:

$(document).ready(function(){

io('view').emit('getnotemessages','view-{{notekey}}',function(msgs){

$('#noteMessages').empty();

if(msgs.length>0){

msgs.forEach(function(newmsg){

$('#noteMessages').append(formatMessage(newmsg));

});

$('#noteMessages').show();

connectMsgDelButton();

Page 527: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

}else$('#noteMessages').hide();

});

varconnectMsgDelButton=function(){

$('.message-del-button').on('click',function(event){

$.post('notesdel-message',{

id:$(this).data("id"),

namespace:$(this).data("namespace")

},

function(response){});

event.preventDefault();

});

};

varformatMessage=function(newmsg){

return'<divid="notemessage-'+newmsg.id+'"class="card">'

+'<divclass="card-body">'

+'<h5class="card-title">'+newmsg.from+'</h5>'

+'<divclass="card-text">'+newmsg.message

+'<smallstyle="display:block">'+newmsg.timestamp

+'</small></div>'

+'<buttontype="button"class="btnbtn-primarymessage-

del-button"data-id="'

+newmsg.id+'"data-namespace="'+newmsg.namespace+'">'

+'Delete</button>'

+'</div>'

+'</div>';

};

io('/view').on('newmessage',function(newmsg){

if(newmsg.namespace==='/view-{{notekey}}'){

$('#noteMessages').prepend(formatMessage(newmsg));

connectMsgDelButton();

}

});

io('/view').on('destroymessage',function(data){

if(data.namespace==='/view-{{notekey}}'){

$('#noteMessages#notemessage-'+data.id).remove();

}

});

$('form#submit-comment').submit(function(event){

//Abortanypendingrequest

if(request){request.abort();}

var$form=$('form#submit-comment');

var$target=$($form.attr('data-target'));

varrequest=$.ajax({

type:$form.attr('method'),

url:$form.attr('action'),

data:$form.serialize()

});

request.done(function(response,textStatus,jqXHR){});

request.fail(function(jqXHR,textStatus,errorThrown){

Page 528: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

alert("ERROR"+jqXHR.responseText);

});

request.always(function(){

//Reenabletheinputs

$('#notes-comment-modal').modal('hide');

});

event.preventDefault();

});

});

Thecodewithin$('form#submit-comment').submithandlestheformsubmissionforthecommentform.BecausewealreadyhavejQueryavailable,wecanuseitsAJAXsupporttoPOSTarequesttotheserverwithoutcausingapagereload.

Usingevent.preventDefault,weensurethatthedefaultactiondoesnotoccur.FortheFORMsubmission,thatmeansthebrowserpagedoesnotreload.WhathappensisanHTTPPOSTissenttonotesmake-commentwithadatapayloadconsistingofthevaluesoftheform'sinputelements.Includedinthosevaluesarethreehiddeninputs,from,namespace,andkey,providingusefulidentificationdata.

Ifyourefertothenotesmake-commentroutedefinition,thiscallsmessagesModel.postMessagetostorethemessageinthedatabase.Thatfunctionthenpostsanevent,newmessage,whichourserver-sidecodeforwardstoanybrowserthat'sconnectedtothenamespace.Shortlyafterthat,anewmessageeventwillarriveinbrowsers.

Thenewmessageeventaddsamessageblock,usingtheformatMessagefunction.TheHTMLforthemessageisprependedto#noteMessages.

Whenthepageisfirstloaded,wewanttoretrievethecurrentmessages.Thisiskickedoffwithio('/view').emit('getnotemessages',...Thisfunction,asthenameimplies,sendsagetnotemessagesmessagetotheserver.Weshowedtheimplementationoftheserver-sidehandlerforthismessageearlier,inroutes/notes.mjs.

Ifyouremember,wesaidthatSocket.IOsupportstheprovisionofacallbackfunctionthatiscalledbytheserverinresponsetoanevent.You

Page 529: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

simplypassafunctionasthelastparametertoa.emitcall.Thatfunctionismadeavailableattheotherendofthecommunication,tobecalledwhenappropriate.Tomakethisclear,wehaveacallbackfunctiononthebrowserbeinginvokedbyserver-sidecode.

Inthiscase,theserver-sidecallsourcallbackfunctionwithalistofmessages.Themessagelistarrivesintheclient-sidecallbackfunction,whichdisplaystheminthe#noteMessagesarea.ItusesjQueryDOMmanipulationtoeraseanyexistingmessages,thenrenderseachmessageintothemessagesareausingtheformatMessagefunction.

Themessagedisplaytemplate,informatMessage,isstraightforward.ItusesaBootstrapcardtogiveanicevisualeffect.And,thereisabuttonfordeletingmessages.

InformatMessagewecreatedaDeletebuttonforeachmessage.Thosebuttonsneedaneventhandler,andtheeventhandlerissetupusingtheconnectMsgDelButtonfunction.Inthiscase,wesendanHTTPPOSTrequesttonotesdel-message.WeagainusethejQueryAJAXsupporttopostthatHTTPrequest.

Thenotesdel-messagerouteinturncallsmessagesModel.destroyMessagetodothedeed.Thatfunctionthenemitsanevent,destroymessage,whichgetssentbacktothebrowser.Asyouseehere,thedestroymessageeventhandlercausesthecorrespondingmessagetoberemovedusingjQueryDOMmanipulation.Wewerecarefultoaddanid=attributetoeverymessagetomakeremovaleasy.

Sincetheflipsideofdestructioniscreation,weneedtohavethenewmessageeventhandlersittingnexttothedestroymessageeventhandler.ItalsousesjQueryDOMmanipulationtoinsertthenewmessageintothe#noteMessagesarea.

Page 530: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Whileenteringamessage,theModallookslikethis:

RunningNotesandpassingmessagesThatwasalotofcode,butwenowhavetheabilitytocomposemessages,displaythemonthescreen,anddeletethem,allwithnopagereloads.

Youcanruntheapplicationaswedidearlier,firststartingtheuserauthenticationserverinonecommand-linewindowandtheNotesapplicationinanother:

Page 531: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Trythiswithmultiplebrowserwindowsviewingthesamenoteordifferentnotes.Thisway,youcanverifythatnotesshowuponlyonthecorrespondingnotewindow.

Page 532: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

OtherapplicationsofModalwindowsWeusedaModalandsomeAJAXcodetoavoidonepagereloadduetoaformsubmission.IntheNotesapplication,asitstands,asimilartechniquecouldbeusedwhencreatinganewnote,editingexistingnotes,anddeletingexistingnotes.Ineachcase,wewoulduseaModal,someAJAXcodetohandletheformsubmission,andsomejQuerycodetoupdatethepagewithoutcausingareload.

Butwait,that'snotall.Considerthesortofdynamicreal-timeuserinterfacewizardryonthepopularsocialnetworks.Imaginewhateventsand/orAJAXcallsarerequired.

WhenyouclickonanimageinTwitter,itpopsup,youguessedit,aModalwindowtoshowalargerversionoftheimage.TheTwitterComposenewTweetwindowisalsoaModalwindow.FacebookusesmanydifferentModalwindows,suchaswhensharingapost,reportingaspampost,orwhiledoingalotofotherthingsFacebook'sdesignersdeemtorequireapop-upwindow.

Socket.IO,aswe'veseen,givesusarichfoundationofeventspassingbetweenserverandclientthatcanbuildmultiuser,multichannelcommunicationexperiencesforyourusers.

Page 533: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SummaryWhilewecamealongwayinthischapter,maybeFacebookdoesn'thaveanythingtofearfromthebabystepswetooktowardconvertingtheNotesapplicationintoasocialnetwork.Thischaptergaveustheopportunitytoexploresomereallycooltechnologyforpseudoreal-timecommunicationbetweenbrowsersessions.

Lookupthetechnicaldefinitionforthephraserealtimeandyou'llseethereal-timewebisnottrulyrealtime.Theactualmeaningofrealtimeinvolvessoftwarewithstricttimeboundariesthatmustrespondtoeventswithinaspecifiedtimeconstraint.Real-timesoftwareistypicallyusedinembeddedsystemstorespondtobuttonpresses,forapplicationsasdiverseasjunkfooddispensersandmedicaldevicesinintensivecareunits.Eattoomuchjunkfoodandyoucouldendupinintensivecare,andbeservedbyreal-timesoftwareinbothcases.Tryandrememberthedistinctionbetweendifferentmeaningsforthisphrase.

Inthischapter,youlearnedaboutusingSocket.IOforpseudoreal-timewebexperiences,usingtheEventEmitterclasstosendmessagesbetweenpartsofanapplication,jQuery,AJAX,andotherbrowser-sideJavaScripttechnologies,whileavoidingpagereloadswhilemakingAJAXcalls.

Inthenextchapter,wewilllookintoNode.jsapplicationdeploymentonrealservers.Runningcodeonourlaptopiscool,buttohitthebigtime,theapplicationneedstobeproperlydeployed.

Page 534: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

DeployingNode.jsApplicationsNowthattheNotesapplicationisfairlycomplete,it'stimetothinkabouthowtodeployittoarealserver.We'vecreatedaminimalimplementationofthecollaborativenoteconceptthatworksfairlywell.Togrow,Notesmustescapeourlaptopandliveonarealserver.ThegoalistolookatdeploymentmethodsforNode.jsapplications.

Inthischapter,wewillcoverthefollowingtopics:

TraditionalLSB-compliantNode.jsdeployment

UsingPM2toimprovereliability

DeploymenttoaVirtualPrivateServer(VPS)provider

MicroservicedeploymentwithDocker(wehavefourdistinctservicestodeploy)

DeploymenttoaDockerhostingprovider

Thefirsttaskistoduplicatethesourcecodefromthepreviouschapter.It'ssuggestedyoucreateanewdirectory,chap10,asasiblingofthechap09directory,andcopyeverythingfromchap09tochap10.

Page 535: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

NotesapplicationarchitectureanddeploymentconsiderationsBeforewegetintodeployingtheNotesapplication,weneedtoreviewitsarchitecture.TodeploytheNotesapplication,wemustunderstandwhatwe'replanningtodo.Wehavesegmentedtheservicesintotwogroups,asshowninthefollowingdiagram:

Theuserauthenticationservershouldbethemoresecureportionofthe

Page 536: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

system.Onourlaptop,weweren'tabletocreatetheenvisionedprotectivewallaroundthatservice,butwe'reabouttoimplementsuchprotection.

Onestrategytoenhancesecurityistoexposeasfewportsaspossible.Thatreducestheso-calledattacksurface,simplifyingourworkinhardeningtheapplicationagainstsecuritybugs.WiththeNotesapplication,wehaveexactlyoneporttoexpose,theHTTPservicethroughwhichusersaccesstheapplication.Theotherports,thetwoforMySQLserversandtheuserauthenticationserviceport,shouldbehidden.

Internally,theNotesapplicationneedstoaccessboththeNotesdatabaseandtheuserauthenticationservice.Thatservice,inturn,needstoaccesstheuserauthenticationdatabase.Ascurrentlyenvisaged,noserviceoutsidetheNotesapplicationrequiresaccesstoeitherdatabaseortotheauthenticationservice.

Implementationofthissegmentationrequireseithertwoorthreesubnets,dependingonthelengthsyouwishtogoto.Thefirst,FrontNet,containstheNotesapplicationanditsdatabase.Thesecond,AuthNet,containstheauthenticationserviceanditsdatabase.AthirdpossiblesubnetwouldcontaintheNotesandauthenticationservices.Thesubnetconfigurationmustlimitthehostswithaccesstothesubnet,andcreateasecuritywallbetweensubnets.

Page 537: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TraditionalLinuxNode.jsservicedeploymentTraditionalLinux/Unixserverapplicationdeploymentusesaninitscripttomanagebackgroundprocesses.Theyaretostarteverytimethesystembootsandcleanlyshutdownwhenthesystemishalted.Whileit'sasimplemodel,thespecificsofthisvarywidelyfromoneoperatingsystem(OS)toanother.

Acommonmethodisfortheinitprocesstomanagebackgroundprocessesusingshellscriptsintheetcinit.ddirectory.OtherOSesuseotherprocessmanagers,suchasupstartorlaunchd.

TheNode.jsprojectitselfdoesnotincludeanyscriptstomanageserverprocessesonanyOS.Node.jsismorelikeaconstructionkit,withthepiecesandpartstobuildservers,andisnotacompletepolishedserverframeworkitself.ImplementingacompletewebservicebasedonNode.jsmeanscreatingthescriptingtointegratewithprocessmanagementonyourOS.It'suptoustodevelopthosescripts.

Webserviceshavetobe:

Reliable:Forexample,toauto-restartwhentheserverprocesscrashes

Manageable:Meaningitintegrateswellwithsystemmanagementpractices

Observable:Meaningtheadministratormustbeabletogetstatusandactivityinformationfromtheservice

Page 538: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Todemonstratewhat'sinvolved,let'susePM2toimplementbackgroundserverprocessmanagementforNotes.PM2detectsthesystemtypeandcanautomaticallyintegrateitselfwiththeprocessmanagementsystem.ItwillcreateanLSB-styleinitscript(http://wiki.debian.org/LSBInitScripts),orotherscripts,asrequiredbytheprocessmanagementsystemonyourserver.

Forthisdeployment,we'llsetupasingleUbuntu17.10server.YoushouldprovisionaVirtualPrivateServer(VPS)fromahostingprovideranddoallinstallationandconfigurationthere.Rentingasmallmachineinstancefromoneofthemajorprovidersforthetimeneededtogothroughthischapterwillonlycostacoupleofdollars.

YoucanalsodothetasksinthissectionusingVirtualBoxonyourlaptop.SimplyinstallDebianorUbuntuasavirtualmachineinVirtualBox,thenfollowtheinstructionsinthissection.Itwon'tbequitethesameasusingaremoteVPShostingprovider,butdoesnotrequirerentingaserver.

BoththeNotesanduserauthenticationserviceswillbeonthatserver,alongwithasingleMySQLinstance.WhileourgoalisastrongseparationbetweenFrontNetandAuthNet,withtwoMySQLinstances,wewon'tdosoatthistime.

Page 539: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Prerequisite–provisioningthedatabasesTheLinuxpackagemanagementsystemdoesn'tallowustoinstalltwoMySQLinstances.Instead,weimplementseparationinthesameMySQLinstancebyusingseparatedatabaseswithdifferentusernamesandaccessprivilegesforeachdatabase.

ThefirststepistoensurethatMySQLisinstalledonyourserver.ForUbuntu,DigitalOceanhasafairlygoodtutorial:https://www.digitalocean.com/community/tutorials/how-to-install-mysql-on-ubuntu-14-

04.WhiletheUbuntuversionforthattutorialisold,theinstructionsarestillaccurateenough.

TheMySQLservermustsupportTCPconnectionsfromlocalhost.Edittheconfigurationfile,etcmysql/my.cnf,tohavethefollowingline:

bind-address=127.0.0.1

ThislimitsMySQLserverconnectionstotheprocessesontheserver.Amiscreantwouldhavetobreakintotheservertoaccessyourdatabase.Nowthatourdatabaseserverisavailable,let'ssetuptwodatabases.

Inthechap10/notes/modelsdirectory,createafilenamedmysql-create-db.sqlcontainingthefollowing:

CREATEDATABASEnotes;

CREATEUSER'notes'@'localhost'IDENTIFIEDBY'notes';

GRANTALLPRIVILEGESONnotes.*TO'notes'@'localhost'WITHGRANTOPTION;

Inthechap10/usersdirectory,createafilenamedmysql-create-db.sqlcontainingthefollowing:

Page 540: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

CREATEDATABASEuserauth;

CREATEUSER'userauth'@'localhost'IDENTIFIEDBY'userauth';

GRANTALLPRIVILEGESONuserauth.*TO'userauth'@'localhost'WITHGRANT

OPTION;

Wecan'trunthosescriptsontheserver,becausetheNotesapplicationhasnotyetbeencopiedtotheserver.Whenthat'saccomplished,we'llrunthescriptsas:

$mysql-uroot-p<chap10/users/mysql-create-db.sql

$mysql-uroot-p<chap10/notes/models/mysql-create-db.sql

Thiswillcreatethetwodatabases,notesanduserauth,withassociatedusernamesandpasswords.Eachusercanaccessonlytheirassociateddatabase.Later,we'llsetupNotesandtheuserauthenticationservicewithYAMLconfigurationfilestoaccessthesedatabases.

Page 541: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

<strong>$curl-sLhttps://deb.nodesource.com/setup_10.x|sudo-Ebash-</strong><br/><strong>$sudoapt-getupdate

$sudoapt-getinstall-ynodejsbuild-essential</strong>

We'veseenthisbefore,sosubstitutetheNode.jsdesiredversionnumberintheURL.InstallingthiswaymeansthatasnewNode.jsreleasesareissued,upgradesareeasilyaccomplishedwiththenormalpackagemanagementprocedures.

Page 542: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SettingupNotesanduserauthenticationontheserverBeforecopyingtheNotesanduserauthenticationcodetothisserver,let'sdoalittlecodingtoprepareforthemove.WeknowthattheNotesandauthenticationservicesmustaccesstheMySQLinstanceonlocalhostusingtheusernamesandpasswordsgivenearlier.

Usingtheapproachwe'vefollowedsofar,thismeansapairofYAMLfilesforSequelizeparameters,andchangingenvironmentvariablesinthepackage.jsonfilestomatch.

Createachap10/notes/models/sequelize-server-mysql.yamlfilecontaining:

dbname:notes

username:notes

password:notes12345

params:

host:localhost

port:3306

dialect:mysql

ItwasdiscoveredduringtestingthatasimplepasswordsuchasnoteswasnotacceptabletotheMySQLserver,andthatalongerpasswordwasrequired.Inchap10/notes/package.json,addthefollowinglinetothescriptssection:

"on-server":"SEQUELIZE_CONNECT=models/sequelize-server-mysql.yaml

NOTES_MODEL=sequelizeUSER_SERVICE_URL=http://localhost:3333PORT=3000node-

-experimental-modules./app",

Thencreateachap10/users/sequelize-server-mysql.yamlfilecontainingthefollowingcodethefollowingcode:

Page 543: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

dbname:userauth

username:userauth

password:userauth

params:

host:localhost

port:3306

dialect:mysql

Thepasswordsshownintheseconfigurationfilesobviouslywillnotpassanysecurityaudits.

Inchap10/users/package.json,addthefollowinglinetothescriptssection:

"on-server":"PORT=3333SEQUELIZE_CONNECT=sequelize-server-mysql.yamlnode--

experimental-modules./user-server",

Thisconfigurestheauthenticationservicetoaccessthedatabasesjustcreated.

Nowweneedtoselectaplaceontheservertoinstalltheapplicationcode:

#ls/opt

Thisemptydirectorylookstobeasgoodaplaceasany.Simplyuploadchap10/notesandchap10/userstoyourpreferredlocation.Beforeuploading,removethenode_modulesdirectoryinbothdirectories.That'sbothtosavetimeontheupload,andbecauseofthesimplefactthatanynative-codemodulesinstalledonyourlaptopwillbeincompatiblewiththeserver.Onyourlaptop,youmightrunacommandlikethis:

$rsync--archive--verbose./[email protected]:opt

UsetheactualIPaddressordomainnameassignedtotheserverbeingused.

Youshouldendupwithsomethinglikethefollowing:

Page 544: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

#ls/opt

notesusers

Then,ineachdirectory,runthesecommands:

#rm-rfnode_modules

#npminstall

We'rerunningthesecommandsasrootratherthanauserIDthatcanusethesudocommand.Themachineofferedbythechosenhostingprovider(DigitalOcean)isconfiguredsousersloginasroot.OtherVPShostingproviderswillprovidemachineswhereyouloginasaregularuserandthenusesudotoperformprivilegedoperations.Asyoureadtheseinstructions,payattentiontothecommandpromptweshow.We'vefollowedtheconventionwhere$isusedforcommandsrunasaregularuserand#isusedforcommandsrunasroot.Ifyou'rerunningasaregularuser,andneedtorunarootcommand,thenrunthecommandwithsudo.

Thesimplestwayofdoingthisistojustdeletethewholenode_modulesdirectoryandthenletnpminstalldoitsjob.RememberthatwesetupthePATHenvironmentvariablethefollowingway:

#exportPATH=./node_modules/.bin:${PATH}

Youcanplacethisintheloginscript(.bashrc,.cshrc,andsoon)onyourserversoit'sautomaticallyenabled.

Finally,youcannowruntheSQLscriptswrittenearliertosetupthedatabaseinstances:

#mysql-uroot-p<users/mysql-create-db.sql

#mysql-uroot-p<notes/models/mysql-create-db.sql

Thenyoushouldbeabletostartuptheservicesbyhandtocheckthateverythingisworkingcorrectly.TheMySQLinstancehasalreadybeentested,sowejustneedtostarttheuserauthenticationandNotesservices:

#cdoptusers

#DEBUG=users:*npmrunon-server

>[email protected]

Page 545: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

>PORT=3333SEQUELIZE_CONNECT=sequelize-server-mysql.yamlnode--

experimental-modules./user-server

(node:9844)ExperimentalWarning:TheESMmoduleloaderisexperimental.

ThenlogintotheserveronanotherTerminalsessionandrunthefollowing:

#cdoptusers/

#PORT=3333nodeusers-add.js

Created{id:1,username:'me',password:'w0rd',provider:'local',

familyName:'Einarrsdottir',givenName:'Ashildr',middleName:'',

emails:'[]',photos:'[]',

updatedAt:'2018-02-02T00:43:16.923Z',createdAt:'2018-02-

02T00:43:16.923Z'}

#PORT=3333nodeusers-list.js

List[{id:'me',username:'me',provider:'local',

familyName:'Einarrsdottir',givenName:'Ashildr',middleName:'',

emails:'[]',photos:'[]'}]

Theprecedingcommandbothteststhatthebackenduserauthenticationserviceisfunctioningandgivesusauseraccountwecanusetologin.Theusers-listcommanddemonstratesthatitworks.

Youmaygetanerror:

users:error/create-userError:Pleaseinstallmysql2packagemanually

ThisisgeneratedinsideofSequelize.Themysql2driverisanalternateMySQLdriver,implementedinpureJavaScript,andincludessupportforreturningPromisesforsmoothusageinasyncfunctions.Ifyoudogetthismessage,goaheadandinstallthepackageandremembertoaddthisdependencytoyourpackage.json.

NowwecanstarttheNotesservice:

#cd../notes

#npmrunon-server

Page 546: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

>[email protected]

>SEQUELIZE_CONNECT=models/sequelize-server-mysql.yamlNOTES_MODEL=sequelize

USER_SERVICE_URL=http://localhost:3333PORT=3000node--experimental-modules

./app

(node:9932)ExperimentalWarning:TheESMmoduleloaderisexperimental.

Thenwecanuseourwebbrowsertoconnecttotheapplication.Sinceyouprobablydonothaveadomainnameassociatedwiththisserver,NotescanbeaccessedviatheIPaddressoftheserver,suchashttp://159.89.145.190:3000/.

Intheseexamples,we'reusingtheIPaddressoftheVPSusedtotesttheinstructionsinthissection.TheIPaddressyouusewill,ofcourse,bedifferent.

Bynow,youknowthatthedrillforverifyingNotesisworking.Createafewnotes,openafewbrowserwindows,seethatreal-timenotificationswork,andsoon.Onceyou'resatisfiedthatNotesisworkingontheserver,killtheprocessesandmoveontothenextsection,wherewe'llsetthisuptorunwhentheserverstarts.

Page 547: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

AdjustingTwitterauthenticationtoworkontheserverTheTwitterapplicationwesetupforNotespreviouslywon'tworkbecausetheauthenticationURLisincorrectfortheserver.Fornow,wecanloginusingtheuserprofilecreatedpreviously.IfyouwanttoseeOAuthworkwithTwitter,gotoapps.twitter.comandreconfiguretheapplicationtousetheIPaddressofyourserver.

Byhostingsomewhereotherthanourlaptop,theTwittercallbackURLmustpointtothecorrectlocation.Thedefaultvaluewashttp://localhost:3000foruseonourlaptop.ButwenowneedtousetheIPaddressfortheserver.Innotes/package.json,addthefollowingenvironmentvariabletotheon-serverscript:

TWITTER_CALLBACK_HOST=http://159.89.145.190:3000

UsetheactualIPaddressordomainnameassignedtotheserverbeingused.Inarealdeployment,we'llhaveadomainnametousehere.

Page 548: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SettingupPM2tomanageNode.jsprocessesTherearemanywaystomanageserverprocesses,toensurerestartsiftheprocesscrashes,andsoon.We'llusePM2(http://pm2.keymetrics.io/)becauseit'soptimizedforNode.jsprocesses.Itbundlesprocessmanagementandmonitoringintooneapplication.

Let'screateadirectory,init,inwhichtousePM2.ThePM2websitesuggestsyouinstallthetoolgloballybut,asstudentsoftheTwelveFactorApplicationmodel,werecognizeit'sbesttouseexplicitlydeclareddependenciesandavoidglobalunmanageddependencies.

Createapackage.jsonfilecontaining:

{

"name":"pm2deploy",

"version":"1.0.0",

"scripts":{

"start":"pm2startecosystem.json",

"stop":"pm2stopecosystem.json",

"restart":"pm2restartecosystem.json",

"status":"pm2status",

"save":"pm2save",

"startup":"pm2startup"

},

"dependencies":{

"pm2":"^2.9.3"

}

}

InstallPM2usingnpminstallasusual.

InnormalPM2usage,welaunchscriptswithpm2startscript-name.js.Wecouldmakeanetcinitscriptwhichdoesthat,butPM2alsosupportsafile

Page 549: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

namedecosystem.jsonthatcanbeusedtomanageaclusterofprocesses.Wehavetwoprocessestomanagetogether,theuser-facingNotesapplication,andtheuserauthenticationserviceonthebackend.

Createafilenamedecosystem.jsoncontainingthefollowing:

{

"apps":[

{

"name":"UserAuthentication",

"script":"user-server.mjs",

"cwd":"optusers",

"node_args":"--experimental-modules",

"env":{

"PORT":"3333",

"SEQUELIZE_CONNECT":"sequelize-server-mysql.yaml"

},

"env_production":{"NODE_ENV":"production"}

},

{

"name":"Notes",

"script":"app.mjs",

"cwd":"optnotes",

"node_args":"--experimental-modules",

"env":{

"PORT":"3000",

"SEQUELIZE_CONNECT":"models/sequelize-server-mysql.yaml",

"NOTES_MODEL":"sequelize",

"USER_SERVICE_URL":"http://localhost:3333",

"TWITTER_CONSUMER_KEY":"..",

"TWITTER_CONSUMER_SECRET":"..",

"TWITTER_CALLBACK_HOST":"http://45.55.37.74:3000"

},

"env_production":{"NODE_ENV":"production"}

}

]

}

Thisfiledescribesthedirectoriescontainingbothservices,thescripttoruneachservice,thecommand-lineoptions,andtheenvironmentvariablestouse.It'sthesameinformationthatisinthepackage.jsonscripts,butspelledoutmoreclearly.AdjustTWITTER_CALLBACK_HOSTfortheIPaddressoftheserver.Fordocumentation,seehttp://pm2.keymetrics.io/docs/usage/appli

Page 550: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

cation-declaration/.

Wethenstarttheserviceswithnpmrunstart,whichlookslikethefollowingonthescreen:

YoucanagainnavigateyourbrowsertotheURLforyourserver,suchashttp://159.89.145.190:3000,andcheckthatNotesisworking.Oncestarted,someusefulcommandsareasfollows:

#pm2list

#pm2describe1

#pm2logs1

Thesecommandsletyouquerythestatusoftheservices.

Thepm2monitcommandgivesyouapseudo-graphicalmonitorofsystemactivity.Fordocumentation,

Page 551: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

seehttp://pm2.keymetrics.io/docs/usage/monitoring/.

Thepm2logscommandaddressestheapplicationlogmanagementissueweraisedelsewhere.Activitylogsshouldbetreatedasaneventstream,andshouldbecapturedandmanagedappropriately.WithPM2,theoutputisautomaticallycaptured,canbeviewed,andthelogfilescanberotatedandpurged.Seehttp://pm2.keymetrics.io/docs/usage/log-management/fordocumentation.

Ifwerestarttheserver,theseprocessesdon'tstartwiththeserver.Howdowehandlethat?It'sverysimplebecausePM2cangenerateaninitscriptforus:

#pm2save

[PM2]Savingcurrentprocesslist...

[PM2]Successfullysavedinroot.pm2/dump.pm2

#pm2startup

[PM2]InitSystemfound:systemd

Platformsystemd

Template

[Unit]

Description=PM2processmanager

Documentation=https://pm2.keymetrics.io/

After=network.target

...moreoutputisprinted

Thepm2savecommandsavesthecurrentstate.Whateverservicesarerunningatthattimewillbesavedandmanagedbythegeneratedstartupscript.

Thenextstepistogeneratethestartupscript,usingthepmstartupcommand.PM2supportsgeneratingstartupscriptsonseveralOSes,butwhenrunthisway,itautodetectsthesystemtypeandgeneratesthecorrectstartupscript.Italsoinstallsthestartupscript,andstartsitrunning.Seethedocumentationathttp://pm2.keymetrics.io/docs/usage/startup/formoreinformation.

Ifyoulookcloselyattheoutput,someusefulcommandswillbeprinted.

Page 552: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Thedetailswillvarybasedonyouroperatingsystem,becauseeachoperatingsystemhasitsowncommandsformanagingbackgroundprocesses.Inthiscase,theinstallationisgearedtousethesystemctlcommand,asverifiedbythisoutput:

Commandlist

['systemctlenablepm2-root',

'systemctlstartpm2-root',

'systemctldaemon-reload',

'systemctlstatuspm2-root']

[PM2]Writinginitconfigurationinetcsystemd/system/pm2-root.service

[PM2]Makingscriptbootingatstartup...

...

[DONE]

>>>Executingsystemctlstartpm2-root

[DONE]

>>>Executingsystemctldaemon-reload

[DONE]

>>>Executingsystemctlstatuspm2-root

Youarefreetorunthesecommandsyourself:

#systemctlstatuspm2-root

●pm2-root.service-PM2processmanager

Loaded:loaded(etcsystemd/system/pm2-root.service;enabled;vendor

preset:enabled)

Active:active(running)sinceFri2018-02-0222:27:45UTC;29minago

Docs:https://pm2.keymetrics.io/

Process:738ExecStart=optinit/node_modules/pm2/bin/pm2resurrect

(code=exited,status=0/SUCCESS)

MainPID:873(PM2v2.9.3:God)

Tasks:30(limit:4915)

Memory:171.6M

CPU:11.528s

CGroup:system.slicepm2-root.service

├─873PM2v2.9.3:GodDaemon(root.pm2)

├─895nodeoptusers/user-server.mjs

└─904nodeoptnotes/app.mjs

ToverifythatPM2startstheservicesasadvertised,rebootyourserver,thenusePM2tocheckthestatus:

Page 553: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Thefirstthingtonoticeisthatuponinitiallyloggingintotherootaccount,thepm2statuscommandisnotavailable.WeinstalledPM2locallytooptinit,andthecommandisonlyavailableinthatdirectory.

Aftergoingtothatdirectory,wecannowrunthecommandandseethestatus.RemembertosetthecorrectIPaddressordomainnameintheTWITTER_CALLBACK_HOSTenvironmentvariable.Otherwise,logginginwithTwitterwillfail.

WenowhavetheNotesapplicationunderafairlygoodmanagementsystem.Wecaneasilyupdateitscodeontheserverandrestarttheservice.Iftheservicecrashes,PM2willautomaticallyrestartit.Logfilesareautomaticallykeptforourperusal.

PM2alsosupportsdeploymentfromthesourceonourlaptop,whichwecanpushtostagingorproductionenvironments.Tosupportthis,wemustadddeploymentinformationtotheecosystem.jsonfileandthenrunthepm2deploycommandtopushthecodetotheserver.SeethePM2websiteformoreinformation:http://pm2.keymetrics.io/docs/usage/deployment/.

WhilePM2doesagoodjobatmanagingserverprocesses,thesystem

Page 554: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

we'vedevelopedisinsufficientforaninternet-scaleservice.WhatiftheNotesapplicationweretobecomeaviralhitandsuddenlyweneedtodeployamillionserversspreadaroundtheplanet?Deployingandmaintainingserversoneatatime,likethis,isnotscalable.

Wealsoskippedoverimplementingthearchitecturaldecisionsatthebeginning.Puttingtheuserauthenticationdataonthesameserverisasecurityrisk.Wewanttodeploythatdataonadifferentserver,undertightersecurity.

Inthenextsection,we'llexploreanewsystem,Docker,thatsolvestheseproblemsandmore.

Page 555: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Node.jsmicroservicedeploymentwithDockerDocker(http://docker.com)isthenewattractioninthesoftwareindustry.Interestistakingofflikecrazy,spawningmanyprojects,oftenwithnamescontainingpunsaroundshippingcontainers.

Itisdescribedasanopenplatformfordistributedapplicationsfordevelopersandsysadmins.ItisdesignedaroundLinuxcontainerizationtechnologyandfocusesondescribingtheconfigurationofsoftwareonanyvariantofLinux.

Dockerautomatestheapplicationdeploymentwithinsoftwarecontainers.ThebasicconceptsofLinuxcontainersdatebacktochrootjail'sfirstimplementationinthe1970s,andothersystemssuchasSolarisZones.TheDockerimplementationcreatesalayerofsoftwareisolationandvirtualizationbasedonLinuxcgroups,kernelnamespaces,andunion-capablefilesystems,whichblendtogethertomakeDockerwhatitis.Thatwassomeheavygeek-speak,solet'stryasimplerexplanation.

ADockercontainerisarunninginstantiationofaDockerimage.AnimageisagivenLinuxOSandapplicationconfigurationdesignedbydevelopersforwhateverpurposetheyhaveinmind.DevelopersdescribeanimageusingaDockerfile.TheDockerfileisafairlysimple-to-writescriptshowingDockerhowtobuildanimage.Dockerimagesaredesignedtobecopiedtoanyserver,wheretheimageisinstantiatedasaDockercontainer.

Arunningcontainerwillmakeyoufeellikeyou'reinsideavirtualserverrunningonavirtualmachine.ButDockercontainerizationisverydifferentfromavirtualmachinesystemsuchasVirtualBox.TheprocessesrunninginsidethecontainerareactuallyrunningonthehostOS.The

Page 556: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

containerizationtechnology(cgroups,kernelnamespaces,andsoon)createtheillusionofrunningontheLinuxvariantspecifiedintheDockerfile,evenifthehostOSiscompletelydifferent.YourhostOScouldbeUbuntuandthecontainerOScouldbeFedoraorOpenSUSE;Dockermakesitallwork.

Bycontrast,withVirtualMachinesoftware(VirtualBox,andVMWare,amongothers),you'reusingwhatfeelslikearealcomputer.ThereisavirtualBIOSandvirtualizedsystemhardware,andyoumustinstallafull-fledgedguestOS.Youmustfolloweveryritualofcomputerownership,includingsecuringlicensesifit'saclosedsourcesystemsuchasWindows.

WhileDockerisprimarilytargetedatx86flavorsofLinux,itisavailableonseveralARM-basedOSes,aswellasotherprocessors.YoucanevenrunDockeronsingle-boardcomputers,suchasRaspberryPis,forhardware-orientedInternetofThingsprojects.OperatingsystemssuchasResin.IOareoptimizedtosolelyrunDockercontainers.

TheDockerecosystemcontainsmanytools,andtheirnumberisquicklyincreasing.Forourpurposes,we'llbefocusingonthefollowingthreespecifictools:

Dockerengine:Thisisthecoreexecutionsystemthatorchestrateseverything.ItrunsonaLinuxhostsystem,exposinganetwork-basedAPIthatclientapplicationsusetomakeDockerrequests,suchasbuilding,deploying,andrunningcontainers.

Dockermachine:ThisisaclientapplicationperformingfunctionsaroundprovisioningDockerEngineinstancesonhostcomputers.

Dockercompose:Thishelpsyoudefine,inasinglefile,amulticontainerapplication,withallitsdependenciesdefined.

WiththeDockerecosystem,youcancreateawholeuniverseofsubnets

Page 557: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

andservicestoimplementyourdreamapplication.Thatuniversecanrunonyourlaptoporbedeployedtoaglobe-spanningnetworkofcloud-hostingfacilitiesaroundtheworld.Thesurfaceareathroughwhichmiscreantscanattackisstrictlydefinedbythedeveloper.Amulticontainerapplicationwillevenlimitaccesssostronglybetweenservicesthatmiscreantswhodomanagetobreakintoacontainerwillfinditdifficulttobreakoutofthecontainer.

UsingDocker,we'llfirstdesignonourlaptopthesystemshowninthepreviousdiagram.Thenwe'llmigratethatsystemtoaDockerinstanceonaserver.

Page 558: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

InstallingDockeronyourlaptopThebestplacetolearnhowtoinstallDockeronyourlaptopistheDockerdocumentationwebsite.Whatwe'relookingforistheDockerCommunityEdition(CE).ThereistheDockerEnterpriseEdition(EE),withmorefeaturesandsomeopportunitiestopaysupportfees:

macOSinstallation–https://docs.docker.com/docker-for-mac/install/

Windowsinstallation–https://docs.docker.com/docker-for-windows/install/

Ubuntuinstallation–https://docs.docker.com/install/linux/docker-ce/ubuntu/

Instructionsareavailableforseveralotherdistros.SomeusefulpostinstallLinuxinstructionsareathttps://docs.docker.com/install/linux/linux-postinstall/

BecauseDockerrunsonLinux,itdoesnotrunnativelyonmacOSorWindows.InstallationoneitherOSrequiresinstallingLinuxinsideavirtualmachineandthenrunningDockertoolswithinthatvirtualLinuxmachine.Thedayswhenyouhadtohandcraftthatsetupyourselfarelonggone.TheDockerteamhasmadethiseasybydevelopingeasy-to-useDockerapplicationsforMacandWindows.TheDockerforWindowsandDockerforMacbundlespackagetheDockertoolsandlightweightvirtualmachinesoftware.Theresultisverylightweight,andtheDockercontainerscanbeleftrunninginthebackgroundwithlittleimpact.

Page 559: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

YoumayfindreferencestoDockerToolboxasthemethodtoinstallDockeronmacOS.Thatapplicationislonggone,andhasbeenreplacedbyDockerforWindowsandDockerforMac.

Page 560: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

StartingDockerwithDockerforWindows/macOSTostartDockerforWindowsorMacisverysimple.Yousimplyfindanddouble-clickontheapplicationicon.Itlaunchesaswouldanyothernativeapplication.Whenstarted,itmanagesavirtualmachine(notVirtualBox)withinwhichisaLinuxinstancerunningtheDockerEngine.OnmacOS,amenubariconshowsupwithwhichyoucontrolDocker.app,andonWindows,aniconisavailableinthesystemtray.

TherearesettingsavailablesothatDockerautomaticallylauncheseverytimeyoustartyourlaptop.

Onboth,theCPUmustsupportVirtualization.BundledinsideDockerforWindowsandDockerforMacisanultra-lightweighthypervisor,which,inturn,requiresvirtualizationsupportfromtheCPU.

ForWindows,thismayrequireBIOSconfiguration.Seehttps://docs.docker.com/docker-for-windows/troubleshoot/#virtualization-must-be-enabled.

ForMac,thisrequireshardwarefrom2010ornewer,withIntel’shardwaresupportformemorymanagementunit(MMU)virtualization,includingExtendedPageTables(EPT)andUnrestrictedMode.Youcancheckforthissupportbyrunningsysctlkern.hv_support.ItalsorequiresmacOS10.11orlater.

Page 561: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

KickingthetiresofDockerWiththesetupaccomplished,wecanusethelocalDockerinstancetocreateDockercontainers,runafewcommands,and,ingeneral,learnhowtousethisamazingsystem.

Asinsomanysoftwarejourneys,thisonestartswithsayingHelloWorld:

$dockerrunhello-world

Unabletofindimage'hello-world:latest'locally

latest:Pullingfromlibrary/hello-world

ca4f61b1923c:Pullcomplete

Digest:

sha256:66ef312bbac49c39a89aa9bcc3cb4f3c9e7de3788c944158df3ee0176d32b751

Status:Downloadednewerimageforhello-world:latest

HellofromDocker!

Thismessageshowsthatyourinstallationappearstobeworkingcorrectly.

Togeneratethismessage,Dockertookthefollowingsteps:

1.TheDockerclientcontactedtheDockerdaemon.

2.TheDockerdaemonpulledthe"hello-world"imagefromtheDockerHub.

(amd64)

3.TheDockerdaemoncreatedanewcontainerfromthatimagewhichrunsthe

executablethatproducestheoutputyouarecurrentlyreading.

4.TheDockerdaemonstreamedthatoutputtotheDockerclient,whichsent

it

toyourterminal.

Totrysomethingmoreambitious,youcanrunanUbuntucontainerwith:

$dockerrun-itubuntubash

Shareimages,automateworkflows,andmorewithafreeDockerID:

https://cloud.docker.com/

Formoreexamplesandideas,visit:

https://docs.docker.com/engine/userguide/

ThedockerruncommanddownloadsaDockerimage,namedonthe

Page 562: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

commandline,initializesaDockercontainerfromthatimage,andthenrunsthatcontainer.Inthiscase,theimage,namedhello-world,wasnotpresentonthelocalcomputerandhadtobedownloadedandinitialized.Oncethatwasdone,thehello-worldcontainerwasexecutedanditprintedouttheseinstructions.

Youcanqueryyourcomputertoseethatwhilethehello-worldcontainerhasexecutedandfinished,itstillexists:

ThedockerpscommandliststherunningDockercontainers.Asweseehere,thehello-worldcontainerisnolongerrunning,butwiththe-aswitch,dockerpsalsoshowsthosecontainersthatexistbutarenotcurrentlyrunning.WealsoseethatthiscomputerhasaNextcloudinstanceinstalledalongwithitsassociateddatabase.

Whenyou'redoneusingacontainer,youcancleanupwiththefollowingcommand:

$dockerrmboring_lumiere

boring_lumiere

Thenameboring_lumiereisthecontainernameautomaticallygeneratedbyDocker.Whiletheimagenamewashello-world,that'snotthecontainername.Dockergeneratedthecontainernamesoyouhaveamoreuser-friendlyidentifierforthecontainersthanthehexIDshowninthecontainerIDcolumn.Whencreatingacontainer,it'seasytospecifyanycontainernameyoulike.

Page 563: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

CreatingtheAuthNetfortheuserauthenticationserviceWithallthattheoryspinningaroundourheads,it'stimetodosomethingpractical.Let'sstartbysettinguptheuserauthenticationservice.Inthediagramshownearlier,thiswillbetheboxlabeledAuthNetcontainingaMySQLinstanceandtheauthenticationserver.

Page 564: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

MySQLcontainerforDockerTofindpubliclyavailableDockerimages,gotohttps://hub.docker.com/andsearch.You'llfindmanyDockerimagesreadytogo.Forexample,Nextcloud,anditsassociateddatabase,wasshownearlierinstalledalongsidethehello-worldapplicationwhenwekickedthetires.Bothareavailablefromtheirrespectiveprojectteamsandit'ssimply(moreorless)amatteroftypingdockerrunnextcloudtoinstallandrunthecontainers.TheprocessofinstallingNextcloud,anditsassociateddatabase,aswellasmanyotherpackagedapplications,suchasGitLab,isverysimilartowhatwe'reabouttodotobuildAuthNet,sotheskillsyou'reabouttolearnareverypractical.

JustforMySQL,thereareover11,000containersavailable.Fortunately,thetwocontainersprovidedbytheMySQLteamareverypopularandeasytouse.Themysql/mysql-serverimageisalittleeasiertoconfigure,solet'susethat.

ADockerimagenamecanbespecified,alongwithatagthatisusuallythesoftwareversionnumber.Inthiscase,we'llusemysql/mysql-server:5.7,wheremysql/mysql-serveristhecontainername,and5.7isthetag.MySQL5.7isthecurrentGArelease.Downloadtheimageasfollows:

$dockerpullmysql/mysql-server:5.7

5.7:Pullingfrommysql/mysql-server

4040fe120662:Pullcomplete

d049aa45d358:Pullcomplete

a6c7ed00840d:Pullcomplete

853789d8032e:Pullcomplete

Digest:

sha256:1b4c7c24df07fa89cdb7fe1c2eb94fbd2c7bd84ac14bd1779e3dec79f75f37c5

Status:Downloadednewerimageformysql/mysql-server:5.7

Thisdownloadedfourimagesintotal,becausethisimageisbuiltontopof

Page 565: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

threeotherimages.We'llseelaterhowthatworkswhenwelearnhowtobuildaDockerfile.

Acontainercanbestartedusingthisimageasfollows:

$dockerrun--name=mysql--envMYSQL_ROOT_PASSWORD=f00barmysql/mysql-

server:5.7

[Entrypoint]MySQLDockerImage5.7.21-1.1.4

[Entrypoint]Initializingdatabase

[Entrypoint]Databaseinitialized

...

[Entrypoint]ignoringdocker-entrypoint-initdb.d*

[Entrypoint]Servershutdown

[Entrypoint]MySQLinitprocessdone.Readyforstartup.

[Entrypoint]StartingMySQL5.7.21-1.1.4

Westartedthisserviceintheforeground.Thecontainernameismysql.Wesetanenvironmentvariable,which,inturn(accordingtotheimagedocumentation),initializestherootpasswordasshown.Inanotherwindow,wecangetintothecontainerandruntheMySQLclientasfollows:

$dockerexec-itmysqlmysql-uroot-p

Enterpassword:

WelcometotheMySQLmonitor.Commandsendwith;or\g.

YourMySQLconnectionidis4

Serverversion:5.7.21MySQLCommunityServer(GPL)

Copyright(c)2000,2018,Oracleand/oritsaffiliates.Allrightsreserved.

OracleisaregisteredtrademarkofOracleCorporationand/orits

affiliates.Othernamesmaybetrademarksoftheirrespective

owners.

Type'help;'or'\h'forhelp.Type'\c'toclearthecurrentinput

statement.

mysql>showdatabases;

+--------------------+

|Database|

+--------------------+

|information_schema|

|mysql|

|performance_schema|

Page 566: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

|sys|

+--------------------+

4rowsinset(0.00sec)

mysql>

Thedockerexeccommandletsyourunprogramsinsidethecontainer.The-itoptionsaysthecommandisruninteractively,onanassignedterminal.Substitutebashformysql,andyouhaveaninteractivebashcommandshell.

Thismysqlcommandinstanceisrunninginsidethecontainer.Thecontainerisconfiguredbydefaulttonotexposeanyexternalport,andithasadefaultmy.cnffile.

Thedatabasefilesarelockedinsidethecontainer.Assoonasthatcontainerisdeleted,thedatabasewillgoaway.Dockercontainersaremeanttobeephemeral,beingcreatedanddestroyedasneeded,whiledatabasesaremeanttobepermanent,withlifetimesmeasuredindecadessometimes.

Inotherwords,it'scoolthatwecaneasilyinstallandlaunchaMySQLinstance.Butthereareseveraldeficiencies:

Accesstothedatabasefromothersoftware

Storingthedatabasefilesoutsidethecontainerforalongerlifespan

Customconfiguration,becausedatabaseadminslovetotweakthesettings

ItneedstobeconnectedtoAuthNetalongwiththeuserauthenticationservice

Beforeproceeding,let'scleanup.InaTerminalwindow,type:

Page 567: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

$dockerstopmysql

mysql

$dockerrmmysql

mysql

Thisclosesoutandcleansupthecontainers.And,toreiteratethepointmadeearlier,thedatabaseinthatcontainerwentaway.Ifthatdatabasecontainedcriticalinformation,youjustlostitwithnochancetorecoverthedata.

Page 568: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

InitializingAuthNetDockersupportscreatingvirtualbridgenetworksbetweencontainers.RememberthataDockercontainerhasmanyofthefeaturesofaninstalledLinuxOS.EachcontainercanhaveitsownIPaddress(es)andexposedports.DockersupportscreatingwhatamountstobeingavirtualEthernetsegment,calledabridgenetwork.Thesenetworkslivesolelywithinthehostcomputerand,bydefault,arenotreachablebyanythingoutsidethehostcomputer.

ADockerbridgenetwork,therefore,hasstrictlylimitedaccess.AnyDockercontainerattachedtoabridgenetworkcancommunicatewithothercontainersattachedtothatnetwork.Thecontainersfindeachotherbyhostname,andDockerincludesanembeddedDNSservertosetupthehostnamesrequired.ThatDNSserverisconfiguredtonotrequiredotsindomainnames,meaningthattheDNS/hostnameofeachcontainerissimplythecontainername,ratherthansomethingsuchascontainer-name.service.ThispolicyofusinghostnamestoidentifycontainersisDocker'simplementationofservicediscovery.

Createadirectorynamedauthnetasasiblingtotheusersandnotesdirectories.We'llbeworkingonAuthNetinthatdirectory.

Createafile,buildauthnet.sh,containingthefollowing:

dockernetworkcreate--driverbridgeauthnet

Typethefollowing:

$sh-xbuildauthnet.sh

+dockernetworkcreate--driverbridgeauthnet

3021e2069278c2acb08d94a2d31507a43f089db1c02eecc97792414b498eb785

Page 569: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ThiscreatesaDockerbridgenetwork.

Page 570: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ScriptexecutiononWindowsExecutingscriptsonWindowsisdifferentbecauseitusesPowerShellratherthanbash,andalargenumberofotherconsiderations.Forthis,andthescriptswhichfollow,makethesechanges.

Powershellscriptfilenamesmustendwiththe.ps1extension.Formostofthesescripts,that'sallthatisrequiredbecausethescriptsaresosimple.Toexecutethescript,simplytype.\scriptname.ps1inthePowershellwindow.Inotherwords,onWindows,thescriptjustshownmustbenamedbuildauthnet.ps1,andisexecutedas.\buildauthnet.ps1.

Toexecutethescripts,youmayneedtochangethePowershellExecutionPolicy:

PSC:\Users\david\chap10\authnet>Get-ExecutionPolicy

Restricted

PSC:\Users\david\chap10\authnet>Set-ExecutionPolicyUnrestricted

Obviously,therearesecurityconsiderationswiththischange,sochangetheExecutionPolicybackwhenyou'redone.

AsimplermethodonWindowsistosimplypastethesecommandsintoaPowerShellwindow.

Page 571: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

LinkingDockercontainersIntheolderdaysofDocker,weweretoldtolinkcontainersusingthe--linkoption.Withthatoption,Dockerwouldcreateentriesinetchostssothatonecontainercanrefertoanothercontainerbyitshostname.ThatoptionalsoarrangedaccesstoTCPportsandvolumesbetweenlinkedcontainers.Thisallowedthecreationofmulticontainerservices,usingprivateTCPportsforcommunicationthatexposednothingtoprocessesoutsidethecontainers.

Today,wearetoldthatthe--linkoptionisalegacyfeature,andthatinsteadweshouldusebridgenetworks.Inthischapter,we'llfocussolelyonusingbridgenetworks.

Youcanlistthenetworksasfollows:

$dockernetworkls

NETWORKIDNAMEDRIVERSCOPE

3021e2069278authnetbridgelocal

Lookatdetailsaboutthenetworkwiththiscommand:

$dockernetworkinspectauthnet

...muchJSONoutput

Atthemoment,thiswon'tshowanycontainersattachedtoauthnet.Theoutputshowsthenetworkname,theIPrangeofthisnetwork,thedefaultgateway,andotherusefulnetworkconfigurationinformation.Sincenothingisconnectedtothenetwork,let'sgetstartedwithbuildingtherequiredcontainers.

Page 572: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Thedb-userauthcontainerNowthatwehaveanetwork,wecanstartconnectingcontainerstothatnetwork.Andthenwe'llexplorethecontainerstoseehowprivatetheyare.

Createascript,startdb.sh,containing:

dockerrun--namedb-userauth--envMYSQL_RANDOM_ROOT_PASSWORD=true\

--envMYSQL_USER=userauth--envMYSQL_PASSWORD=userauth\

--envMYSQL_DATABASE=userauth\

--volume`pwd`/my.cnf:etcmy.cnf\

--volume`pwd`/../userauth-data:varlib/mysql\

--networkauthnetmysql/mysql-server:5.7

OnWindows,youwillneedtonamethescriptstartdb.ps1instead,andputthetextallononelineratherthanextendthelineswithbackslashes.And,thevolumemountedonvarlib/mysqlmustbecreatedseparately.Usethesecommandsinstead:

dockervolumecreatedb-userauth-volume

dockerrun--namedb-userauth--envMYSQL_RANDOM_ROOT_PASSWORD=true--env

MYSQL_USER=userauth--envMYSQL_PASSWORD=userauth--env

MYSQL_DATABASE=userauth--volume$PSScriptRoot\my.cnf:/etc/my.cnf--volume

db-userauth-volume:/var/lib/mysql--networkauthnetmysql/mysql-server:5.7

Whenrun,thecontainerwillbenameddb-userauth.Togivealittlebitofsecurity,therootpasswordhasbeenrandomized.We'veinsteaddefinedadatabasenameduserauth,accessedbyausernameduserauth,usingthepassworduserauth.That'snotexactlysecure,sofeelfreetochoosebetternamesandpasswords.Thecontainerisattachedtotheauthnetnetwork.

Therearetwo--volumeoptionsthatwemusttalkabout.InDockerese,avolumeisathinginsideacontainerthatcanbemountedfromoutside

Page 573: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

thecontainer.Inthiscase,we'redefiningavolume,userauth-data,inthehostfilesystemtobemountedasvarlib/mysqlinsidethecontainer.And,we'redefiningalocalmy.cnffiletobeusedasetcmy.cnfinsidethecontainer.

FortheWindowsversion,wehavetwochangestothe--volumemounts.Wespecifythemountforetcmy.cnfas$PSScriptRoot\my.cnf:etcmy.cnf,becausethat'showyoureferencealocalfileinPowershell.

Forvarlib/mysql,wereferencedaseparatelycreatedvolume.Thevolumeiscreatedusingthevolumecreatecommand,andwiththatcommandthereisnoopportunitytocontrolthelocationofthevolume.It'simportantthatthevolumelivesoutsidethecontainer,sothatthedatabasefilessurvivethedestruction/creationcycleforthiscontainer.

Takentogether,thosesettingsmeanthedatabasefilesandtheconfigurationfileliveoutsidethecontainerandwillthereforeexistbeyondthelifetimeofonespecificcontainer.Togetthemy.cnf,youwillhavetorunthecontaineroncewithoutthe--volume`pwd`/my.cnf:etcmy.cnfoptionsoyoucancopythedefaultmy.cnffileintotheauthnetdirectory.

Runthescriptoncewithoutthatoption:

$shstartdb.sh

...muchoutput

[Entrypoint]GENERATEDROOTPASSWORD:UMyh@q]@j4qijyj@wK4s4SkePIkq

...muchoutput

Theoutputissimilartowhatwesawearlier,butforthisnewlinegivingtherandomizedpassword:

$dockernetworkinspectauthnet

Thiswilltellyouthedb-userauthcontainerisattachedtoauthnet:

$dockerexec-itdb-userauthmysql-uuserauth-p

Enterpassword:

WelcometotheMySQLmonitor.Commandsendwith;or\g.

Page 574: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

...muchoutput

mysql>showdatabases;

+--------------------+

|Database|

+--------------------+

|information_schema|

|userauth|

+--------------------+

2rowsinset(0.00sec)

mysql>useuserauth;

Databasechanged

mysql>showtables;

Emptyset(0.00sec)

Weseeourdatabasehasbeencreatedandit'sempty.Butwedidthissowecouldgrabthemy.cnffile:

$dockercpdb-userauth:etcmy.cnf.

$ls

my.cnfmysql-datastartdb.sh

Thedockercpcommandisusedforcopyingfilesinandoutofcontainers.Ifyou'veusedscp,thesyntaxwillbefamiliar.

Onceyouhavethemy.cnffile,there'sabigpileofsettingchangesyoumightwanttomake.Thefirstspecificchangetomakeiscommentingoutthelinereadingsocket=varlib/mysql/mysql.sock,andthesecondisaddingalinereadingbind-address=0.0.0.0.ThepurposewiththesechangesistoconfiguretheMySQLservicetolistenonaTCPportratherthanaUnixdomainsocket.ThismakesitpossibletocommunicatewiththeMySQLservicefromoutsidethecontainer.Theresultwouldbe:

#socket=varlib/mysql/mysql.sock

bind-address=0.0.0.0

Nowstopthedb-userauthservice,andremovethecontainer,aswedidearlier.Editthestartdbscripttoenablethelinemountingetcmy.cnfintothecontainer,andthenrestartthecontainer:

Page 575: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

$dockerstopdb-userauth

db-userauth

$dockerrmdb-userauth

db-userauth

$sh./startdb.sh

[Entrypoint]MySQLDockerImage5.7.21-1.1.4

[Entrypoint]StartingMySQL5.7.21-1.1.4

Now,ifweinspecttheauthnetnetwork,weseethefollowing:

$dockernetworkinspectauthnet

"Name":"authnet",

...

"Subnet":"172.18.0.0/16",

"Gateway":"172.18.0.1"

...

"Containers":{

"Name":"db-userauth",

"MacAddress":"02:42:ac:12:00:02",

"IPv4Address":"172.18.0.2/16",

...

Inotherwords,theauthnetnetworkhasthenetworknumber172.18.0.0/16,andthedb-userauthcontainerwasassigned172.18.0.2.Thislevelofdetailisrarelyimportant,butitisusefulonourfirsttimethroughtocarefullyexaminethesetupsoweunderstandwhatwe'redealingwith:

#catetcresolv.conf

searchattlocal.net

nameserver127.0.0.11

optionsndots:0

Aswesaidearlier,thereisaDNSserverrunningwithintheDockerbridgenetworksetup,anddomainnameresolutionisconfiguredtousenodots.That'ssoDockercontainernamesaretheDNShostnameforthecontainer:

#mysql-hdb-userauth-uuserauth-p

Enterpassword:

WelcometotheMySQLmonitor.Commandsendwith;or\g.

YourMySQLconnectionidis33

Serverversion:5.7.21MySQLCommunityServer(GPL)

Page 576: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

AccesstheMySQLserverusingthecontainernameasthehostname.

Page 577: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

DockerfilefortheauthenticationserviceIntheusersdirectory,createafilenamedDockerfilecontainingthefollowing:

FROMnode:10

ENVDEBUG="users:*"

ENVPORT="3333"

ENVSEQUELIZE_CONNECT="sequelize-docker-mysql.yaml"

ENVREST_LISTEN="0.0.0.0"

RUNmkdir-p/userauth

COPYpackage.jsonsequelize-docker-mysql.yaml.mjs.jsuserauth

WORKDIR/userauth

RUNapt-getupdate-y\

&&apt-get-yinstallcurlpythonbuild-essentialgitca-certificates\

&&npminstall--unsafe-perm

EXPOSE3333

CMDnpmrundocker

Dockerfilesdescribetheinstallationofanapplicationonaserver.Seehttps://docs.docker.com/engine/reference/builder/fordocumentation.TheydocumentassemblyofthebitsinaDockercontainerimage,andtheinstructionsinaDockerfileareusedtobuildaDockerimage.

TheFROMcommandspecifiesapre-existingimagefromwhichtoderiveagivenimage.Wetalkedaboutthisearlier;youcanbuildaDockercontainerstartingfromanexistingimage.TheofficialNode.jsDockerimage(https://hub.docker.com/_/node/)we'reusingisderivedfromdebian:jessie.Therefore,commandsavailablewithinthecontainerarewhatDebianoffers,andweuseapt-gettoinstallmorepackages.WeuseNode.js10becauseitsupportsES6modulesandtheotherfeatureswe've

Page 578: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

beenusing.

TheENVcommandsdefineenvironmentvariables.Inthiscase,we'reusingthesameenvironmentvariablesdefinedwithintheuserauthenticationservice,exceptwehaveanewREST_LISTENvariable.We'lltakealookatthatshortly.

TheRUNcommandsarewhereweruntheshellcommandsrequiredtobuildthecontainer.Thefirstthingistomakea/userauthdirectorythatwillcontaintheservicesourcecode.TheCOPYcommandcopiesfilesintothatdirectory.Andthenwe'llneedtorunannpminstallsothatwecanruntheservice.ButfirstweusetheWORKDIRcommandtomovethecurrentworkingdirectoryinto/userauthsothatthenpminstallisruninthecorrectplace.WealsoinstalltherequisiteDebianpackagessothatanynativecodeNode.jspackagescanbeinstalled.

It'srecommendedthatyoualwayscombineapt-getupdatewithapt-getinstallinthesamecommandline,likethis,becauseoftheDockerbuildcache.Whenrebuildinganimage,Dockerstartswiththefirstchangedline.Byputtingthosetwotogether,youensurethatapt-getupdateisexecutedanytimeyouchangethelistofpackagestobeinstalled.Foracompletediscussion,seethedocumentationathttps://docs.docker.com/develop/develop-images/dockerfile_best-practices/.

Attheendofthiscommandisnpminstall--unsafe-perm.Theissuehereisthatthesecommandsarebeingrunasroot.Normally,whennpmisrunasroot,itchangesitsuserIDtoanonprivilegeduser.Thiscancausefailure,however,andthe--unsafe-permoptionpreventschangingtheuserID.

TheEXPOSEcommandinformsDockerthatthecontainerlistensonthenamedTCPport.Thisdoesnotexposetheportbeyondthecontainer.

Finally,theCMDcommanddocumentstheprocesstolaunchwhenthecontainerisexecuted.TheRUNcommandsareexecutedwhilebuildingthecontainer,whileCMDsayswhat'sexecutedwhenthecontainerstarts.

Page 579: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

WecouldhaveinstalledPM2inthecontainer,thenusedaPM2commandtolaunchtheservice.ButDockerisabletofulfillthesamefunction,becauseitsupportsautomaticallyrestartingacontaineriftheserviceprocessdies.We'llseehowtodothislater.

Page 580: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ConfiguringtheauthenticationserviceforDockerWe'reusingadifferentfileforSEQUELIZE_CONNECT.Createanewfilenamedusers/sequelize-docker-mysql.yamlcontainingthefollowing:

dbname:userauth

username:userauth

password:userauth

params:

host:db-userauth

port:3306

dialect:mysql

Thedifferenceisthatinsteadoflocalhostasthedatabasehost,weusedb-userauth.Earlier,weexploredthedb-userauthcontaineranddeterminedthatwasthehostnameofthecontainer.Byusingdb-userauthinthisfile,theauthenticationservicewillusethedatabaseinthecontainer.

NowweneedtotakecareoftheenvironmentvariablenamedREST_LISTEN.Previously,theauthenticationserverhadlistenedonlytohttp://localhost:3333.We'ddonethisforsecuritypurposes,thatis,tolimitwhichprocessescouldconnecttotheservice.UnderDocker,weneedtoconnecttothisservicefromoutsideitscontainersothatothercontainerscanconnecttothisservice.Therefore,itmustlistentoconnectionsfromoutsidethelocalhost.Inusers-server.mjs,weneedtomakethefollowingchange:

server.listen(process.env.PORT,

process.env.REST_LISTEN?process.env.REST_LISTEN:"localhost",

()=>{log(server.name+'listeningat'+server.url);});

Thatis,iftheREST_LISTENvariableexists,theRESTserveristoldtolistento

Page 581: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

whateveritsays,otherwisetheserviceistolistentolocalhost.WiththeenvironmentvariableintheDockerfile,theauthenticationservicewilllistentotheworld(0.0.0.0).Arewethrowingcautiontothewindandabrogatingourfiduciarydutyinkeepingthesacredtrustofstoringallthisuseridentificationinformation?No.Bepatient.We'lldescribemomentarilyhowtoconnectthisserviceanditsdatabasetoAuthNetandwillpreventaccesstoAuthNetbyanyotherprocess.

Page 582: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

BuildingandrunningtheauthenticationserviceDockercontainerInusers/package.jsonaddthefollowinglinetothescriptssection:

"docker":"node--experimental-modules./user-server",

"docker-build":"dockerbuild-tnode-web-development/userauth."

Previously,we'veputtheconfigurationenvironmentvariablesintopackage.json.Inthiscase,theconfigurationenvironmentvariablesareintheDockerfile.ThismeansweneedawaytoruntheserverwithnoenvironmentvariablesotherthanthoseintheDockerfile.Withthisscriptsentry,wecandonpmrundockerandthentheDockerfileenvironmentvariableswillsupplyallconfiguration.

Wecanbuildtheauthenticationserviceasfollows:

$npmrundocker-build

>[email protected]/chap10/users

>dockerbuild-tnode-web-development/userauth.

SendingbuildcontexttoDockerdaemon33.8MB

Step1/11:FROMnode:9.5

--->a696309517c6

Step2/11:ENVDEBUG="users:*"

--->Usingcache

--->f8cc103432e8

Step3/11:ENVPORT="3333"

--->Usingcache

--->39b24b8b554e

...moreoutput

Page 583: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ThedockerbuildcommandbuildsacontainerfromaDockerfile.Aswesaidearlier,theprocessbeginswiththeimagedefinedintheFROMcommand.Thenthebuildproceedsstepbystep,andtheoutputshowsliterallyeachstepasitisexecuted.

Thencreateascript,authnet/startserver.sh,oronWindowscallitstartserver.ps1,containingthefollowingcommand:

dockerrun-it--nameuserauth--net=authnetnode-web-development/userauth

Thislaunchesthenewlybuiltcontainer,givingitthenameuserauth,attachingittoauthnet:

$sh-xstartserver.sh

+dockerrun-it--nameuserauth--net=authnetnode-web-development/userauth

>[email protected]/userauth

>node--experimental-modules./user-server

(node:17)ExperimentalWarning:TheESMmoduleloaderisexperimental.

users:serviceUserAuth-Servicelisteningathttp://0.0.0.0:3333+0ms

Thatstartstheuserauthenticationservice.OnWindows,startitas.\startserver.ps1.Youshouldrecallthatit'saRESTservice,andthereforerunningitthroughitspacesisdonewithusers-add.jsandtheotherscripts.But,sincewedidnotexposeapublicportfromtheservicewemustrunthosescriptsfrominsidethecontainer.

Wedeterminewhetheracontainerexposesapublicportinoneoftwoways.Theeasiestisrunningdockerps-aandviewingthecontainerlistingdetails.ThereisacolumnmarkedPORTS,andforuserauthwesee3333/tcp.ThisisasideeffectoftheEXPOSEcommandintheDockerfile.Ifthatportwereexposed,itwouldappearinthePORTScolumnas0.0.0.0:3333->3333/tcp.Rememberthegoalfortheuserauthcontainer,andauthnetoverall,wasthatitwouldnotbepubliclyaccessiblebecauseofsecurityconcerns.

Page 584: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ExploringAuthnetLet'sexplorewhatwejustcreated:

$dockernetworkinspectauthnet

ThisprintsoutalargeJSONobjectdescribingthenetwork,anditsattachedcontainers,whichwe'velookedatbefore.Ifallwentwell,we'llseetherearenowtwocontainersattachedtoauthnetwherethere'dpreviouslybeenonlyone.

Let'sgointotheuserauthcontainerandpokearound:

$dockerexec-ituserauthbash

root@a29d833287bf:/userauth#ls

node_modulesuser-server.mjsusers-list.js

package-lock.jsonusers-add.jsusers-sequelize.mjs

package.jsonusers-delete.js

sequelize-docker-mysql.yamlusers-find.js

The/userauthdirectoryisinsidethecontainerandisexactlythefilesplacedinthecontainerusingtheCOPYcommand,plustheinstalledfilesinnode_modules:

root@a29d833287bf:/userauth#PORT=3333nodeusers-list.js

List[]

root@a29d833287bf:/userauth#PORT=3333nodeusers-add.js

Created{id:1,username:'me',password:'w0rd',provider:'local',

familyName:'Einarrsdottir',givenName:'Ashildr',

middleName:'',emails:'[]',photos:'[]',

updatedAt:'2018-02-05T01:54:53.320Z',createdAt:'2018-02-

05T01:54:53.320Z'}

root@a29d833287bf:/userauth#PORT=3333nodeusers-list.js

List[{id:'me',username:'me',provider:'local',

familyName:'Einarrsdottir',givenName:'Ashildr',middleName:'',

emails:'[]',photos:'[]'}]

Page 585: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Ourtestofaddingausertotheauthenticationserviceworks:

root@a29d833287bf:/userauth#ps-eafw

UIDPIDPPIDCSTIMETTYTIMECMD

root10001:52pts/000:00:00binsh-cnpmrundocker

root91001:52pts/000:00:00npm

root199001:52pts/000:00:00sh-cnode--experimental-

modules./user-server

root2019001:52pts/000:00:01node--experimental-modules

./user-server

root300001:54pts/100:00:00bash

root7030001:57pts/100:00:00ps-eafw

root@a29d833287bf:/userauth#pingdb-userauth

PINGdb-userauth(172.18.0.2):56databytes

64bytesfrom172.18.0.2:icmp_seq=0ttl=64time=0.105ms

64bytesfrom172.18.0.2:icmp_seq=1ttl=64time=0.077ms

^C---db-userauthpingstatistics---

2packetstransmitted,2packetsreceived,0%packetloss

round-tripmin/avg/max/stddev=0.077/0.091/0.105/0.000ms

root@a29d833287bf:/userauth#pinguserauth

PINGuserauth(172.18.0.3):56databytes

64bytesfrom172.18.0.3:icmp_seq=0ttl=64time=0.132ms

64bytesfrom172.18.0.3:icmp_seq=1ttl=64time=0.095ms

^C---userauthpingstatistics---

2packetstransmitted,2packetsreceived,0%packetloss

Theprocesslistingisinterestingtostudy.ProcessPID1isthenpmrundockercommandintheDockerfile.Processesproceedfromtheretothenodeprocessrunningtheactualserver.

Apingcommandprovesthetwocontainersareavailableashostnamesmatchingthecontainernames.

Then,youcanlogintothedb-userauthcontainerandinspectthedatabase:

$dockerexec-itdb-userauthbash

bash-4.2#mysql-uuserauth-p

Enterpassword:

WelcometotheMySQLmonitor.Commandsendwith;or\g.

...

mysql>useuserauth

Databasechanged

Page 586: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

mysql>showtables;

+--------------------+

|Tables_in_userauth|

+--------------------+

|Users|

+--------------------+

1rowinset(0.00sec)

mysql>select*fromUsers;

+----+----------+----------+----------+---------------+-----------+--...

|id|username|password|provider|familyName|givenName|...

+----+----------+----------+----------+---------------+-----------+--...

|1|me|w0rd|local|Einarrsdottir|Ashildr|...

+----+----------+----------+----------+---------------+-----------+--...

1rowinset(0.00sec)

WehavesuccessfullyDockerizedtheuserauthenticationserviceintwocontainers,db-userauthanduserauth.We'vepokedaroundtheinsidesofarunningcontainerandfoundsomeinterestingthings.But,ourusersneedthefantasticNotesapplicationtoberunning,andwecan'taffordtorestonourlaurels.

Page 587: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

CreatingFrontNetfortheNotesapplicationWehavethebackhalfofoursystemsetupinaDockercontainer,aswellastheprivatebridgenetworktoconnectthebackendcontainers.Wenowneedtosetupanotherprivatebridgenetwork,frontnet,andattachtheotherhalfofoursystemtothatnetwork.

Createadirectory,frontnet,whichiswherewe'lldevelopthetoolstobuildandrunthatnetwork.Inthatdirectory,createafile,buildfrontnet.sh,oronWindows,buildfrontnet.ps1,containing:dockernetworkcreate--driverbridgefrontnet

Let'sgoaheadandcreatethefrontnetbridgenetwork:

$sh-xbuildfrontnet.sh

+dockernetworkcreate--driverbridgefrontnet

f3df227d4bfff57bc7aed1e096a2ad16f6cebce4938315a54d9386a42d1ae3ed

$dockernetworkls

NETWORKIDNAMEDRIVERSCOPE

3021e2069278authnetbridgelocal

f3df227d4bfffrontnetbridgelocal

We'llproceedfromheresimilarlytohowauthnetwascreated.However,wecanworkmorequicklybecausewe'vealreadygoneoverthebasics.

Page 588: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

MySQLcontainerfortheNotesapplicationFromtheauthnetdirectory,copythemy.cnfandstartdb.shfilesintothefrontnetdirectory.Themy.cnffilecanprobablybeusedunmodified,butwehaveafewchangestomaketothestartdb.shfile:

dockerrun--namedb-notes--envMYSQL_RANDOM_ROOT_PASSWORD=true\

--envMYSQL_USER=notes--envMYSQL_PASSWORD=notes12345\

--envMYSQL_DATABASE=notes\

--volume`pwd`/my.cnf:/etc/my.cnf\

--volume`pwd`/../notes-data:/var/lib/mysql\

--networkfrontnetmysql/mysql-server:5.7

OnWindows,namethefilestartdb.ps1containingthis:

dockervolumecreatenotes-data-volume

dockerrun--namedb-notes--envMYSQL_RANDOM_ROOT_PASSWORD=true--env

MYSQL_USER=notes--envMYSQL_PASSWORD=notes12345--envMYSQL_DATABASE=notes-

-volume$PSScriptRoot\my.cnf:/etc/my.cnf--volumenotes-data-

volume:/var/lib/mysql--networkfrontnetmysql/mysql-server:5.7

Thechangesaresimplesubstitutionstotransliteratefromuserauthtonotes.Andthenrunit:

$mkdir../notes-data

$sh-xstartdb.sh

+pwd

+pwd

+dockerrun--namedb-notes--envMYSQL_RANDOM_ROOT_PASSWORD=true--env

MYSQL_USER=notes--envMYSQL_PASSWORD=notes12345--envMYSQL_DATABASE=notes-

-volumehomedavid/nodewebdev/nodeweb-development-code-4th-

edition/chap10/frontnet/my.cnf:/etc/my.cnf--volume

homedavid/nodewebdev/nodeweb-development-code-4th-

edition/chap10/frontnet/../notes-data:/var/lib/mysql--networkfrontnet

mysql/mysql-server:5.7

[Entrypoint]MySQLDockerImage5.7.21-1.1.4

Page 589: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

[Entrypoint]Initializingdatabase

[Entrypoint]Databaseinitialized

[Entrypoint]GENERATEDROOTPASSWORD:3kZ@q4hBItYGYj3Mes!AdiP83Nol

[Entrypoint]ignoringdocker-entrypoint-initdb.d*

[Entrypoint]Servershutdown

[Entrypoint]MySQLinitprocessdone.Readyforstartup.

[Entrypoint]StartingMySQL5.7.21-1.1.4

ForWindows,simplyrun.\startdb.ps1.

Thisdatabasewillbeavailableatthedb-notesdomainnameonfrontnet.Becauseit'sattachedtofrontnet,itwon'tbereachablebycontainersconnectedtoauthnet.

$dockerexec-ituserauthbash

root@0a2009334b79:/userauth#pingdb-notes

ping:unknownhost

Sincedb-notesisonadifferentnetworksegment,we'veachievedseparation.

Page 590: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

DockerizingtheNotesapplicationInthenotesdirectory,createafilenamedDockerfilecontainingthefollowing:

FROMnode:10

ENVDEBUG="notes:*,messages:*"

ENVSEQUELIZE_CONNECT="models/sequelize-docker-mysql.yaml"

ENVNOTES_MODEL="sequelize"

ENVUSER_SERVICE_URL="http://userauth:3333"

ENVPORT="3000"

ENVNOTES_SESSIONS_DIR="/sessions"

#ENVTWITTER_CONSUMER_KEY="..."

#ENVTWITTER_CONSUMER_SECRET="..."

#UsethislinewhentheTwitterCallbackURL

#hastobeotherthanlocalhost:3000

#ENVTWITTER_CALLBACK_HOST=http://45.55.37.74:3000

RUNmkdir-pnotesappnotesapp/mintynotesapppartialsnotesapppublic

notesapproutesnotesappthemenotesappviews

COPYminty/notesappminty/

COPYmodels/*.mjsmodels/sequelize-docker-mysql.yamlnotesappmodels/

COPYpartials/notesapppartials/

COPYpublic/notesapppublic/

COPYroutes/notesapproutes/

COPYtheme/notesapptheme/

COPYviews/notesappviews/

COPYapp.mjspackage.jsonnotesapp

WORKDIR/notesapp

RUNapt-getupdate-y\

&&apt-get-yinstallcurlpythonbuild-essentialgitca-certificates\

&&npminstall--unsafe-perm

#Uncommenttobuildthethemedirectory

#WORKDIRnotesapptheme

#RUNnpmrundownload&&npmrunbuild&&npmrunclean

Page 591: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

WORKDIR/notesapp

VOLUME/sessions

EXPOSE3000

CMDnode--experimental-modules./app

ThisissimilartotheDockerfileweusedfortheauthenticationservice.We'reusingtheenvironmentvariablesfromnotes/package.json,plusanewone,andthere'sacoupleofnewtricksinvolvedhere,solet'stakealook.

ThemostobviouschangeisthenumberofCOPYcommands.TheNotesapplicationisalotmoreinvolvedgiventhenumberofsubdirectoriesfulloffilesthatmustbeinstalled.Westartbycreatingthetop-leveldirectoriesoftheNotesapplicationdeploymenttree.Then,onebyone,wecopyeachsubdirectoryintoitscorrespondingsubdirectoryinthecontainerfilesystem.

InaCOPYcommand,thetrailingslashonthedestinationdirectoryisimportant.Why?Becausethedocumentationsaysthatthetrailingslashisimportant.

Thebigquestionis:WhyusemultipleCOPYcommandssuchasthis?Thiswouldhavebeentriviallysimple:

COPY./notesapp

But,itisimportanttoavoidcopyingthenode_modulesdirectoryintothecontainer.Thecontainernode_modulesmustbebuiltinsidethecontainer,becausethecontaineroperatingsystemisalmostcertainlydifferenttothehostoperatingsystem.Anynativecodemodulesmustbebuiltforthecorrectoperatingsystem.Thatconstraintledtothequestionofconciselycopyingspecificfilestothedestination.

We'vedevelopedaprocesstobuildaBootstrap4theme,whichwedevelopedinChapter6,ImplementingtheMobile-FirstParadigm.IfyouhaveaBootstrap4themetobuild,simplyuncommentthecorrespondinglinesintheDockerfile.Thoselinesmovetheworkingdirectoryto

Page 592: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

notesappthemeandthenrunthescriptstobuildthetheme.Anewscriptisrequiredintheme/package.jsontoremovethetheme/node_modulesdirectoryafterthethemehasbeenbuilt:

"scripts":{

...

"clean":"rm-rfbootstrap-4.0.0/node_modules"

...

}

WealsohaveanewSEQUELIZE_CONNECTfile.Createmodels/sequelize-docker-mysql.yamlcontainingthefollowing:

dbname:notes

username:notes

password:notes12345

params:

host:db-notes

port:3306

dialect:mysql

Thiswillaccessadatabaseserveronthedb-notesdomainnameusingthenameddatabase,username,andpassword.

NoticethattheUSER_SERVICE_URLvariablenolongeraccessestheauthenticationserviceatlocalhost,butatuserauth.TheuserauthdomainnameiscurrentlyonlyadvertisedbytheDNSserveronAuthNet,buttheNotesserviceisonFrontNet.Thismeanswe'llhavetoconnecttheuserauthcontainertotheFrontNetbridgenetworksothatitsnameisknownthereaswell.We'llgettothatinaminute.

InChapter8,wediscussedtheneedtoprotecttheAPIkeyssuppliedbyTwitter.

Wedidn'twanttocommitthekeysinthesourcecode,buttheyhavetogosomewhere.PlaceholdersareintheDockerfileforspecifyingTWITTER_CONSUMER_KEYandTWITTER_CONSUMER_SECRET.

Page 593: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ThevalueforTWITTER_CALLBACK_HOSTneedstoreflectwhereNotesisdeployed.Rightnow,itisstillonyourlaptop,butbytheendofthechapter,itwillbedeployedtotheserver,and,atthattime,itwillneedtheIPaddressordomainnameoftheserver.

AnewvariableisNOTES_SESSIONS_DIRandthematchingVOLUMEdeclaration.IfweweretorunmultipleNotesinstances,theycouldsharesessiondatabysharingthisvolume.

SupportingtheNOTES_SESSIONS_DIRvariablerequiresonechangeinapp.mjs:

constsessionStore=newFileStore({

path:process.env.NOTES_SESSIONS_DIR?

process.env.NOTES_SESSIONS_DIR:"sessions"

});

Insteadofahardcodeddirectoryname,wecanuseanenvironmentvariabletodefinethelocationwheresessiondataisstored.Alternatively,therearesessionStoreimplementationsforvariousserverssuchasREDIS,enablingsessiondatasharingbetweencontainersonseparatehostsystems.

Innotes/package.json,addthesescripts:

"scripts":{

...

"docker":"node--experimental-modules./app",

"docker-build":"dockerbuild-tnode-web-development/notes."

...

}

Asfortheauthenticationserver,thisletsusbuildthecontainerandthen,withinthecontainer,wecanruntheservice.

Nowwecanbuildthecontainerimage:

$npmrundocker-build

>[email protected]/chap10/notes

Page 594: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

>dockerbuild-tnode-web-development/notes.

SendingbuildcontexttoDockerdaemon76.27MB

Step1/22:FROMnode:9.5

--->a696309517c6

Step2/22:ENVDEBUG="notes:*,messages:*"

--->Usingcache

--->8628ecad9fa4

Next,inthefrontnetdirectory,createafilenamedstartserver.sh,or,onWindows,startserver.ps1:

dockerrun-it--namenotes--net=frontnet-p3000:3000node-web-

development/notes

Unliketheauthenticationservice,theNotesapplicationcontainermustexportaporttothepublic.Otherwise,thepublicwillneverbeabletoenjoythiswonderfulcreationwe'rebuilding.The-poptionishowweinstructDockertoexposeaport.

ThefirstnumberisaTCPportnumberpublishedfromthecontainer,andthesecondnumberistheTCPportinsidethecontainer.Generallyspeaking,thisoptionmapsaportinsidethecontainertoonereachablebythepublic.

Thenrunitasfollows:

$sh-xstartserver.sh

+dockerrun-it--namenotes--net=frontnet-p3000:3000node-web-

development/notes

(node:6)ExperimentalWarning:TheESMmoduleloaderisexperimental.

notes:debug-INDEXListeningonport3000+0ms

OnWindows,run.\startserver.ps1.

Atthispoint,wecanconnectourbrowsertohttp://localhost:3000andstartusingtheNotesapplication.Butwe'llquicklyrunintoaproblem:

Page 595: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Theuserexperienceteamisgoingtoscreamaboutthisuglyerrormessage,soputitonyourbacklogtogenerateaprettiererrorscreen.Forexample,aflockofbirdspullingawhaleoutoftheoceanispopular.

ThiserrormeansthatNotescannotaccessanythingatthehostnameduserauth.Thathostdoesexist,becausethecontainerisrunning,butit'snotonfrontnet,andisnotreachablefromthenotescontainer.Namely:

$dockerexec-itnotesbash

root@125a196c3fd5:/notesapp#pinguserauth

ping:unknownhost

root@125a196c3fd5:/notesapp#pingdb-notes

PINGdb-notes(172.19.0.2):56databytes

64bytesfrom172.19.0.2:icmp_seq=0ttl=64time=0.136ms

^C---db-notespingstatistics---

1packetstransmitted,1packetsreceived,0%packetloss

round-tripmin/avg/max/stddev=0.136/0.136/0.136/0.000ms

root@125a196c3fd5:/notesapp#

IfyouinspectFrontNetandAuthNet,you'llseethecontainersattachedtoeachdonotoverlap:

$dockernetworkinspectfrontnet

$dockernetworkinspectauthnet

Inthearchitecturediagramatthebeginningofthechapter,weshowedaconnectionbetweenthenotesanduserauthcontainers.Theconnectionisrequiredsonotescanauthenticateitsusers.Butthatconnectiondoesnot

Page 596: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

exist,yet.

Unfortunately,asimplechangetostartserver.sh(startserver.ps1)doesnotwork:

dockerrun-it--namenotes--net=authnet--net=frontnet-p3000:3000node-

web-development/notes

Whileitisconceptuallysimpletospecifymultiple--netoptionswhenstartingacontainer,Dockerdoesnotsupportthis.Itsilentlyacceptsthecommandasshown,butonlyconnectsthecontainertothelastnetworkmentionedintheoptions.Instead,Dockerrequiresthatyoutakeasecondsteptoattachthecontainertoasecondnetwork:

$dockernetworkconnectauthnetnotes

Withnootherchange,theNotesapplicationwillnowallowyoutologinandstartaddingandeditingnotes.

Thereisaglaringarchitecturequestionstaringatus.Doweconnecttheuserauthservicetofrontnet,ordoweconnectthenotesservicetoauthnet?Toverifythateitherdirectionsolvestheproblem,runthesecommands:

$dockernetworkdisconnectauthnetnotes

$dockernetworkconnectfrontnetuserauth

Thefirsttimearound,weconnectednotestoauthnet,thenwedisconnecteditfromauthnet,andthenconnecteduserauthtofrontnet.Thatmeanswetriedbothcombinationsand,asexpected,inbothcasesnotesanduserauthwereabletocommunicate.

Thisisaquestionforsecurityexpertssincetheconsiderationistheattackvectorsavailabletoanyintruders.SupposeNoteshasasecurityholeallowinganinvadertogainaccess.Howdowelimitwhatisreachablevia

Page 597: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

thathole?

Theprimaryobservationisthatbyconnectingnotestoauthnet,notesnotonlyhasaccesstouserauth,butalsotodb-userauth:

$dockernetworkdisconnectfrontnetuserauth

$dockernetworkconnectauthnetnotes

$dockerexec-itnotesbash

root@7fce818e9a4d:/notesapp#pinguserauth

PINGuserauth(172.18.0.3):56databytes

64bytesfrom172.18.0.3:icmp_seq=0ttl=64time=0.103ms

^C---userauthpingstatistics---

1packetstransmitted,1packetsreceived,0%packetloss

round-tripmin/avg/max/stddev=0.103/0.103/0.103/0.000ms

root@7fce818e9a4d:/notesapp#pingdb-userauth

PINGdb-userauth(172.18.0.2):56databytes

64bytesfrom172.18.0.2:icmp_seq=0ttl=64time=0.201ms

^C---db-userauthpingstatistics---

1packetstransmitted,1packetsreceived,0%packetloss

round-tripmin/avg/max/stddev=0.201/0.201/0.201/0.000ms

root@7fce818e9a4d:/notesapp#

Thissequencereconnectsnotestoauthnet,anddemonstratestheabilitytoaccessboththeuserauthanddb-userauthcontainers.Therefore,asuccessfulinvadercouldaccessthedb-userauthdatabase,aresultwewantedtoprevent.Ourdiagramatthebeginningshowednosuchconnectionbetweennotesanddb-userauth.

GiventhatourgoalforusingDockerwastolimittheattackvectors,wehaveacleardistinctionbetweenthetwocontainer/networkconnectionsetups.Attachinguserauthtofrontnetlimitsthenumberofcontainersthatcanaccessdb-userauth.Foranintrudertoaccesstheuserinformationdatabase,theymustfirstbreakintonotes,andthenbreakintouserauth.Unless,thatis,ouramateurattemptatasecurityauditisflawed.

Page 598: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ControllingthelocationofMySQLdatavolumesThedb-userauthanddb-notesDockerfilescontainVOLUMEvarlib/mysql,andwhenwestartedthecontainers,wegave--volumeoptions,assigningahostdirectoryforthatcontainerdirectory:

dockerrun--namedb-notes\

...

--volume`pwd`/../notes-data:varlib/mysql\

...

Wecaneasilyseethisconnectsahostdirectory,soitappearswithinthecontaineratthatlocation.SimplyinspectingthehostdirectorywithtoolssuchaslsshowsthatfilesarecreatedinthatdirectorycorrespondingtoaMySQLdatabase.

TheVOLUMEinstructioninstructsDockertocreateadirectoryoutsidethecontainerandtomapthatdirectorysothatit'smountedinsidethecontaineronthenamedpath.TheVOLUMEinstructionbyitselfdoesn'tcontrolthedirectorynameonthehostcomputer.Ifno--volumeoptionisgiven,Dockerstillarrangesforthecontentofsaiddirectorytobekeptoutsidethecontainer.That'suseful,andatleastthedataisavailableoutsidethecontainer,butyouhaven'tcontrolledthelocation.

Ifwerestartthedb-notescontainerwithoutusingthe--volumeoptionforvarlib/mysql,wecaninspectthecontainertodiscoverwhereDockerputthevolume:

$dockerinspect--format'{{json.Mounts}}'db-notes

[{"Type":"bind",

"Source":"Usersdavid/chap10/frontnet/my.cnf","Destination":"etcmy.cnf",

"Mode":"","RW":true,"Propagation":"rprivate"},

Page 599: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

{"Type":"volume","Name":"39f9a80b49e3ecdebc7789de7b7dd2366c400ee7fbfedd6e4df1

8f7e60bad409",

"Source":"varlib/docker/volumes/39f9a80b49e3ecdebc7789de7b7dd2366c400ee7fbfed

d6e4df18f7e60bad409/_data","Destination":"varlib/mysql",

"Driver":"local","Mode":"","RW":true,"Propagation":""}]

That'snotexactlyauser-friendlypathname,butyoucansnoopintothatdirectoryandseethatindeedtheMySQLdatabaseisstoredthere.Thesimplestwaytouseauser-friendlypathnameforavolumeiswiththe--volumeoptionsweshowedearlier.

Anotheradvantagewehaveistoeasilyswitchdatabases.Forexample,wecouldtestNoteswithpre-cookedtestdatabasesfullofnoteswritteninSwahili(notes-data-swahili),Romanian(notes-data-romanian),German(notes-data-german)andEnglish(notes-data-english).Eachtestdatabasecouldbestoredinthenameddirectory,andtestingagainstthespecificlanguageisassimpleasrunningthenotescontainerwithdifferent--volumeoptions.

Inanycase,ifyourestartthenotescontainerwiththe--volumeoption,youcaninspectthecontainerandseethedirectoryismountedonthedirectoryyouspecified:

$dockerinspect--format'{{json.Mounts}}'db-notes

[{"Type":"bind",

"Source":"Usersdavid/chap10/frontnet/my.cnf","Destination":"etcmy.cnf",

"Mode":"","RW":true,"Propagation":"rprivate"},

{"Type":"bind",

"Source":"Usersdavid/chap10/notes-data","Destination":"varlib/mysql",

"Mode":"","RW":true,"Propagation":"rprivate"}]

Withthe--volumeoptions,wehavecontrolledthelocationofthehostdirectorycorrespondingtothecontainerdirectory.

Thelastthingtonoteisthatcontrollingthelocationofsuchdirectoriesmakesiteasiertomakebackupsandtakeotheradministrativeactionswiththatdata.

Page 600: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

DockerdeploymentofbackgroundservicesWiththescriptswe'vewrittensofar,theDockercontainerisrunintheforeground.Thatmakesiteasiertodebugtheservicesinceyouseetheerrors.Foraproductiondeployment,weneedtheDockercontainerdetachedfromtheterminal,andanassurancethatitwillrestartitselfautomatically.Thosetwoattributesaresimpletoimplement.

Simplychangethispattern:$dockerrun-it...

Tothispattern:

$dockerrun--detach--restartalways...

The-itoptioniswhatcausestheDockercontainertorunintheforeground.UsingtheseoptionscausestheDockercontainertorundetachedfromyourterminal,andiftheserviceprocessdies,thecontainerwillautomaticallyrestart.

Page 601: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

DeployingtothecloudwithDockercomposeThisiscoolthatwecancreateencapsulatedinstantiationsofthesoftwareserviceswe'vecreated.ButthepromisewastousetheDockerizedapplicationfordeploymentoncloudservices.Inotherwords,weneedtotakeallthislearningandapplyittothetaskofdeployingNotesonapublicinternetserverwithafairlyhighdegreeofsecurity.

We'vedemonstratedthat,withDocker,Notescanbedecomposedintofourcontainersthathaveahighdegreeofisolationfromeachother,andfromtheoutsideworld.

Thereisanotherglaringproblem:ourprocessintheprevioussectionwaspartlymanual,partlyautomated.Wecreatedscriptstolauncheachportionofthesystem,whichisagoodpracticeaccordingtotheTwelveFactorApplicationmodel.ButwedidnotautomatetheentireprocesstobringupNotesandtheauthenticationservices.Noristhissolutionscalablebeyondonemachine.

Let'sstartwiththelastissuefirst—scalability.WithintheDockerecosystem,severalDockerorchestratorservicesareavailable.AnOrchestratorautomaticallydeploysandmanagesDockercontainersoveragroupofmachines.SomeexamplesofDockerOrchestratorsareDockerSwarm(whichisbuiltintotheDockerCLI),Kubernetes,CoreOSFleet,andApacheMesos.Thesearepowerfulsystemsabletoautomaticallyincrease/decreaseresourcesasneeded,tomovecontainersfromonehosttoanother,andmore.Wementionthesesystemsforyourfurtherstudyasyourneedsgrow.

Dockercompose(https://docs.docker.com/compose/overview/)willsolvetheotherproblemswe'veidentified.Itletsuseasilydefineandrunseveral

Page 602: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Dockercontainerstogetherasacompleteapplication.ItusesaYAMLfile,docker-compose.yml,todescribethecontainers,theirdependencies,thevirtualnetworks,andthevolumes.Whilewe'llbeusingittodescribethedeploymentontoasinglehostmachine,Dockercomposecanbeusedformultimachinedeployments,especiallywhencombinedwithDockerSwarm.UnderstandingDockercomposewillprovideabasisuponwhichtounderstand/usetheothertools,suchasSwarmorKubernetes.

Dockermachine(https://docs.docker.com/machine/overview/)isatoolforinstallingDockerEngineonvirtualhosts,eitherlocalorremote,andformanagingDockercontainersonthosehosts.We'llbeusingthistoprovisionaserveronacloudhostingservice,andpushcontainersintothatserver.ItcanalsobeusedtoprovisionavirtualhostonyourlaptopwithinaVirtualBoxinstance.

Beforeproceeding,ensureDockercomposeandDockermachineareinstalled.Ifyou'veinstalledDockerforWindowsorDockerforMac,bothareinstalledalongwitheverythingelse.OnLinux,youmustinstallbothseparatelybyfollowingtheinstructionsatthelinksgivenearlier.

Page 603: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

DockercomposefilesLet'sstartbycreatingadirectory,compose,asasiblingtotheusersandnotesdirectories.Inthatdirectory,createafilenameddocker-compose.yml:

version:'3'

services:

db-userauth:

image:"mysql/mysql-server:5.7"

container_name:db-userauth

command:["mysqld","--character-set-server=utf8mb4",

"--collation-server=utf8mb4_unicode_ci",

"--bind-address=0.0.0.0"]

expose:

-"3306"

networks:

-authnet

volumes:

-db-userauth-data:/var/lib/mysql

-../authnet/my.cnf:/etc/my.cnf

environment:

MYSQL_RANDOM_ROOT_PASSWORD:"true"

MYSQL_USER:userauth

MYSQL_PASSWORD:userauth

MYSQL_DATABASE:userauth

restart:always

userauth:

build:../users

container_name:userauth

depends_on:

-db-userauth

networks:

-authnet

-frontnet

restart:always

db-notes:

image:"mysql/mysql-server:5.7"

container_name:db-notes

command:["mysqld","--character-set-server=utf8mb4",

Page 604: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

"--collation-server=utf8mb4_unicode_ci",

"--bind-address=0.0.0.0"]

expose:

-"3306"

networks:

-frontnet

volumes:

-db-notes-data:/var/lib/mysql

-../frontnet/my.cnf:/etc/my.cnf

environment:

MYSQL_RANDOM_ROOT_PASSWORD:"true"

MYSQL_USER:notes

MYSQL_PASSWORD:notes12345

MYSQL_DATABASE:notes

restart:always

notes:

build:../notes

container_name:notes

restart:always

depends_on:

-db-notes

networks:

-frontnet

ports:

-"3000:3000"

restart:always

networks:

frontnet:

driver:bridge

authnet:

driver:bridge

volumes:

db-userauth-data:

db-notes-data:

That'sthedescriptionoftheentireNotesdeployment.It'satafairlyhighlevelofabstraction,roughlyequivalenttotheoptionsonthecommand-linetoolswe'veusedsofar.FurtherdetailsarelocatedinsidetheDockerfiles,whicharereferencedfromthiscomposefile.

Theversionlinesaysthatthisisaversion3composefile.Theversionnumberisinspectedbythedocker-composecommand,soitcancorrectlyinterpretitscontent.Thefulldocumentationisworthreadingathttps://doc

Page 605: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

s.docker.com/compose/compose-file/.

Therearethreemajorsectionsusedhere:services,volumes,andnetworks.Theservicessectiondescribesthecontainersbeingused,thenetworkssectiondescribesthenetworks,andthevolumessectiondescribesthevolumes.Thecontentofeachsectionmatchestheintent/purposeofthecommandsweranearlier.Theinformationwe'vealreadydealtwithisallhere,justrearranged.

Therearetwodatabasecontainers,db-userauthanddb-notes.BothreferencetheDockerhubimageusingtheimagetag.Forthedatabases,wedidnotcreateaDockerfile,butinsteadbuiltdirectlyfromtheDockerhubimage.Thesamehappenshereinthecomposefile.

Fortheuserauthandnotescontainers,wecreatedaDockerfile.Thedirectorycontainingthatfileisreferencedbythebuildtag.Tobuildthecontainer,docker-composelooksforafilenamedDockerfileinthenameddirectory.Therearemoreoptionsforthebuildtag,whicharediscussedintheofficialdocumentation.

Thecontainer_nameattributeisequivalenttothe--nameattributeandspecifiesauser-friendlynameforthecontainer.WemustspecifythecontainernameinordertospecifythecontainerhostnameinordertodoDocker-styleservicediscovery.

ThecommandtagoverridestheCMDtagintheDockerfile.We'vespecifiedthisforthetwodatabasecontainers,sowecaninstructMySQLtobindtoIPaddress0.0.0.0.Eventhoughwedidn'tcreateaDockerfileforthedatabasecontainers,thereisaDockerfilecreatedbytheMySQLmaintainers.

Thenetworksattributeliststhenetworkstowhichthiscontainermustbeconnectedandisexactlyequivalenttothe--netargument.Eventhoughthedockercommanddoesn'tsupportmultiple--netoptions,wecanlistmultiplenetworksinthecomposefile.Inthiscase,thenetworksarebridgenetworks.Aswedidearlier,thenetworksthemselvesmustbecreatedseparately,andinacomposefile,that'sdoneinthenetworkssection.

Page 606: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Eachofthenetworksinoursystemisabridgenetwork.Thisfactisdescribedinthecomposefile.

Theexposeattributedeclareswhichportsareexposedfromthecontainer,andisequivalenttotheEXPOSEtag.Theexposedportsarenotpublishedoutsidethehostmachine,however.Theportsattributedeclarestheportsthataretobepublished.Intheportsdeclaration,wehavetwoportnumbers:thefirstbeingthepublishedportnumberandthesecondbeingtheportnumberinsidethecontainer.Thisisexactlyequivalenttothe-poptionusedearlier.

Thenotescontainerhasafewenvironmentvariables,suchasTWITTER_CONSUMER_KEYandTWITTER_CONSUMER_SECRET,thatyoumayprefertostoreinthisfileratherthanintheDockerfile.

Thedepends_onattributeletsuscontrolthestartuporder.Acontainerthatdependsonanotherwillwaittostartuntilthedepended-uponcontainerisrunning.

Thevolumesattributedescribesmappingsofacontainerdirectorytoahostdirectory.Inthiscase,we'vedefinedtwovolumenames,db-userauth-dataanddb-notes-data,andthenusedthemforthevolumemapping.Toexplorethevolumes,startwiththiscommand:

$dockervolumels

DRIVERVOLUMENAME

...

localcompose_db-notes-data

localcompose_db-userauth-data

...

Thevolumenamesarethesameasinthecomposefile,butwithcompose_tackedonthefront.

Youcaninspectthevolumelocationusingthedockercommandline:

$dockervolumeinspectcompose_db-notes-data

Page 607: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

$dockervolumeinspectcompose_db-userauth-data

Ifit'spreferable,youcanspecifyapathnameinthecomposefile:

db-auth:

..

volumes:

#-db-userauth-data:/var/lib/mysql

-../userauth-data:/var/lib/mysql

db-notes:

..

volumes:

#-db-notes-data:/var/lib/mysql

-../notes-data:/var/lib/mysql

Thisisthesameconfigurationwemadeearlier.Itusestheuserauth-dataandnotes-datadirectoriesfortheMySQLdatafilesfortheirrespectivedatabasecontainers.

Theenvironmenttagdescribestheenvironmentvariablesthatwillbereceivedbythecontainer.Asbefore,environmentvariablesshouldbeusedtoinjectconfigurationdata.

Therestartattributecontrolswhathappensif,orwhen,thecontainerdies.Whenacontainerstarts,itrunstheprogramnamedintheCMDinstruction,andwhenthatprogramexits,thecontainerexits.Butwhatifthatprogramismeanttorunforever,shouldn'tDockerknowitshouldrestarttheprocess?Wecoulduseabackgroundprocesssupervisor,suchasSupervisordorPM2.But,wecanalsousetheDockerrestartoption.

Therestartattributecantakeoneofthefollowingfourvalues:

no–donotrestart

on-failure:count–restartuptoNtimes

always–alwaysrestart

Page 608: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

unless-stopped–startthecontainerunlessitwasexplicitlystopped

Page 609: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

RunningtheNotesapplicationwithDockercomposeOnWindows,we'reabletorunthecommandsinthissectionunchanged.

Beforedeployingthistoaserver,let'srunitonourlaptopusingdocker-compose:

$dockerstopdb-notesuserauthdb-authnotesapp

db-notes

userauth

db-auth

notesapp

$dockerrmdb-notesuserauthdb-authnotesapp

db-notes

userauth

db-auth

notesapp

Wefirstneededtostopanddeletetheexistingcontainers.Becausethecomposefilewantstolaunchcontainerswiththesamenamesaswe'dbuiltearlier,wealsohavetoremovetheexistingcontainers:

$docker-composebuild

Buildingdb-auth

..lotsofoutput

$docker-composeup

Creatingdb-auth

Recreatingcompose_db-notes_1

Recreatingcompose_userauth_1

Recreatingcompose_notesapp_1

Attachingtodb-auth,db-notes,userauth,notesapp

Oncethat'sdone,wecanbuildthecontainers,docker-composebuild,andthenstartthemrunning,docker-composeup.

Page 610: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Thefirsttestistoexecuteashellinuserauthtorunouruserdatabasescript:

$dockerexec-ituserauthbash

root@9972adbbdbb3:/userauth#PORT=3333nodeusers-add.js

Created{id:2,

username:'me',password:'w0rd',provider:'local',

familyName:'Einarrsdottir',givenName:'Ashildr',middleName:'',

emails:'[]',photos:'[]',

updatedAt:'2018-02-07T02:24:04.257Z',createdAt:'2018-02-

07T02:24:04.257Z'}

root@9972adbbdbb3:/userauth#

Nowthatwe'veprovedthattheauthenticationservicewillwork,and,bytheway,createdauseraccount,youshouldbeabletobrowsetotheNotesapplicationandrunitthroughitspaces.

Youcanalsotrypingingdifferentcontainerstoensurethattheapplicationnetworktopologyhasbeencreatedcorrectly.

IfyouuseDockercommand-linetoolstoexploretherunningcontainersandnetworks,you'llseetheyhavenewnames.Thenewnamesaresimilartotheoldnames,butprefixedwiththestringcompose_.ThisisasideeffectofusingDockercompose.

Bydefault,docker-composeattachestothecontainerssothatloggingoutputisprintedontheTerminal.Outputfromallfourcontainerswillbeintermingledtogether.Thankfully,eachlineisprependedbythecontainername.

Whenyou'redonetestingthesystem,simplytypeCTRL+ContheTerminal:

^CGracefullystopping...(pressCtrl+Cagaintoforce)

Stoppingdb-userauth...done

Stoppinguserauth...done

Stoppingdb-notes...done

Stoppingnotes...done

ToavoidrunningwiththecontainersattachedtotheTerminal,usethe-doption.ThissaystodetachfromtheTerminalandruninthebackground.

Page 611: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Analternatewaytobringdownthesystemdescribedinthecomposefileiswiththedocker-composedowncommand.

Theupcommandbuilds,recreates,andstartsthecontainers.Thebuildstepcanbehandledseparatelyusingthedocker-composebuildcommand.Likewise,startingandstoppingthecontainerscanbehandledseparatelybyusingthedocker-composestartanddocker-compose-stopcommands.

Inallcases,yourcommandshellshouldbeinthedirectorycontainingthedocker-compose.ymlfile.That'sthedefaultnameforthisfile.Thiscanbeoverriddenwiththe-foptiontospecifyadifferentfilename.

Page 612: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

DeployingtocloudhostingwithDockercomposeWe'veverifiedonourlaptopthattheservicesdescribedbythecomposefileworkasintended.Launchingthecontainersisnowautomated,fixingoneoftheissueswenamedearlier.It'snowtimetoseehowtodeploytoacloud-hostingprovider.ThisiswhereweturntoDockermachine.

DockermachinecanbeusedtoprovisionDockerinstancesinsideaVirtualBoxhostonyourlaptop.Whatwe'llbedoingisprovisioningaDockersystemonDigitalOcean.Thedocker-machinecommandcomeswithdriverssupportingalonglistofcloud-hostingproviders.It'seasytoadapttheinstructionsshownhereforotherproviders,simplybysubstitutingadifferentdriver.

AftersigningupforaDigitalOceanaccount,clickontheAPIlinkinthedashboard.WeneedanAPItokentograntdocker-machineaccesstotheaccount.Gothroughtheprocessofcreatingatokenandsaveawaythetokenstringyou'regiven.TheDockerwebsitehasatutorialathttps://docs.docker.com/machine/examples/ocean/.

Withthetokeninhand,typethefollowing:

$docker-machinecreate--driverdigitalocean--digitalocean-size2gb\

--digitalocean-access-tokenTOKEN-FROM-PROVIDER\

sandbox

Runningpre-createchecks...

Creatingmachine...

(sandbox)CreatingSSHkey...

(sandbox)CreatingDigitalOceandroplet...

(sandbox)WaitingforIPaddresstobeassignedtotheDroplet...

Waitingformachinetoberunning,thismaytakeafewminutes...

Detectingoperatingsystemofcreatedinstance...

Page 613: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

WaitingforSSHtobeavailable...

Detectingtheprovisioner...

Provisioningwithubuntu(systemd)...

InstallingDocker...

Copyingcertstothelocalmachinedirectory...

Copyingcertstotheremotemachine...

SettingDockerconfigurationontheremotedaemon...

CheckingconnectiontoDocker...

Dockerisupandrunning!

ToseehowtoconnectyourDockerClienttotheDockerEnginerunningonthis

virtualmachine,run:docker-machineenvsandbox

Thedigitaloceandriveris,aswesaidearlier,usedwithDigitalOcean.TheDockerwebsitehasalistofdriversathttps://docs.docker.com/machine/drivers/.

Alotofinformationisprintedhereaboutthingsbeingsetup.Themostimportantisthemessageattheend.AseriesofenvironmentvariablesareusedtotellthedockercommandwheretoconnecttotheDockerEngineinstance.Asthemessagessay,run:docker-machineenvsandbox:

$docker-machineenvsandbox

exportDOCKER_TLS_VERIFY="1"

exportDOCKER_HOST="tcp://45.55.37.74:2376"

exportDOCKER_CERT_PATH="homedavid/.docker/machine/machines/sandbox"

exportDOCKER_MACHINE_NAME="sandbox"

#Runthiscommandtoconfigureyourshell:

#eval$(docker-machineenvsandbox)

That'stheenvironmentvariablesusedtoaccesstheDockerhostwejustcreated.Youshouldalsogotoyourcloud-hostingproviderdashboardandseethatthehosthasbeencreated.Thiscommandalsogivesussomeinstructionstofollow:

$eval$(docker-machineenvsandbox)

$docker-machinels

NAMEACTIVEDRIVERSTATEURLSWARM

DOCKERERRORS

sandbox*digitaloceanRunningtcp://45.55.37.74:2376

v18.01.0-ce

ThisshowsthatwehaveaDockerEngineinstancerunninginahostatour

Page 614: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

chosencloud-hostingprovider.

Oneinterestingtestatthispointistorundockerps-aonthisTerminal,andthentorunitinanotherTerminalthatdoesnothavetheseenvironmentvariables.Thatshouldshowthecloudhosthasnocontainersatall,whileyourlocalmachinemayhavesomecontainers(dependingonwhatyoucurrentlyhaverunning):

$dockerrunhello-world

Unabletofindimage'hello-world:latest'locally

latest:Pullingfromlibrary/hello-world

ca4f61b1923c:Pullcomplete

Digest:

sha256:66ef312bbac49c39a89aa9bcc3cb4f3c9e7de3788c944158df3ee0176d32b751

Status:Downloadednewerimageforhello-world:latest

...

$dockerimages

REPOSITORYTAGIMAGEIDCREATED

SIZE

hello-worldlatestf2a91732366c2monthsago

1.85kB

Here,we'veverifiedthatwecanlaunchacontainerontheremotehost.

Thenextstepistobuildourcontainersforthenewmachine.Becausewe'veswitchedtheenvironmentvariablestopointtothenewserver,thesecommandscauseactiontohappenthereratherthaninsideourlaptop:

$docker-composebuild

db-userauthusesanimage,skipping

db-notesusesanimage,skipping

Buildingnotes

Step1/22:FROMnode:9.5

9.5:Pullingfromlibrary/node

f49cf87b52c1:Pullcomplete

7b491c575b06:Pullcomplete

b313b08bab3b:Pullcomplete

51d6678c3f0e:Pullcomplete

...

Becausewechangedtheenvironmentvariables,thebuildoccursonthe

Page 615: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

sandboxmachineratherthanonourlaptop,aspreviously.

ThiswilltakeawhilebecausetheDockerimagecacheontheremotemachineisempty.Additionally,buildingthenotesappanduserauthcontainerscopiestheentiresourcetreetotheserverandrunsallbuildstepsontheserver.

Thebuildmayfailifthedefaultmemorysizeis500MB,thedefaultonDigitalOceanatthetimeofwriting.Ifso,thefirstthingtotryisresizingthememoryonthehosttoatleast2GB.

Oncethebuildisfinished,launchthecontainersontheremotemachine:

$docker-composeup

Creatingnotes...done

Recreatingdb-userauth...done

Recreatingdb-notes...done

Creatingnotes...

Attachingtodb-userauth,db-notes,userauth,notes

Oncethecontainersstart,youshouldtesttheuserauthcontaineraswe'vedonepreviously.Unfortunately,thefirsttimeyoudothis,thatcommandwillfail.Theproblemistheselinesinthedocker-compose.yml:

-../authnet/my.cnf:etcmy.cnf

...

-../frontnet/my.cnf:etcmy.cnf

Inthiscase,thebuildoccursontheremotemachine,andthedocker-machinecommanddoesnotcopythenamedfiletotheserver.Hence,whenDockerattemptstostartthecontainer,itisunabletodosobecausethatvolumemountcannotbesatisfiedbecausethefileissimplynotthere.This,then,meanssomesurgeryondocker-compose.yml,andtoaddtwonewDockerfiles.

First,makethesechangestodocker-compose.yml:

...

db-userauth:

Page 616: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

build:../authnet

container_name:db-userauth

networks:

-authnet

volumes:

-db-userauth-data:varlib/mysql

restart:always

...

db-notes:

build:../frontnet

container_name:db-notes

networks:

-frontnet

volumes:

-db-notes-data:varlib/mysql

restart:always

InsteadofbuildingthedatabasecontainersfromaDockerimage,we'renowbuildingthemfromapairofDockerfiles.NowwemustcreatethosetwoDockerfiles.

Inauthnet,createafilenamedDockerfilecontainingthefollowing:

FROMmysql/mysql-server:5.7

EXPOSE3306

COPYmy.cnfetc

ENVMYSQL_RANDOM_ROOT_PASSWORD="true"

ENVMYSQL_USER=userauth

ENVMYSQL_PASSWORD=userauth

ENVMYSQL_DATABASE=userauth

CMD["mysqld","--character-set-server=utf8mb4",\

"--collation-server=utf8mb4_unicode_ci","--bind-address=0.0.0.0"]

Thiscopiescertainsettingsfromwhathadbeenthedb-userauthdescriptionindocker-compose.yml.TheimportantthingisthatwenowCOPYthemy.cnffileratherthanuseavolumemount.

Infrontnet,createaDockerfilecontainingthefollowing:

FROMmysql/mysql-server:5.7

EXPOSE3306

COPYmy.cnfetc

ENVMYSQL_RANDOM_ROOT_PASSWORD="true"

Page 617: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ENVMYSQL_USER=notes

ENVMYSQL_PASSWORD=notes12345

ENVMYSQL_DATABASE=notes

CMD["mysqld","--character-set-server=utf8mb4",\

"--collation-server=utf8mb4_unicode_ci","--bind-address=0.0.0.0"]

Thisisthesame,butwithafewcriticalvalueschanged.

Aftermakingthesechanges,wecannowbuildthecontainers,andlaunchthem:

$docker-composebuild

...muchoutput

$docker-composeup--force-recreate

...muchoutput

Nowthatwehaveaworkingbuild,andcanbringupthecontainers,let'sinspectthemandverifyeverythingworks.

Executeashellinuserauthtotestandsetuptheuserdatabase:

$dockerexec-ituserauthbash

root@931dd2a267b4:/userauth#PORT=3333nodeusers-list.js

List[{id:'me',username:'me',provider:'local',

familyName:'Einarrsdottir',givenName:'Ashildr',middleName:'',

emails:'[]',photos:'[]'}]

Asmentionedpreviously,thisverifiesthattheuserauthserviceworks,thattheremotecontainersaresetup,andthatwecanproceedtousingtheNotesapplication.

Thequestionis:What'stheURLtouse?Theserviceisnotonlocalhost,becauseit'sontheremoteserver.Wedon'thaveadomainnameassigned,butthereisanIPaddressfortheserver.

Runthefollowingcommand:

$docker-machineipsandbox

45.55.37.74

Page 618: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

DockertellsyoutheIPaddress,whichyoushoulduseasthebasisoftheURL.Hence,inyourbrowser,visithttp://IP-ADDRESS:3000

WithNotesdeployedtotheremoteserver,youshouldcheckoutallthethingswe'velookedatpreviously.Thebridgenetworksshouldexist,asshownpreviously,withthesamelimitedaccessbetweencontainers.Theonlypublicaccessshouldbeport3000onthenotescontainer.

RemembertosettheTWITTER_CALLBACK_HOSTenvironmentvariableappropriatelyforyourserver.

Becauseourdatabasecontainersmountavolumetostorethedata,let'sseewherethatvolumelandedontheserver:

$dockervolumels

DRIVERVOLUMENAME

localcompose_db-notes-data

localcompose_db-userauth-data

Thosearetheexpectedvolumes,oneforeachcontainer:

$dockervolumeinspectcompose_db-notes-data

[

{

"CreatedAt":"2018-02-07T06:30:06Z",

"Driver":"local",

"Labels":{

"com.docker.compose.project":"compose",

"com.docker.compose.volume":"db-notes-data"

},

"Mountpoint":"varlib/docker/volumes/compose_db-notes-

data/_data",

"Name":"compose_db-notes-data",

"Options":{},

"Scope":"local"

}

]

Thosearethedirectories,butthey'renotlocatedonourlaptop.Instead,

Page 619: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

they'reontheremoteserver.Accessingthesedirectoriesmeansloggingintotheremoteservertotakealook:

$docker-machinesshsandbox

WelcometoUbuntu16.04.3LTS(GNU/Linux4.4.0-112-genericx86_64)

*Documentation:https://help.ubuntu.com

*Management:https://landscape.canonical.com

*Support:https://ubuntu.com/advantage

GetcloudsupportwithUbuntuAdvantageCloudGuest:

http://www.ubuntu.com/business/services/cloud

4packagescanbeupdated.

0updatesaresecurityupdates.

Lastlogin:WedFeb704:00:292018from108.213.68.139

root@sandbox:~#

Fromthispoint,youcaninspectthedirectoriescorrespondingtothesevolumesandseethattheyindeedcontainMySQLconfigurationanddatafiles:

root@sandbox:~#lsvarlib/docker/volumes/compose_db-notes-data/_data

auto.cnfclient-key.pemib_logfile1mysql.sock.lock

public_key.pem

ca-key.pemib_buffer_poolibtmp1notesserver-

cert.pem

ca.pemibdata1mysqlperformance_schemaserver-

key.pem

client-cert.pemib_logfile0mysql.sockprivate_key.pemsys

You'llalsofindthattheDockercommand-linetoolswillwork.Theprocesslistisespeciallyinteresting:

Page 620: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Lookcloselyatthisandyouseeaprocesscorrespondingtoeverycontainerinthesystem.Theseprocessesarerunninginthehostoperatingsystem.Dockercreateslayersofconfiguration/containmentaroundthoseprocessestocreatetheappearancethattheprocessisrunningunderadifferentoperatingsystem,andwithvarioussystem/networkconfigurationfiles,asspecifiedinthecontainerscreenshot.

TheclaimedadvantageDockerhasovervirtualizationapproaches,suchasVirtualBox,isthatDockerisverylightweight.WeseerightherewhyDockerislightweight:thereisnovirtualizationlayer,thereisonlyacontainerizationprocess(docker-containerd-shim).

Onceyou'resatisfiedthatNotesisworkingontheremoteserver,youcanshutitdownandremoveitasfollows:

$docker-composestop

Stoppingnotesapp...done

Stoppinguserauth...done

Stoppingdb-notes...done

Stoppingdb-auth...done

Thisshutsdownallthecontainersatonce:

$docker-machinestopsandbox

Stopping"sandbox"...

Page 621: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Machine"sandbox"wasstopped.

Thisshutsdowntheremotemachine.Thecloud-hostingproviderdashboardwillshowthattheDroplethasstopped.

Atthispoint,youcangoaheadanddeletetheDockermachineinstanceaswell,ifyoulike:

$docker-machinermsandbox

Abouttoremovesandbox

Areyousure?(y/n):y

Successfullyremovedsandbox

And,ifyou'retrulycertainyouwanttodeletethemachine,theprecedingcommanddoesthedeed.Assoonasyoudothis,themachinewillbeerasedfromyourcloud-hostingproviderdashboard.

Page 622: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SummaryThischapterhasbeenquiteajourney.Wewentfromanapplicationthatexistedsolelyonourlaptop,toexploringtwowaystodeployNode.jsapplicationstoaproductionserver.

WestartedbyreviewingtheNotesapplicationarchitectureandhowthatwillaffectdeployment.Thatenabledyoutounderstandwhatyouhadtodoforserverdeployment.

ThenyoulearnedthetraditionalwaytodeployservicesonLinuxusinganinitscript.PM2isausefultoolformanagingbackgroundprocessesinsuchanenvironment.Youalsolearnedhowtoprovisionaremoteserverusingavirtualmachinehostingservice.

ThenyoutookalongtripintothelandofDocker,anewandexcitingsystemfordeployingservicesonmachines.YoulearnedhowtowriteaDockerfilesothatDockerknowshowtoconstructaserviceimage.YoulearnedseveralwaystodeployDockerimagesonalaptoporonaremoteserver.Andyoulearnedhowtodescribeamulti-containerapplicationusingDockercompose.

You'realmostreadytowrapupthisbook.You'velearnedalotalongtheway;therearetwofinalthingstocover.

Inthenextchapter,wewilllearnaboutbothunittestingandfunctionaltesting.Whileacoreprincipleoftest-drivendevelopmentistowritetheunittestsbeforewritingtheapplication,we'vedoneittheotherwayaroundandputthechapteraboutunittestingattheendofthisbook.That'snottosayunittestingisunimportant,becauseitisextremelyimportant.

Inthefinalchapter,we'llexplorehowtohardenourapplication,andapplicationinfrastructure,againstattackers.

Page 623: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

UnitTestingandFunctionalTestingUnittestinghasbecomeaprimarypartofgoodsoftwaredevelopmentpractice.Itisamethodbywhichindividualunitsofsourcecodearetestedtoensureproperfunctioning.Eachunitistheoreticallythesmallesttestablepartofanapplication.InaNode.jsapplication,youmightconsidereachmoduleasaunit.

Inunittesting,eachunitistestedseparately,isolatingtheunitundertestasmuchaspossiblefromotherpartsoftheapplication.Ifatestfails,youwouldwantittobeduetoabuginyourcoderatherthanabuginthepackagethatyourcodehappenstouse.Acommontechniqueistousemockobjectsormockdatatoisolateindividualpartsoftheapplicationfromoneanother.

Functionaltesting,ontheotherhand,doesn'ttrytotestindividualcomponents,butinsteaditteststhewholesystem.Generallyspeaking,unittestingisperformedbythedevelopmentteam,andfunctionaltestingisperformedbyaQualityAssurance(QA)orQualityEngineering(QE)team.Bothtestingmodelsareneededtofullycertifyanapplication.Ananalogymightbethatunittestingissimilartoensuringthateachwordinasentenceiscorrectlyspelled,whilefunctionaltestingensuresthattheparagraphcontainingthatsentencehasagoodstructure.

Inthischapter,we'llcover:

Assertionsasthebasisofsoftwaretests

TheMochaunittestingframeworkandtheChaiassertionslibrary

Usingteststofindbugsandfixingthebug

Page 624: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

UsingDockertomanagetestinfrastructure

TestingaRESTbackendservice

UItestinginarealwebbrowserusingPuppeteer

ImprovingUItestabilitywithelementIDattributes

Page 625: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Assert–thebasisoftestingmethodologiesNode.jshasausefulbuilt-intestingtool,theassertmodule.Itsfunctionalityissimilartoassertlibrariesinotherlanguages.Namely,it'sacollectionoffunctionsfortestingconditions,andiftheconditionsindicateanerror,theassertfunctionthrowsanexception.

Atitssimplest,atestsuiteisaseriesofassertcallstovalidatethebehaviorofathingbeingtested.Forexample,atestsuitecouldinstantiatetheuserauthenticationservice,thenmakeanAPIcall,usingassertmethodstovalidatetheresult,thenmakeanotherAPIcall,validatingitsresults,andsoon.

Consideracodesnippetlikethis,whichyoucouldsaveinafilenameddeleteFile.js:

constfs=require('fs');

exports.deleteFile=function(fname,callback){

fs.stat(fname,(err,stats)=>{

if(err)callback(newError(`thefile${fname}doesnotexist`));

else{

fs.unlink(fname,err2=>{

if(err)callback(newError(`couldnotdelete${fname}`));

elsecallback();

});

}

});

};

Thefirstthingtonoticeisthiscontainsseverallayersofasynchronouscallbackfunctions.Thatpresentsacoupleofchallenges:

Page 626: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Capturingerrorsfromdeepinsideacallback,toensurethetestscenariofails

Detectingconditionswherethecallbacksarenevercalled

Thefollowingisanexampleofusingassertfortesting.Createafilenamedtest-deleteFile.jscontainingthefollowing:

constfs=require('fs');

constassert=require('assert');

constdf=require('./deleteFile');

df.deleteFile("no-such-file",(err)=>{

assert.throws(

function(){if(err)throwerr;},

function(error){

if((errorinstanceofError)

&&doesnotexist.test(error)){

returntrue;

}elsereturnfalse;

},

"unexpectederror"

);

});

Thisiswhat'scalledanegativetestscenario,inthatit'stestingwhetherrequestingtodeleteanonexistentfilethrowsanerror.

Ifyouarelookingforaquickwaytotest,theassertmodulecanbeusefulwhenusedthisway.Ifitrunsandnomessagesareprinted,thenthetestpasses.But,diditcatchtheinstanceofthedeleteFilecallbackneverbeingcalled?

$nodetest-deleteFile.js

Theassertmoduleisusedbymanyofthetestframeworksasacoretoolforwritingtestcases.Whatthetestframeworksdoiscreateafamiliartestsuiteandtestcasestructuretoencapsulateyourtestcode.

Page 627: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TherearemanystylesofassertionlibrariesavailableinNode.js.Laterinthischapter,we'llusetheChaiassertionlibrary(http://chaijs.com/)whichgivesyouachoicebetweenthreedifferentassertionstyles(should,expect,andassert).

Page 628: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TestingaNotesmodelLet'sstartourunittestingjourneywiththedatamodelswewrotefortheNotesapplication.Becausethisisunittesting,themodelsshouldbetestedseparatelyfromtherestoftheNotesapplication.

InthecaseofmostoftheNotesmodels,isolatingtheirdependenciesimpliescreatingamockdatabase.Areyougoingtotestthedatamodelortheunderlyingdatabase?Mockingoutadatabasemeanscreatingafakedatabaseimplementation,whichdoesnotlooklikeaproductiveuseofourtime.Youcanarguethattestingadatamodelisreallyabouttestingtheinteractionbetweenyourcodeandthedatabase,thatmockingoutthedatabasemeansnottestingthatinteraction,andthereforeweshouldtestourcodeagainstthedatabaseengineusedinproduction.

Withthatlineofreasoninginmind,we'llskipmockingoutthedatabase,andinsteadrunthetestsagainstadatabasecontainingtestdata.Tosimplifylaunchingthetestdatabase,we'lluseDockertostartandstopaversionoftheNotesapplicationstackthat'ssetupfortesting.

Page 629: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

MochaandChai–thechosentesttoolsIfyouhaven'talreadydoneso,duplicatethesourcetreetouseinthischapter.Forexample,ifyouhadadirectorynamedchap10,createonenamedchap11containingeverythingfromchap10.

Inthenotesdirectory,createanewdirectorynamedtest.

Mocha(http://mochajs.org/)isoneofmanytestframeworksavailableforNode.js.Asyou'llseeshortly,ithelpsuswritetestcasesandtestsuites,anditprovidesatestresultsreportingmechanism.ItwaschosenoverthealternativesbecauseitsupportsPromises.ItfitsverywellwiththeChaiassertionlibrarymentionedearlier.And,we'llneedtouseES6modulesfromtestsuiteswritteninCommonJS,andthereforewemustusetheesmmodule.

Youmayfindreferencestoanearlier@std/esmmodule.Thatmodulehasbeendeprecated,withesmputinitsplace.

Whileinthenotes/testdirectory,typethistoinstallMocha,Chai,andesm:$npminit...answerthequestionstocreatepackage.json$npminstallmocha@[email protected]

Page 630: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

NotesmodeltestsuiteBecausewehaveseveralNotesmodels,thetestsuiteshouldrunagainstanymodel.WecanwritetestsusingtheNotesmodelAPIwedeveloped,andanenvironmentvariableshouldbeusedtodeclarethemodeltotest.

Becausewe'vewrittentheNotesapplicationusingES6modules,wehaveasmallchallengetoovercome.MochaonlysupportsrunningtestsinCommonJSmodules,andNode.js(asofthiswriting)doesnotsupportloadinganES6modulefromaCommonJSmodule.AnES6modulecanuseimporttoloadaCommonJSmodule,butaCommonJSmodulecannotuserequiretoloadanES6module.Therearevarioustechnicalreasonsbehindthis,thebottomlineisthatwe'relimitedinthisway.

BecauseMocharequiresthattestsbeCommonJSmodules,we'reinthepositionofhavingtoloadanES6moduleintoaCommonJSmodule.Amodule,esm,existswhichallowsthatcombinationtowork.Ifyou'llreferback,weinstalledthatmoduleintheprevioussection.Let'sseehowtouseit.

Inthetestdirectory,createafilenamedtest-model.jscontainingthisastheoutershellofthetestsuite:'usestrict';require=require("esm")(module,{"esm":"js"});constassert=require('chai').assert;constmodel=require('../models/notes');describe("ModelTest",function(){..});

ThesupporttoloadES6modulesisenabledbytherequire('esm')statementshownhere.Itreplacesthestandardrequirefunctionwithonefromtheesmmodule.ThatparameterlistattheendenablesthefeaturetoloadES6modulesinaCommonJSmodule.Onceyou'vedonethis,yourCommonJSmodulecanloadanES6moduleasevidencedbyrequire('../models/notes')acoupleoflineslater.

Page 631: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TheChailibrarysupportsthreeflavorsofassertions.We'reusingtheassertstylehere,butit'seasytouseadifferentstyleifyouprefer.FortheotherstylessupportedbyChai,seehttp://chaijs.com/guide/styles/.

Chai'sassertionsincludeaverylonglistofusefulassertionfunctions,seehttp://chaijs.com/api/assert/.

TheNotesmodeltotestmustbeselectedwiththeNOTES_MODELenvironmentvariable.Forthemodelsthatalsoconsultenvironmentvariables,we'llneedtosupplythatconfigurationaswell.

WithMocha,atestsuiteiscontainedwithinadescribeblock.Thefirstargumentisdescriptivetext,whichyouusetotailorthepresentationoftestresults.

Ratherthanmaintainingaseparatetestdatabase,wecancreateoneontheflywhileexecutingtests.Mochahaswhatarecalledhooks,whicharefunctionsexecutedbeforeoraftertestcaseexecution.Thehookfunctionsletyou,thetestsuiteauthor,setupandteardownrequiredconditionsforthetestsuitetooperateasdesired.Forexample,tocreateatestdatabasewithknowntestcontent:describe("ModelTest",function(){beforeEach(asyncfunction(){try{constkeyz=awaitmodel.keylist();for(letkeyofkeyz){awaitmodel.destroy(key);}awaitmodel.create("n1","Note1","Note1");awaitmodel.create("n2","Note2","Note2");awaitmodel.create("n3","Note3","Note3");}catch(e){console.error(e);throwe;}});..});

ThisdefinesabeforeEachhook,whichisexecutedbeforeeverytestcase.

Page 632: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Theotherhooksarebefore,after,beforeEach,andafterEach.Theeachhooksaretriggeredbeforeoraftereachtestcaseexecution.

Thisismeanttobeacleanup/preparationstepbeforeeverytest.ItusesourNotesAPItofirstdeleteallnotesfromthedatabase(ifany)andthencreateasetofnewnoteswithknowncharacteristics.Thistechniquesimplifiestestsbyensuringthatwehaveknownconditionstotestagainst.

Wealsohaveasideeffectoftestingthemodel.keylistandmodel.createmethods.

InMocha,aseriesoftestcasesareencapsulatedwithadescribeblock,andwrittenusinganitblock.Thedescribeblockismeanttodescribethatgroupoftests,andtheitblockisforcheckingassertionsonaspecificaspectofthethingbeingtested.Youcannestthedescribeblocksasdeeplyasyoulike:describe("checkkeylist",function(){it("shouldhavethreeentries",asyncfunction(){constkeyz=awaitmodel.keylist();assert.exists(keyz);assert.isArray(keyz);assert.lengthOf(keyz,3);});it("shouldhavekeysn1n2n3",asyncfunction(){constkeyz=awaitmodel.keylist();assert.exists(keyz);assert.isArray(keyz);assert.lengthOf(keyz,3);for(letkeyofkeyz){assert.match(key,n[123],"correctkey");}});it("shouldhavetitlesNode#",asyncfunction(){constkeyz=awaitmodel.keylist();assert.exists(keyz);assert.isArray(keyz);assert.lengthOf(keyz,3);varkeyPromises=keyz.map(key=>model.read(key));

Page 633: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

constnotez=awaitPromise.all(keyPromises);for(letnoteofnotez){assert.match(note.title,Note[123],"correcttitle");}});});

TheideaistocallNotesAPIfunctions,thentotesttheresultstocheckwhethertheymatchedtheexpectedresults.

Thisdescribeblockiswithintheouterdescribeblock.Thedescriptionsgiveninthedescribeanditblocksareusedtomakethetestreportmorereadable.Theitblockformsapseudo-sentencealongthelinesofit(thethingbeingtested)shoulddothisorthat.

ItisimportantwithMochatonotusearrowfunctionsinthedescribeanditblocks.Bynow,youwillhavegrownfondofarrowfunctionsbecauseofhowmucheasiertheyaretowrite.But,MochacallsthesefunctionswithathisobjectcontainingusefulfunctionsforMocha.Becausearrowfunctionsavoidsettingupathisobject,Mochawouldbreak.

EventhoughMocharequiresregularfunctionsforthedescribeanditblocks,wecanusearrowfunctionswithinthosefunctions.

HowdoesMochaknowwhetherthetestcodepasses?Howdoesitknowwhenthetestfinishes?Thissegmentofcodeshowsoneofthethreemethods.

Generally,Mochaislookingtoseeifthefunctionthrowsanexception,orwhetherthetestcasetakestoolongtoexecute(atimeoutsituation).Ineithercase,Mochawillindicateatestfailure.That'sofcoursesimpletodeterminefornon-asynchronouscode.But,Node.jsisallaboutasynchronouscode,andMochahastwomodelsfortestingasynchronouscode.Inthefirst(notseenhere),Mochapassesinacallbackfunction,andthetestcodeistocallthecallbackfunction.Inthesecond,asseenhere,itlooksforaPromisebeingreturnedbythetestfunction,anddeterminespass/failonwhetherthePromiseisintheresolveorrejectstate.

Inthiscase,we'reusingasyncfunctions,becausetheyautomaticallyreturnaPromise.Withinthefunctions,we'recallingasynchronousfunctions

Page 634: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

usingawait,ensuringanythrownexceptionisindicatedasarejectedPromise.

Anotheritemtonoteisthequestionaskedearlier:whatifthecallbackfunctionwe'retestingisnevercalled?Or,whatifaPromiseisneverresolved?Mochastartsatimerandifthetestcasedoesnotfinishbeforethetimerexpires,Mochafailsthetestcase.

Page 635: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ConfiguringandrunningtestsWehavemoreteststowrite,butlet'sfirstgetsetuptorunthetests.Thesimplestmodeltotestisthein-memorymodel.Let'saddthistothescriptssectionofnotes/test/package.json:

"test-notes-memory":"NOTES_MODEL=memorymochatest-model",

Toinstalldependencies,wemustrunnpminstallinboththenotes/testandnotesdirectories.Thatwayboththedependenciesforthetestcode,andthedependenciesforNotes,areinstalledintheircorrectplace.

Then,wecanrunitasfollows:

$npmruntest-notes-memory

>[email protected]/chap11/notes/test

>NOTES_MODEL=memorymochatest-model

ModelTest

checkkeylist

√shouldhavethreeentries

√shouldhavekeysn1n2n3

√shouldhavetitlesNode#

3passing(18ms)

Themochacommandisusedtorunthetestsuite.

Thestructureoftheoutputfollowsthestructureofthedescribeanditblocks.Youshouldsetupthedescriptivetextstringssoitreadsnicely.

Page 636: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

MoretestsfortheNotesmodelThatwasn'tenoughtotestmuch,solet'sgoaheadandaddsomemoretests:

describe("readnote",function(){

it("shouldhavepropernote",asyncfunction(){

constnote=awaitmodel.read("n1");

assert.exists(note);

assert.deepEqual({key:note.key,title:note.title,body:

note.body},{

key:"n1",title:"Note1FAIL",body:"Note1"

});

});

it("Unknownnoteshouldfail",asyncfunction(){

try{

constnote=awaitmodel.read("badkey12");

assert.notExists(note);

thrownewError("shouldnotgethere");

}catch(err){

//thisisexpected,sodonotindicateerror

assert.notEqual(err.message,"shouldnotgethere");

}

});

});

describe("changenote",function(){

it("afterasuccessfulmodel.update",asyncfunction(){

constnewnote=awaitmodel.update("n1","Note1title

changed","Note1bodychanged");

constnote=awaitmodel.read("n1");

assert.exists(note);

assert.deepEqual({key:note.key,title:note.title,body:

note.body},{

key:"n1",title:"Note1titlechanged",body:"Note1body

changed"

});

});

});

describe("destroynote",function(){

Page 637: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

it("shouldremovenote",asyncfunction(){

awaitmodel.destroy("n1");

constkeyz=awaitmodel.keylist();

assert.exists(keyz);

assert.isArray(keyz);

assert.lengthOf(keyz,2);

for(letkeyofkeyz){

assert.match(key,n[23],"correctkey");

}

});

it("shouldfailtoremoveunknownnote",asyncfunction(){

try{

awaitmodel.destroy("badkey12");

thrownewError("shouldnotgethere");

}catch(err){

//thisisexpected,sodonotindicateerror

assert.notEqual(err.message,"shouldnotgethere");

}

});

});

after(function(){model.close();});

});

Noticethatforthenegativetests–wherethetestpassesifanerroristhrown–werunitinatry/catchblock.ThethrownewErrorlineineachcaseshouldnotexecutebecausetheprecedingcodeshouldthrowanerror.Therefore,wecancheckifthemessageinthatthrownerroristhemessagewhicharrives,andfailthetestifthat'sthecase.

Now,thetestreport:

$npmruntest-notes-memory

>[email protected]/chap11/notes/test

>NOTES_MODEL=memorymochatest-model

ModelTest

checkkeylist

√shouldhavethreeentries

√shouldhavekeysn1n2n3

√shouldhavetitlesNode#

readnote

√shouldhavepropernote

√Unknownnoteshouldfail

Page 638: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

changenote

√afterasuccessfulmodel.update

destroynote

√shouldremovenote

√shouldfailtoremoveunknownnote

8passing(17ms)

Intheseadditionaltests,wehaveacoupleofnegativetests.Ineachtestthatweexpecttofail,wesupplyanotekeythatweknowisnotinthedatabase,andwethenensurethatthemodelgivesusanerror.

TheChaiAssertionsAPIincludessomeveryexpressiveassertions.Inthiscase,we'veusedthedeepEqualmethodwhichdoesadeepcomparisonoftwoobjects.Inourcase,itlookslikethis:

assert.deepEqual({key:note.key,title:note.title,body:note.body},{

key:"n1",title:"Note1",body:"Note1"

});

Thisreadsnicelyinthetestcode,butmoreimportantlyareportedtestfailurelooksverynice.Sincethesearecurrentlypassing,tryintroducinganerrorbychangingoneoftheexpectedvaluestrings.Uponrerunningthetest,you'llsee:

ModelTest

checkkeylist

√shouldhavethreeentries

√shouldhavekeysn1n2n3

√shouldhavetitlesNode#

readnote

1)shouldhavepropernote

√Unknownnoteshouldfail

changenote

√afterasuccessfulmodel.update

destroynote

√shouldremovenote

√shouldfailtoremoveunknownnote

7passing(42ms)

1failing

Page 639: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

1)ModelTest

readnote

shouldhavepropernote:

AssertionError:expected{Object(key,title,...)}todeeply

equal{Object(key,title,...)}

+expected-actual

{

"body":"Note1"

"key":"n1"

-"title":"Note1"

+"title":"Note1FAIL"

}

atContext.<anonymous>(test-model.js:53:16)

at<anonymous>

Atthetopisthestatusreportofeachtestcase.Foronetest,insteadofacheckmarkisanumber,andthenumbercorrespondstothereporteddetailsatthebottom.Mochapresentstestfailuresthiswaywhenthespecreporterisused.Mochasupportsothertestreportformats,someofwhichproducedatathatcanbesentintoteststatusreportingsystems.Formoreinformation,seehttps://mochajs.org/#reporters.

Inthiscase,thefailurewasdetectedbyadeepEqualmethod,whichpresentsthedetectedobjectinequalityinthisway.

Page 640: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TestingdatabasemodelsThatwasgood,butweobviouslywon'trunNotesinproductionwiththein-memoryNotesmodel.Thismeansthatweneedtotestalltheothermodels.

TestingtheLevelUPandfilesystemmodelsiseasy,justaddthistothescriptssectionofpackage.json:"test-notes-levelup":"NOTES_MODEL=levelupmocha","test-notes-fs":"NOTES_MODEL=fsmocha",

Thenrunthefollowingcommand:$npmruntest-notes-fs$npmruntest-notes-levelup

Thiswillproduceasuccessfultestresult.

ThesimplestdatabasetotestisSQLite3,sinceitrequireszerosetup.WehavetwoSQLite3modelstotest,let'sstartwithnotes-sqlite3.js.Addthefollowingtothescriptssectionofpackage.json:"test-notes-sqlite3":"rm-fchap11.sqlite3&&sqlite3chap11.sqlite3--init../models/chap07.sql<devnull&&NOTES_MODEL=sqlite3SQLITE_FILE=chap11.sqlite3mochatest-model",

Thiscommandsequenceputsthetestdatabaseinthechap11.sqlite3file.Itfirstinitializesthatdatabaseusingthesqlite3command-linetool.Notethatwe'veconnecteditsinputtodevnullbecausethesqlite3commandwillpromptforinputotherwise.Then,itrunsthetestsuitepassinginenvironmentvariablesrequiredtorunagainsttheSQLite3model.

Runningthetestsuitedoesfindtwoerrors:$npmruntest-notes-sqlite3

>[email protected]/chap11/notes/test>rm-fchap11.sqlite3&&sqlite3chap11.sqlite3--init

Page 641: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

../models/chap07.sql<devnull&&NOTES_MODEL=sqlite3SQLITE_FILE=chap11.sqlite3mochatest-model

ModelTestcheckkeylist√shouldhavethreeentries√shouldhavekeysn1n2n3√shouldhavetitlesNode#readnote√shouldhavepropernote1)Unknownnoteshouldfailchangenote√afterasuccessfulmodel.update(114ms)destroynote√shouldremovenote(103ms)2)shouldfailtoremoveunknownnote

6passing(6s)2failing

1)ModelTestreadnoteUnknownnoteshouldfail:UncaughtTypeError:Cannotreadproperty'notekey'ofundefinedatStatement.db.get(/home/david/nodewebdev/nodeweb-development-code-4th-edition/chap11/notes/models/notes-sqlite3.mjs:64:39)

2)ModelTestdestroynoteshouldfailtoremoveunknownnote:

AssertionError:expected'shouldnotgethere'tonotequal'shouldnotgethere'+expected-actual

Thefailingtestcallsmodel.read("badkey12"),akeywhichweknowdoesnot

Page 642: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

exist.Writingnegativetestspaidoff.Thefailinglineofcodeatmodels/notes-sqlite3.mjs(line64)readsasfollows:constnote=newNote(row.notekey,row.title,row.body);

It'seasyenoughtoinsertconsole.log(util.inspect(row));justbeforethisandlearnthat,forthefailingcall,SQLite3gaveusundefinedforrow,explainingtheerrormessage.

Thetestsuitecallsthereadfunctionmultipletimeswithanotekeyvaluethatdoesexist.Obviously,whengivenaninvalidnotekeyvalue,thequerygivesanemptyresultssetandSQLite3invokesthecallbackwithboththeundefinederrorandtheundefinedrowvalues.Thisiscommonbehaviorfordatabasemodules.Anemptyresultsetisn'tanerror,andthereforewereceivednoerrorandanundefinedrow.

Infact,wesawthisbehaviorearlierwithmodels/notes-sequelize.mjs.Theequivalentcodeinmodels/notes-sequelize.mjsdoestherightthing,andithasacheck,whichwecanadapt.Let'srewritethereadfunctioninmodels/notes-sqlite.mjstothis:exportasyncfunctionread(key){vardb=awaitconnectDB();varnote=awaitnewPromise((resolve,reject)=>{db.get("SELECT*FROMnotesWHEREnotekey=?",[key],(err,row)=>{if(err)returnreject(err);if(!row){reject(newError(`Nonotefoundfor${key}`));}else{constnote=newNote(row.notekey,row.title,row.body);resolve(note);}});});returnnote;}

Thisissimple,wejustcheckwhetherrowisundefinedand,ifso,throwanerror.Whilethedatabasedoesn'tseeanemptyresultssetasanerror,Notesdoes.Furthermore,Notesalreadyknowshowtodealwithathrownerror

Page 643: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Ifweinspecttheothermodels,they'rethrowingerrorsforanonexistentkey.InSQL,itobviouslyisnotanerrorifthisSQL(frommodels/notes-sqlite3.mjs)doesnotdeleteanything:db.run("DELETEFROMnotesWHEREnotekey=?;",...);

Unfortunately,thereisn'taSQLoptiontomakethisSQLstatementfailifitdoesnotdeleteanyrecords.Therefore,wemustaddachecktoseeifarecordexists.Namely:exportasyncfunctiondestroy(key){constdb=awaitconnectDB();constnote=awaitread(key);returnawaitnewPromise((resolve,reject)=>{db.run("DELETEFROMnotesWHEREnotekey=?;",[key],err=>{if(err)returnreject(err);resolve();});});}Therefore,wereadthenoteandasabyproductweverifythenoteexists.Ifthenotedoesn'texist,readwillthrowanerror,andtheDELETEoperationwillnotevenrun.ThesearethebugswereferredtoinChapter7,DataStorageandRetrieval.Wesimplyforgottocheckfortheseconditionsinthis

inthiscase.Makethischangeandthatparticulartestcasepasses.

Thereisasecondsimilarerrorinthedestroylogic.Thetesttodestroyanonexistentnotefailstoproduceanerroratthisline:

awaitmodel.destroy("badkey12");

Page 644: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

particularmodel.Thankfully,ourdiligenttestingcaughttheproblem.Atleast,that'sthestorytotellthemanagersratherthantellingthemthatweforgottocheckforsomethingwealreadyknewcouldhappen.Nowthatwe'vefixedmodels/notes-sqlite3.mjs,let'salsotestmodels/notes-sequelize.mjsusingtheSQLite3database.Todothis,weneedaconnectionobjecttospecifyintheSEQUELIZE_CONNECTvariable.Whilewecanreusetheexistingone,let'screateanewone.Createafilenamedtest/sequelize-sqlite.yamlcontainingthis:dbname:notestestusername:password:params:dialect:sqlitestorage:notestest-sequelize.sqlite3logging:falseThisway,wedon'toverwritetheproductiondatabaseinstancewithourtestsuite.Sincethetestsuitedestroysthedatabaseittests,itmustberunagainstadatabasewearecomfortabledestroying.TheloggingparameterturnsoffthevoluminousoutputSequelizeproducessothatwecanreadthetestresultsreport.Addthefollowingtothescriptssectionofpackage.json:"test-notes-sequelize-sqlite":"NOTES_MODEL=sequelizeSEQUELIZE_CONNECT=sequelize-sqlite.yamlmochatest-model"Thenrunthetestsuite:$npmruntest-notes-sequelize-sqlite

..

8passing(2s)

Wepasswithflyingcolors!We'vebeenabletoleveragethesametestsuiteagainstmultipleNotesmodels.Weevenfoundtwobugsinonemodel.But,wehavetwotestconfigurationsremainingtotest.Ourtestresultsmatrixreadsasfollows:models-fs:PASSmodels-memory:PASSmodels-levelup:PASSmodels-sqlite3:2failures,nowfixedmodels-sequelize:withSQLite3:PASS

Page 645: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

models-sequelize:withMySQL:untestedmodels-mongodb:untestedThetwountestedmodelsbothrequirethesetupofadatabaseserver.Weavoidedtestingthesecombinations,butourmanagerwon'tacceptthatexcusebecausetheCEOneedstoknowwe'vecompletedthetestcycles.Notesmustbetestedinasimilarconfigurationtotheproductionenvironment.Inproduction,we'llbeusingaregulardatabaseserver,ofcourse,withMySQLorMongoDBbeingtheprimarychoices.Therefore,weneedawaythatincursalowoverheadtoruntestsagainstthosedatabases.Testingagainsttheproductionconfigurationmustbesoeasythatweshouldfeelnoresistanceindoingso,toensurethattestsarerunoftenenoughtomakethedesiredimpact.Fortunately,we'vealreadyhadexperienceofatechnologythatsupportseasilycreatinganddestroyingthedeploymentinfrastructure.Hello,Docker!

Page 646: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

UsingDockertomanagetestinfrastructureOneadvantageDockergivesistheabilitytoinstalltheproductionenvironmentonourlaptop.It'sthenveryeasytopushthesameDockersetuptothecloud-hostingenvironmentforstagingorproductiondeployment.

Whatwe'lldointhissectionisdemonstratereusingtheDockerComposeconfigurationdefinedpreviouslyfortestinfrastructure,andtoautomateexecutingtheNotestestsuiteinsidethecontainersusingashellscript.Generallyspeaking,it'simportanttoreplicatetheproductionenvironmentwhenrunningtests.Dockercanmakethisaneasythingtodo.

UsingDocker,we'llbeabletoeasilytestagainstadatabase,andhaveasimplemethodforstartingandstoppingatestversionofourproductionenvironment.Let'sgetstarted.

Page 647: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

DockerComposetoorchestratetestinfrastructureWehadagreatexperienceusingDockerComposetoorchestrateNotesapplicationdeployment.Thewholesystem,withfourindependentservices,iseasilydescribedincompose/docker-compose.yml.Whatwe'lldoisduplicatetheComposefile,thenmakeacoupleofsmallchangesrequiredtosupporttestexecution.

Let'sstartbymakinganewdirectory,test-compose,asasiblingtothenotes,users,andcomposedirectories.Copycompose/docker-compose.ymltothenewlycreatedtest-composedirectory.We'llbemakingseveralchangestothisfileandacoupleofsmallchangestotheexistingDockerfiles.

Wewanttochangethecontainerandnetworknamessoourtestinfrastructuredoesn'tclobbertheproductioninfrastructure.We'llconstantlydeleteandrecreatethetestcontainers,soastokeepthedevelopershappy,we'llleavedevelopmentinfrastructurealoneandperformtestingonseparateinfrastructure.Bymaintainingseparatetestcontainersandnetworks,ourtestscriptscandoanythingtheylikewithoutdisturbingthedevelopmentorproductioncontainers.

Considerthischangetothedb-authanddb-notescontainers:

db-userauth-test:

build:../authnet

container_name:db-userauth-test

networks:

-authnet-test

environment:

MYSQL_RANDOM_ROOT_PASSWORD:"true"

MYSQL_USER:userauth-test

MYSQL_PASSWORD:userauth-test

MYSQL_DATABASE:userauth-test

Page 648: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

volumes:

-db-userauth-test-data:/var/lib/mysql

restart:always

..

db-notes-test:

build:../frontnet

container_name:db-notes-test

networks:

-frontnet-test

environment:

MYSQL_RANDOM_ROOT_PASSWORD:"true"

MYSQL_USER:notes-test

MYSQL_PASSWORD:notes12345

MYSQL_DATABASE:notes-test

volumes:

-db-notes-test-data:/var/lib/mysql

restart:always

Thisisthesameasearlier,butwith-testappendedtocontainerandnetworknames.

That'sthefirstchangewemustmake,append-testtoeverycontainerandnetworknameintest-compose/docker-compose.yml.Everythingwe'lldowithtestswillrunoncompletelyseparatecontainers,hostnames,andnetworksfromthoseofthedevelopmentinstance.

Thischangewillaffectthenotes-testanduserauth-testservicesbecausethedatabaseserverhostnamesarenowdb-auth-testanddb-notest-test.Thereareseveralenvironmentvariablesorconfigurationfilestoupdate.

Anotherconsiderationistheenvironmentvariablesrequiredtoconfiguretheservices.Previously,wedefinedallenvironmentvariablesintheDockerfiles.It'sextremelyusefultoreusethoseDockerfilessoweknowwe'retestingthesamedeploymentasisusedinproduction.Butweneedtotweaktheconfigurationsettingstomatchthetestinfrastructure.

Thedatabaseconfigurationshownhereisanexample.ThesameDockerfilesareused,butwealsodefineenvironmentvariablesintest-compose/docker-compose.yml.Asyoumightexpect,thisoverridestheDockerfileenvironmentvariableswiththevaluessethere:

Page 649: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

userauth-test:

build:../users

container_name:userauth-test

depends_on:

-db-userauth-test

networks:

-authnet-test

-frontnet-test

environment:

DEBUG:""

NODE_ENV:"test"

SEQUELIZE_CONNECT:"sequelize-docker-test-mysql.yaml"

HOST_USERS_TEST:"localhost"

restart:always

volumes:

-./reports-userauth:/reports

..

notes-test:

build:../notes

container_name:notes-test

depends_on:

-db-notes-test

networks:

-frontnet-test

ports:

-"3000:3000"

restart:always

environment:

NODE_ENV:"test"

SEQUELIZE_CONNECT:"test/sequelize-mysql.yaml"

USER_SERVICE_URL:"http://userauth-test:3333"

volumes:

-./reports-notes:/reports

...

networks:

frontnet-test:

driver:bridge

authnet-test:

driver:bridge

volumes:

db-userauth-test-data:

db-notes-test-data:

Again,wechangedthecontainerandnetworknamestoappend-test.WemovedsomeoftheenvironmentvariablesfromDockerfiletotest-compose/docker-compose.yml.Finally,weaddedsomedatavolumestomount

Page 650: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

hostdirectoriesinsidethecontainer.

Anotherthingtodoistosetupdirectoriestostoretestcode.AcommonpracticeinNode.jsprojectsistoputtestcodeinthesamedirectoryastheapplicationcode.Earlierinthischapter,wedidso,implementingasmalltestsuiteinthenotes/testdirectory.Asitstands,notes/Dockerfiledoesnotcopythatdirectoryintothecontainer.Thetestcodemustexistinthecontainertoexecutethetests.Anotherissueisit'shelpfultonotdeploytestcodeinproduction.

Whatwecandoistoensurethattest-compose/docker-compose.ymlmountsnotes/testintothecontainer:

notes-test:

...

volumes:

-./reports-notes:/reports

-../notes/test:/notesapp/test

Thisgivesusthebestofbothworlds.

Thetestcodeisinnotes/testwhereitbelongs

Thetestcodeisnotcopiedintotheproductioncontainer

Intestmode,thetestdirectoryappearswhereitbelongs

WehaveacoupleofconfigurationfilesremainingfortheSequelizedatabaseconnectiontosetup.

Fortheuserauth-testcontainer,theSEQUELIZE_CONNECTvariablenowreferstoaconfigurationfilethatdoesnotexist,thankstooverridingthevariableinuser/Dockerfile.Let'screatethatfileastest-compose/userauth/sequelize-docker-mysql.yaml,containingthefollowing:

dbname:userauth-test

username:userauth-test

Page 651: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

password:userauth-test

params:

host:db-userauth-test

port:3306

dialect:mysql

Thevaluesmatchthevariablespassedtothedb-userauth-testcontainer.Thenwemustensurethisconfigurationfileismountedintotheuserauth-testcontainer:

userauth-test:

...

volumes:

-./reports-userauth:/reports

-./userauth/sequelize-docker-test-mysql.yaml:/userauth/sequelize-

docker-test-mysql.yaml

Fornotes-testwehaveaconfigurationfile,test/sequelize-mysql.yaml,toputinthenotes/testdirectory:

dbname:notes-test

username:notes-test

password:notes12345

params:

host:db-notes-test

port:3306

dialect:mysql

logging:false

Again,thismatchestheconfigurationvariablesindb-notes-test.Intest-compose/docker-compose.yml,wemountthatfileintothecontainer.

Page 652: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ExecutingtestsunderDockerComposeNowwe'rereadytoexecutesomeofthetestsinsideacontainer.We'veusedaDockerComposefiletodescribethetestenvironmentfortheNotesapplication,usingthesamearchitectureasintheproductionenvironment.Thetestscriptsandconfigurationhasbeeninjectedintothecontainers.Thequestionis,howdoweautomatetestexecution?

Thetechniquewe'lluseistorunashellscript,andusedockerexec-ittoexecutecommandstorunthetestscripts.Thisissomewhatautomated,andwithsomemoreworkitcanbefullyautomated.

Intest-compose,let'smakeashellscriptcalledrun.sh(onWindows,run.ps1):

docker-composestop

docker-composebuild

docker-composeup--force-recreate-d

dockerps

dockernetworkls

sleep20

dockerexec-it--workdirnotesapptest-eDEBUG=notes-testnpminstall

dockerexec-it--workdirnotesapptest-eDEBUG=notes-testnpmruntest-

notes-memory

dockerexec-it--workdirnotesapptest-eDEBUG=notes-testnpmruntest-

notes-fs

dockerexec-it--workdirnotesapptest-eDEBUG=notes-testnpmruntest-

notes-levelup

dockerexec-it--workdirnotesapptest-eDEBUG=notes-testnpmruntest-

notes-sqlite3

dockerexec-it--workdirnotesapptest-eDEBUG=notes-testnpmruntest-

notes-sequelize-sqlite

dockerexec-it--workdirnotesapptest-eDEBUG=notes-testnpmruntest-

notes-sequelize-mysql

Page 653: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

docker-composestop

It'scommonpracticetoruntestsoutofacontinuousintegrationsystemsuchasJenkins.Continuousintegrationsystemsautomaticallyrunbuildsortestsagainstsoftwareproducts.Thebuildandtestresultsdataisusedtoautomaticallygeneratestatuspages.Visithttps://jenkins.io/index.html,whichisagoodstartingpointforaJenkinsjob.

Thatmakesthefirstrealsteptobuildingthecontainers,followedbybringingthemup.Thescriptsleepsforafewsecondstogivethecontainerstimetofullyinstantiatethemselves.

Thesubsequentcommandsallfollowaparticularpatternthatisimportanttounderstand.Thecommandsareexecutedinthenotesapptestdirectorythankstothe--workdiroption.RememberthatdirectoryisinjectedintothecontainerbytheDockerComposefile.

Using-eDEBUG=we'vedisabledtheDEBUGoptions.Ifthoseoptionsareset,we'dhaveexcessunwantedoutputinthetestresults,sousingthisoptionensuresthatdebuggingoutputdoesn'toccur.

Nowthatyouunderstandtheoptions,youcanseethatthesubsequentcommandsareallexecutedinthetestdirectoryusingthepackage.jsoninthatdirectory.Itstartsbyrunningnpminstall,andthenrunningeachofthescenariosinthetestmatrix.

Torunthetests,simplytype:

$sh-xrun.sh

That'sgood,we'vegotmostofourtestmatrixautomatedandprettywellsquaredaway.ThereisaglaringholeinthetestmatrixandpluggingthatholewillletusseehowtosetupMongoDBunderDocker.

Page 654: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

MongoDBsetupunderDockerandtestingNotesagainstMongoDBInChapter7,DataStorageandRetrieval,wedevelopedMongoDBsupportforNotes,andsincethenwe'vefocusedonSequelize.Tomakeupforthatslight,let'smakesureweatleasttestourMongoDBsupport.TestingonMongoDBwouldsimplyrequiredefiningacontainerfortheMongoDBdatabaseandalittlebitofconfiguration.

Visithttps://hub.docker.com/_/mongo/fortheofficialMongoDBcontainer.You'llbeabletoretrofitthistoallowdeployingtheNotesapplicationrunningonMongoDB.

Addthistotest-compose/docker-compose.yml:

db-notes-mongo-test:

image:mongo:3.6-jessie

container_name:db-notes-mongo-test

networks:

-frontnet-test

volumes:

-./db-notes-mongo:/data/db

That'sallthat'srequiredtoaddaMongoDBcontainertoaDockerComposefile.We'veconnectedittofrontnetsothatthenotes(notes-test)containercanaccesstheservice.

Theninnotes/test/package.jsonweaddalinetofacilitaterunningtestsonMongoDB:

"test-notes-mongodb":"MONGO_URL=mongodb://db-notes-mongo-test/

Page 655: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

MONGO_DBNAME=chap11-testNOTES_MODEL=mongodbmocha--no-timeoutstest-model"

SimplybyaddingtheMongoDBcontainertofrontnet-test,thedatabaseisavailableattheURLshownhere.Hence,it'ssimpletonowrunthetestsuiteusingtheNotesMongoDBmodel.

The--no-timeoutsoptionwasnecessarytoavoidaspuriouserrorwhiletestingthesuiteagainstMongoDB.ThisoptioninstructsMochatonotcheckwhetheratestcaseexecutiontakestoolong.

Thefinalrequirementistoaddthislineinrun.sh(orrun.ps1forWindows):

dockerexec-it--workdirnotesapptest-eDEBUG=notes-testnpmruntest-

notes-mongodb

That,then,ensuresMongoDBistestedduringeverytestrun.

Wecannowreporttothemanagerthefinaltestresultsmatrix:

models-fs:PASS

models-memory:PASS

models-levelup:PASS

models-sqlite3:Twofailures,nowfixed,PASS

models-sequelizewithSQLite3:PASS

models-sequelizewithMySQL:PASS

models-mongodb:PASS

Themanagerwilltellyou"goodjob"andthenrememberthattheModelsareonlyaportionoftheNotesapplication.We'velefttwoareascompletelyuntested:

Page 656: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TheRESTAPIfortheuserauthenticationservice

Functionaltestingoftheuserinterface

Let'sgetonwiththosetestingareas.

Page 657: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TestingRESTbackendservicesIt'snowtimetoturnourattentiontotheuserauthenticationservice.We'vementionedtestsofthisservice,sayingthatwe'llgettothemlater.Wehaddevelopedsomescriptsforadhoctesting,whichhavebeenusefulallalong.Butlaterisnow,andit'stimetogetcrackingonsomerealtests.

There'saquestionofwhichtooltousefortestingtheauthenticationservice.Mochadoesagoodjoboforganizingaseriesoftestcases,andweshouldreuseithere.ButthethingwehavetotestisaRESTservice.Thecustomerofthisservice,theNotesapplication,usesitthroughtheRESTAPI,givingusaperfectrationalizationtotestattheRESTinterface.OuradhocscriptsusedtheSuperAgentlibrarytosimplifymakingRESTAPIcalls.Therehappenstobeacompanionlibrary,SuperTest,thatismeantforRESTAPItesting.Readitsdocumentationhere:https://www.npmjs.com/package/supertest.

We'vealreadymadethetest-compose/userauthdirectory.Inthatdirectory,createafilenamedtest.js:

'usestrict';

constassert=require('chai').assert;

constrequest=require('supertest')(process.env.URL_USERS_TEST);

constutil=require('util');

consturl=require('url');

constURL=url.URL;

constauthUser='them';

constauthKey='D4ED43C0-8BD6-4FE2-B358-7C0E230D11EF';

describe("UsersTest",function(){

...Testcodefollows

});

Page 658: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ThissetsupMochaandtheSuperTestclient.TheURL_USERS_TESTenvironmentvariablespecifiesthebaseURLoftheservertorunthetestagainst.You'llalmostcertainlybeusinghttp://localhost:3333giventheconfigurationwe'veusedearlierinthebook.SuperTestinitializesitselfalittledifferentlytoSuperAgent.TheSuperTestmoduleexposesafunctionthatwecallwiththeURL_USERS_TESTenvironmentvariable,thenweuseTHATrequestobjectthroughouttherestofthescripttomakeRESTAPIrequests.

Thisvariablewasalreadysetintest-compose/docker-compose.ymlwiththerequiredvalue.TheotherthingofimportanceisapairofvariablestostoretheauthenticationuserIDandkey:

beforeEach(asyncfunction(){

awaitrequest.post('/create-user')

.send({

username:"me",password:"w0rd",provider:"local",

familyName:"Einarrsdottir",givenName:"Ashildr",

middleName:"",

emails:[],photos:[]

})

.set('Content-Type','application/json')

.set('Acccept','application/json')

.auth(authUser,authKey);

});

afterEach(asyncfunction(){

awaitrequest.delete('destroyme')

.set('Content-Type','application/json')

.set('Acccept','application/json')

.auth(authUser,authKey);

});

Ifyouremember,thebeforeEachfunctionisrunimmediatelybeforeeverytestcase,andafterEachisrunafterward.ThesefunctionsusetheRESTAPItocreateourtestuserbeforerunningthetest,andthenafterwardtodestroythetestuser.Thatwayourtestscanassumethisuserwillexist:

describe("Listuser",function(){

it("listcreatedusers",asyncfunction(){

Page 659: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

constres=awaitrequest.get('/list')

.set('Content-Type','application/json')

.set('Acccept','application/json')

.auth(authUser,authKey);

assert.exists(res.body);

assert.isArray(res.body);

assert.lengthOf(res.body,1);

assert.deepEqual(res.body[0],{

username:"me",id:"me",provider:"local",

familyName:"Einarrsdottir",givenName:"Ashildr",

middleName:"",

emails:[],photos:[]

});

});

});

Now,wecanturntotestingsomeAPImethods,suchasthe/listoperation.

Wehavealreadyguaranteedthatthereisanaccount,inthebeforeEachmethod,so/listshouldgiveusanarraywithoneentry.

ThisfollowsthegeneralpatternforusingMochatotestaRESTAPImethod.First,weuseSuperTest'srequestobjecttocalltheAPImethod,andawaititsresult.Oncewehavetheresult,weuseassertmethodstovalidateitiswhatisexpected:

describe("finduser",function(){

it("findcreatedusers",asyncfunction(){

constres=awaitrequest.get('findme')

.set('Content-Type','application/json')

.set('Acccept','application/json')

.auth(authUser,authKey);

assert.exists(res.body);

assert.isObject(res.body);

assert.deepEqual(res.body,{

username:"me",id:"me",provider:"local",

familyName:"Einarrsdottir",givenName:"Ashildr",

middleName:"",

emails:[],photos:[]

});

});

it("failtofindnonexistentusers",asyncfunction(){

varres;

Page 660: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

try{

res=awaitrequest.get('findnonExistentUser')

.set('Content-Type','application/json')

.set('Acccept','application/json')

.auth(authUser,authKey);

}catch(e){

return;//Testisokayinthiscase

}

assert.exists(res.body);

assert.isObject(res.body);

assert.deepEqual(res.body,{});

});

});

Wearecheckingthe/findoperationintwoways:

Lookingfortheaccountweknowexists–failureisindicatediftheuseraccountisnotfound

Lookingfortheoneweknowdoesnotexist–failureisindicatedifwereceivesomethingotherthananerrororanemptyobject

Addthistestcase:

describe("deleteuser",function(){

it("deletenonexistentusers",asyncfunction(){

varres;

try{

res=awaitrequest.delete('destroynonExistentUser')

.set('Content-Type','application/json')

.set('Acccept','application/json')

.auth(authUser,authKey);

}catch(e){

return;//Testisokayinthiscase

}

assert.exists(res);

assert.exists(res.error);

assert.notEqual(res.status,200);

});

});

Finally,weshouldcheckthe/destroyoperation.Wealreadycheckthis

Page 661: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

operationintheafterEachmethod,wherewedestroyaknownuseraccount.Weneedtoalsoperformthenegativetestandverifyitsbehavioragainstanaccountweknowdoesnotexist.

Thedesiredbehavioristhateitheranerroristhrown,ortheresultshowsanHTTPstatusindicatinganerror.Infact,thecurrentauthenticationservercodegivesa500statuscodealongwithsomeotherinformation.

Intest-compose/docker-compose.yml,weneedtoinjectthisscript,test.js,intotheuserauth-testcontainer.We'lladdthathere:

userauth-test:

...

volumes:

-./reports-userauth:/reports

-./userauth/sequelize-docker-test-mysql.yaml:/userauth/sequelize-docker-

test-mysql.yaml

-./userauth/test.js:/userauth/test.js

Wehaveatestscript,andhaveinjectedthatscriptintothedesiredcontainer(userauth-test).Thenextstepistoautomaterunningthistest.Onewayistoaddthistorun.sh(akarun.ps1onWindows):

dockerexec-it-eDEBUG=userauth-testnpminstallsupertestmochachai

dockerexec-it-eDEBUG=userauth-test./node_modules/.bin/mochatest.js

Now,ifyouruntherun.shtestscriptyou'llseetherequiredpackagesgetinstalled,andthenthistestsuiteexecution.

Page 662: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

AutomatingtestresultsreportingIt'scoolwehaveautomatedtestexecution,andMochamakesthetestresultslooknicewithallthosecheckmarks.Whatifthemanagementwantsagraphoftestfailuretrendsovertime?Ortherecouldbeanynumberofreasonstoreporttestresultsasdataratherthanauser-friendlyprintoutontheconsole.

Mochauseswhat'scalledaReportertoreporttestresults.AMochaReporterisamodulethatprintsdatainwhateverformatitsupports.InformationisontheMochawebsite:https://mochajs.org/#reporters.

Youwillfindthecurrentlistofavailablereporterslikeso:

#mocha--reporters

dot-dotmatrix

doc-htmldocumentation

spec-hierarchicalspeclist

json-singlejsonobject

progress-progressbar

list-spec-stylelisting

tap-test-anything-protocol

...

Thenyouuseaspecificreporterlikeso:

#mocha--reportertaptest

1..4

ok1UsersTestListuserlistcreatedusers

ok2UsersTestfinduserfindcreatedusers

ok3UsersTestfinduserfailtofindnonexistentusers

ok4UsersTestdeleteuserdeletenonexistentusers

#tests4

Page 663: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

#pass4

#fail0

TestAnythingProtocol(TAP)isawidelyusedtestresultsformat,increasingthepossibilityoffindinghigherlevelreportingtools.Obviously,thenextstepwouldbetosavetheresultsintoafilesomewhere,aftermountingahostdirectoryintothecontainer.

Page 664: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

FrontendheadlessbrowsertestingwithPuppeteerAbigcostareaintestingismanualuserinterfacetesting.Therefore,awiderangeoftoolshavebeendevelopedtoautomaterunningtestsattheHTTPlevel.SeleniumisapopulartoolimplementedinJava,forexample.IntheNode.jsworld,wehaveafewinterestingchoices.Thechai-httpplugintoChaiwouldletusinteractattheHTTPlevelwiththeNotesapplication,whilestayingwithinthenow-familiarChaienvironment.

However,forthissection,we'llusePuppeteer(https://github.com/GoogleChrome/puppeteer).Thistoolisahigh-levelNode.jsmoduletocontrolaheadlessChromeorChromiumbrowser,usingtheDevToolsprotocol.Thatprotocolallowstoolstoinstrument,inspect,debug,andprofileChromiumorChrome.

Puppeteerismeanttobeageneralpurposetestautomationtool,andhasastrongfeaturesetforthatpurpose.Becauseit'seasytomakewebpagescreenshotswithPuppeteer,itcanalsobeusedinascreenshotservice.

BecausePuppeteeriscontrollingarealwebbrowser,youruserinterfacetestswillbeveryclosetolivebrowsertestingwithouthavingtohireahumantodothework.BecauseitusesaheadlessversionofChrome,novisiblebrowserwindowwillshowonyourscreen,andtestscanberuninthebackground,instead.AdownsidetothisattractivestoryisthatPuppeteeronlyworksagainstChrome.MeaningthatanautomatedtestagainstChromedoesnottestyourapplicationagainstotherbrowsers,suchasOperaorFirefox.

Page 665: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SettingupPuppeteerLet'sfirstsetupthedirectoryandinstallthepackages:

$mkdirtest-compose/notesui

$cdtest-compose/notesui

$npminit

...answerthequestions

[email protected]@[email protected]

Duringinstallation,you'llseethatPuppeteercausesthedownloadofChromiumlikeso:

DownloadingChromiumr497674-92.5Mb[====================]100%0.0s

ThepuppeteermodulewilllaunchthatChromiuminstanceasneeded,managingitasabackgroundprocess,andcommunicatingwithitusingtheDevToolsprotocol.

Inthescriptwe'reabouttowrite,weneedauseraccountthatwecanusetologinandperformsomeactions.Fortunately,wealreadyhaveascripttosetupatestaccount.Inusers/package.json,addthislinetothescriptssection:"setupuser":"PORT=3333nodeusers-add",

We'reabouttowritethistestscript,butlet'sfinishthesetup,thefinalbitofwhichisaddingtheselinestorun.sh:

dockerexec-ituserauth-testnpmrunsetupuser

dockerexec-itnotesapp-testnpmruntest-docker-ui

Whenexecuted,thesetwolinesensurethatthetestuserissetup,anditthenrunstheuserinterfacetests.

Page 666: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ImprovingtestabilityintheNotesUIWhiletheNotesapplicationdisplayswellinthebrowser,howdowewritetestsoftwaretodistinguishonepagefromanother?Thekeyrequirementisfortestscriptstoinspectthepage,determinewhichpageisbeingdisplayed,andreadthedataonthepage.ThatmeanseachHTMLelementmustbeeasilyaddressableusingaCSSselector.

WhiledevelopingtheNotesapplication,weforgottodothat,andtheSoftwareQualityEngineering(SQE)managerhasrequestedourassistance.Atstakeisthetestingbudget,whichwillbestretchedfurtherthemoretheSQEteamcanautomatetheirtests.

Allthat'snecessaryistoaddafewidorclassattributestoHTMLelementstoimprovetestability.Withafewidentifiers,andacommitmenttomaintainthoseidentifiers,theSQEteamcanwriterepeatabletestscriptstovalidatetheapplication.

Innotes/partials/header.hbs,changetheselines:

...

<aid="btnGoHome"class="navbar-brand"href='/'>

...

{{#ifuser}}

...

<aclass="nav-itemnav-linkbtnbtn-darkcol-auto"id="btnLogout"

href="userslogout">...</a>

<aclass="nav-itemnav-linkbtnbtn-darkcol-auto"id="btnAddNote"

href='notesadd'>...</a>

{{else}}

...

<aclass="nav-itemnav-linkbtnbtn-darkcol-auto"id="btnloginlocal"

href="userslogin">..</a>

<aclass="nav-itemnav-linkbtnbtn-darkcol-auto"

Page 667: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

id="btnLoginTwitter"href="usersauth/twitter">...</a>

...

{{/if}}

...

Innotes/views/index.hbs,makethesechanges:

<divid="notesHomePage"class="container-fluid">

<divclass="row">

<divid="notetitles"class="col-12btn-group-vertical"role="group">

{{#eachnotelist}}

<aid="{{key}}"class="btnbtn-lgbtn-blockbtn-outline-dark"

href="notesview?key={{key}}">...</a>

{{/each}}

</div>

</div>

</div>

Innotes/views/login.hbs,makethesechanges:

<divid="notesLoginPage"class="container-fluid">

...

<formid="notesLoginForm"method='POST'action='userslogin'>

...

<buttontype="submit"id="formLoginBtn"class="btnbtn-

default">Submit</button>

</form>

...

</div>

Innotes/views/notedestroy.hbs,makethesechanges:

<formid="formDestroyNote"method='POST'action='notesdestroy/confirm'>

...

<buttonid="btnConfirmDeleteNote"type="submit"value='DELETE'

class="btnbtn-outline-dark">DELETE</button>

...

</form>

Innotes/views/noteedit.hbs,makethesechanges:

<formid="formAddEditNote"method='POST'action='notessave'>

Page 668: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

...

<buttonid='btnSave'type="submit"class="btnbtn-default">Submit</button>

...

</form>

Innotes/views/noteview.hbs,makethesechanges:

<divid="noteView"class="container-fluid">

...

<pid="showKey">Key:{{notekey}}</p>

...

<aid="btnDestroyNote"class="btnbtn-outline-dark"

href="notesdestroy?key={{notekey}}"role="button">...</a>

<aid="btnEditNote"class="btnbtn-outline-dark"

href="notesedit?key={{notekey}}"role="button">...</a>

<buttonid="btnComment"type="button"class="btnbtn-outline-dark"

data-toggle="modal"data-target="#notes-comment-modal">...</button>

...

</div>

Whatwe'vedoneisaddid=attributestoselectedelementsinthetemplates.WecannoweasilywriteCSSselectorstoaddressanyelement.TheengineeringteamcanalsostartusingtheseselectorsinUIcode.

Page 669: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

PuppeteertestscriptforNotesIntest-compose/notesui,createafilenameduitest.jscontainingthefollowing:

constpuppeteer=require('puppeteer');

constassert=require('chai').assert;

constutil=require('util');

const{URL}=require('url');

describe('Notes',function(){

this.timeout(10000);

letbrowser;

letpage;

before(asyncfunction(){

browser=awaitpuppeteer.launch({slomo:500});

page=awaitbrowser.newPage();

awaitpage.goto(process.env.NOTES_HOME_URL);

});

after(asyncfunction(){

awaitpage.close();

awaitbrowser.close();

});

});

ThisisthestartofaMochatestsuite.Inthebeforefunction,wesetupPuppeteerbylaunchingaPuppeteerinstance,startinganewPageobject,andtellingthatPagetogototheNotesapplicationhomepage.ThatURLispassedinusingthenamedenvironmentvariable.

It'susefultofirstthinkaboutscenarioswemightwanttoverifywiththeNotesapplications:

LogintotheNotesapplication

Page 670: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Addanotetotheapplication

Viewanaddednote

Deleteanaddednote

Logout

Andsoon

Here'scodeforanimplementationoftheLoginscenario:

describe('Login',function(){

before(asyncfunction(){...});

it('shouldclickonloginbutton',asyncfunction(){

constbtnLogin=awaitpage.waitForSelector('#btnloginlocal');

awaitbtnLogin.click();

});

it('shouldfillinloginform',asyncfunction(){

constloginForm=awaitpage.waitForSelector('#notesLoginPage

#notesLoginForm');

awaitpage.type('#notesLoginForm#username',"me");

awaitpage.type('#notesLoginForm#password',"w0rd");

awaitpage.click('#formLoginBtn');

});

it('shouldreturntohomepage',asyncfunction(){

consthome=awaitpage.waitForSelector('#notesHomePage');

constbtnLogout=awaitpage.waitForSelector('#btnLogout');

constbtnAddNote=awaitpage.$('#btnAddNote');

assert.exists(btnAddNote);

});

after(asyncfunction(){...});

});

ThistestsequencehandlestheLoginScenario.ItshowsyouafewofthePuppeteerAPImethods.DocumentationofthefullAPIisathttps://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md.ThePageobjectencapsulatestheequivalentofabrowsertabinChrome/Chromium.

ThewaitForSelectorfunctiondoeswhatitsays–itwaitsuntilanHTMLelementmatchingtheCSSselectorappears,anditwillwaitoveroneor

Page 671: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

morepagerefreshes.Thereareseveralvariantsofthisfunctiontoallowwaitingforseveralkindsofthings.ThisfunctionreturnsaPromise,makingitworthourtimetouseasyncfunctionsinourtestcode.ThePromisewillresolvetoanElementHandle,whichisawrapperaroundanHTMLelement,orelsethrowanexception,whichwouldconvenientlymakethetestfail.

Thenamedelement,#btnloginlocal,isinpartials/header.hbs,andwillshowuponlywhenauserisnotloggedin.Hence,wewillhavedeterminedthatthebrowseriscurrentlydisplayingaNotespage,andthatitisnotloggedin.

Theclickmethoddoeswhatitsuggests,andcausesamousebuttonclickonthereferencedHTMLelement.Ifyouwanttoemulateatap,suchasforamobiledevice,thereisatapmethodforthatpurpose.

Thenextstageofthetestsequencepicksupfromthatclick.ThebrowsershouldhavegonetotheLoginpage,andthereforethisCSSselectorshouldbecomevalid:#notesLoginPage#notesLoginForm.WhatwedonextistypetextforourtestuserIDandpasswordintothecorrespondingformelements,andthenclickontheLogInbutton.

Thenextteststagepicksupfromthere,andthebrowsershouldbeonthehomepageasdeterminedbythisCSSselector:#notesHomePage.Ifwewereloggedinsuccessfully,thepageshouldhaveLogOut(#btnLogout)andADDNotebuttons(#btnAddNote).

Inthiscase,we'veusedadifferentfunction,$,tocheckiftheADDNotebuttonexists.Unlikethewaitfunctions,$simplyqueriesthecurrentpagewithoutwaiting.IfthenamedCSSSelectorisnotinthecurrentpage,itsimplyreturnsnullratherthanthrowinganexception.Therefore,todeterminethattheelementexists,weuseassert.existsratherthanrelyingonthethrownexception.

Page 672: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

RunningtheloginscenarioNowthatwehaveonetestscenarioentered,let'sgiveitawhirl.Inonewindow,starttheNotestestinfrastructure:

$cdtest-compose

$docker-composeup--force-rebuild

Theninanotherwindow:

$dockerexec-ituserauthbash

userauth#PORT=3333node./users-add.js

userauth#exit

$cdtest-compare/notesui

$NOTES_HOME_URL=http://localhost:3000mocha--no-timeoutsuitest.js

Notes

Login

√shouldclickonloginbutton

√shouldfillinloginform(72ms)

√shouldreturntohomepage(1493ms)

3passing(3s)

TheNOTES_HOME_URLvariableiswhatthescriptlooksfortodirecttheChromiumbrowsertousetheNotesapplication.Torunthetests,weshoulduseDockerComposetolaunchthetestinfrastructure,andthenensurethetestuserisinstalledintheuserdatabase.

Page 673: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TheAddNotescenarioAddthistouitest.js:

describe('AddNote',function(){

//before(asyncfunction(){...});

it('shouldseeAddNotebutton',asyncfunction(){

constbtnAddNote=awaitpage.waitForSelector('#btnAddNote');

awaitbtnAddNote.click();

});

it('shouldfillinAddNoteform',asyncfunction(){

constformAddEditNote=await

page.waitForSelector('#formAddEditNote');

awaitpage.type('#notekey','key42');

awaitpage.type('#title','Hello,world!');

awaitpage.type('#body','Loremipsumdolor');

awaitpage.click('#btnSave');

});

it('shouldviewnote',asyncfunction(){

awaitpage.waitForSelector('#noteView');

constshownKey=awaitpage.$eval('#showKey',el=>

el.innerText);

assert.exists(shownKey);

assert.isString(shownKey);

assert.include(shownKey,'key42');

constshownTitle=awaitpage.$eval('#notetitle',el=>

el.innerText);

assert.exists(shownTitle);

assert.isString(shownTitle);

assert.include(shownTitle,'Hello,world!');

constshownBody=awaitpage.$eval('#notebody',el=>

el.innerText);

assert.exists(shownBody);

assert.isString(shownBody);

assert.include(shownBody,'Loremipsumdolor');

});

it('shouldgotohomepage',asyncfunction(){

awaitpage.waitForSelector('#btnGoHome');

awaitpage.goto(process.env.NOTES_HOME_URL);

//awaitpage.click('#btnGoHome');

awaitpage.waitForSelector('#notesHomePage');

consttitles=awaitpage.$('#notetitles');

Page 674: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

assert.exists(titles);

constkey42=awaitpage.$('#key42');

assert.exists(key42);

constbtnLogout=awaitpage.$('#btnLogout');

assert.exists(btnLogout);

constbtnAddNote=awaitpage.$('#btnAddNote');

assert.exists(btnAddNote);

});

//after(asyncfunction(){...});

});

Thisisamoreinvolvedscenario,inwhichwe:

ClickontheADDNotebutton

Waitforthenoteeditscreentoshowup

FillinthetextforthenoteandclicktheSavebutton

Validatethenoteviewpagetoensurethat'scorrect

Validatethehomepagetoensurethat'scorrect.

MostofthisisusingthesamePuppeteerfunctionsasbefore,butwithacoupleofadditions.

The$evalfunctionlooksfortheelementmatchingtheCSSselector,andinvokesthecallbackfunctiononthatelement.Ifnoelementisfoundanerroristhrowninstead.Asusedhere,weareretrievingthetextfromcertainelementsonthescreen,andvalidatingthatitmatcheswhatthetestenteredasthenote.That'sanend-to-endtestofaddingandretrievingnotes.

Thenextdifferenceisusinggotoinsteadofclickingon#btnGoHome.

Asyouaddtestscenariostothetestscript,you'llfinditeasyforPuppeteertohaveaspurioustimeout,orfortheloginprocesstomysteriouslynotwork,orotherspuriouserrors.

Page 675: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Ratherthangoovertheremainingscenarios,we'llspendthenextsectiondiscussinghowtomitigatesuchissues.Butfirstweneedtoprovethescenariodoesworkevenifwehavetorunthetest10timestogetthisresult:

$NOTES_HOME_URL=http://localhost:3000./node_modules/.bin/mocha--no-

timeoutsuitest3.js

Notes

Login

√shouldclickonloginbutton(50ms)

√shouldfillinloginform(160ms)

√shouldreturntohomepage(281ms)

AddNote

√shouldseeAddNotebutton

√shouldfillinAddNoteform(1843ms)

√shouldviewnote

√shouldgotohomepage(871ms)

7passing(5s)

Page 676: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Mitigating/preventingspurioustesterrorsinPuppeteerscriptsThegoalistofullyautomatethetestrun,inordertoavoidhavingtohireahumantobabysitthetestexecutionandspendtimererunningtestsbecauseofspuriouserrors.Todoso,thetestsneedtoberepeatablewithoutanyspuriouserrors.Puppeteerisacomplexsystem–thereisaNode.jsmodulecommunicatingwithaChromiuminstancerunningHeadlessinthebackground–anditseemseasyfortimingissuestocauseaspuriouserror.

Page 677: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ConfiguringtimeoutsBothMochaandPuppeteerallowyoutosettimeoutvalues,andalongtimeoutvaluecanavoidtriggeringanerror,ifsomeactionsimplyrequiresalongtimetorun.Atthetopofthetestsuite,weusedthisMochafunction:

this.timeout(10000);

Thatgives10secondsforeverytestcase.Ifyouwanttousealongertimeout,increasethatnumber.

Thepuppeteer.launchfunctioncantakeatimeoutvalueinitsoptionsobject.Bydefault,Puppeteerusesa30-secondtimeoutonmostoperations,andtheyalltakeanoptionsobjectwithasettingtochangethattimeoutperiod.Inthiscase,we'veaddedtheslowMooptiontoslowdownoperationsonthebrowser.

Page 678: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TracingeventsonthePageandthePuppeteerinstanceAnotherusefultacticistogenerateatraceofwhathappenedsoyoucanpuzzleaway.Insertingconsole.logstatementsistediousandmakesyourcodelookalittleugly.Puppeteeroffersacoupleofmethodstotracetheactionsandtodynamicallyturnofftracing.

Inuitest.js,addthiscode:functionframeEvent(evtname,frame){console.log(`${evtname}${frame.url()}${frame.title()}`);}

functionignoreURL(url){if(url.match(/\/assets\//)===null&&url.match(/\/socket.io\//)===null&&url.match(/fonts.gstatic.com/)===null&&url.match(/fonts.googleapis.com/)===null){returnfalse;}else{returntrue;}}...before(asyncfunction(){browser=awaitpuppeteer.launch({slomo:500});page=awaitbrowser.newPage();page.on('console',msg=>{console.log(`${msg.type()}${msg.text()}${msg.args().join('')}`);});page.on('error',err=>{console.error(`pageERROR${err.stack}`);

Page 679: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

});page.on('pageerror',err=>{console.error(`pagePAGEERROR${err.stack}`);});page.on('request',req=>{if(ignoreURL(req.url()))return;console.log(`pagerequest${req.method()}${req.url()}`);});page.on('response',async(res)=>{if(ignoreURL(res.url()))return;console.log(`pageresponse${res.status()}${res.url()}`);});page.on('frameattached',async(frame)=>frameEvent('frameattached',awaitframe));page.on('framedetached',async(frame)=>frameEvent('framedetached',awaitframe));page.on('framenavigated',async(frame)=>frameEvent('framenavigated',awaitframe));awaitpage.goto(process.env.NOTES_HOME_URL);});...

Thatis,thePageobjectoffersseveraleventlistenersinwhichwecanoutputdetailsaboutvariousevents,includingHTTPrequestsandresponses.WecanevenprintouttheHTMLtextoftheresponse.TheignoreURLfunctionletsussuppressafewselectURLssowe'renotinundatedwithunimportantrequestsandresponses.

YoucantracePuppeteeritselfusingitsDEBUGenvironmentvariable.SeetheREADMEformoreinformation:https://github.com/GoogleChrome/puppeteer.

Page 680: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

InsertingpausesItcanbeusefultoinsertalongpauseatcertainpointstogivethebrowsertimetodosomething.Trythisfunction:functionwaitFor(timeToWait){returnnewPromise(resolve=>{setTimeout(()=>{resolve(true);},timeToWait);});};

ThisishowweimplementtheequivalentofasleepfunctionusingPromises.UsingsetTimeOutthisway,alongwithatimeoutvalue,simplycausesadelayforthegivennumberofmilliseconds.

Tousethisfunction,simplyinsertthisintothetestscenarios:

awaitwaitFor(3000);

Avariantonthisistowaitforthingstofullyrenderinthebrowser.Forexample,youmighthaveseenapausebeforetheHomeiconintheupper-leftcornerfullyrenders.Thatpausecancausespuriouserrors,andthisfunctioncanwaituntilthatbuttonfullyrendersitself:asyncfunctionwaitForBtnGoHome(){returnpage.waitForSelector('#btnGoHome');}

Touseit:

awaitwaitForBtnGoHome();

Ifyoudon'twanttomaintainthisextrafunction,it'seasyenoughtoaddthewaitForSelectorcallintoyourtestcasesinstead.

Page 681: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

AvoidingWebSocketsconflictsAnerror,Cannotfindcontextwithspecifiedidundefined,canbethrownbyPuppeteer.AccordingtoanissueinthePuppeteerissuequeue,thiscanarisefromunplannedinteractionsbetweenPuppeteerandWebSockets:https://github.com/GoogleChrome/puppeteer/issues/1325ThisissueinturnaffectstheSocket.IOsupportintheNotesapplication,andthereforeitmaybeusefultodisableSocket.IOsupportduringtestruns.

It'sfairlysimpletoallowdisablingofSocket.IO.Inapp.mjs,addthisexportedfunction:

exportfunctionenableSocketio(){

varret=true;

constenv=process.env.NOTES_DISABLE_SOCKETIO;

if(!env||env!=='true'){

ret=true;

}

returnret;

}

Thislooksforanenvironmentvariabletocausethefunctiontoreturntrueorfalse.

Inroutes/index.mjsandroutes/notes.mjs,addthisline:

import{enableSocketio,sessionCookieName}from'../app';

Wedothistoimporttheprecedingfunction.ItalsodemonstratessomeoftheflexibilitywegetfromES6Modules,becausewecanimportjusttherequiredfunctions.

Inroutes/index.mjsandroutes/notes.mjs,foreveryrouterfunctionthatcallsres.rendertosendresults,usetheenableSocketiofunctionasso:

Page 682: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

res.render('view-name',{

...

enableSocketio:enableSocketio()

});

Hence,we'veimportedthefunctionandforeveryviewwepassenableSocketioasdatatotheviewtemplate.

Inviews/index.hbsandviews/noteview.hbs,wehaveasectionofJavaScriptcodetoimplementSocketIO-basedsemi-real-timefeatures.Surroundeachsuchsectionlikeso:

{{#ifenableSocketio}}

...JavaScriptcodeforSocketIOsupport

{{/if}}

Byeliminatingtheclient-sideSocketIOcode,weensuretheuserinterfacedoesnotopenaconnectiontotheSocketIOservice.ThepointofthisexercisewastoavoidusingWebSocketstoavoidissueswithPuppeteer.

Similarly,inviews/noteview.hbssupportdisablingtheCommentbuttonlikeso:

{{#ifenableSocketio}}

<buttonid="btnComment"type="button"class="btnbtn-outline-dark"

data-toggle="modal"data-target="#notes-comment-

modal">Comment</button>

{{/if}}

Thefinalstepwouldbetosettheenvironmentvariable,NOTES_DISABLE_SOCKETIO,intheDockerComposefile.

Page 683: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

TakingscreenshotsOneofPuppeteer'scorefeaturesistotakescreenshots,eitherasPNGorPDFfiles.Inourtestscripts,wecantakescreenshotstotrackwhatwasonthescreenatanygiventimeduringthetest.Forexample,iftheLoginscenariospuriouslyfailstologin,wecanseethatinthescreenshots:awaitpage.screenshot({type:'png',path:`./screen/login-01-start.png`});

Simplyaddcodesnippetslikethisthroughoutyourtestscript.Thefilenameshownherefollowsaconventionwherethefirstsegmentnamesthetestscenario,thenumberisasequencenumberwithinthetestscenario,andthelastdescribesthestepwithinthetestscenario.

Takingscreenshotsalsoprovidesanotherstageofvalidation.Youmaywanttodovisualvalidationofyourapplicationaswell.ThepixelmatchmodulecancomparetwoPNGfiles,andthereforeasetofso-calledGoldenImagescanbemaintainedforcomparisonduringtestruns.

ForanexampleofusingPuppeteerthisway,see:https://meowni.ca/posts/2017-puppeteer-tests/.

Page 684: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SummaryWe'vecoveredalotofterritoryinthischapter,lookingatthreedistinctareasoftesting:unittesting,RESTAPItesting,andUIfunctionaltests.Ensuringthatanapplicationiswelltestedisanimportantstepontheroadtosoftwaresuccess.Ateamthatdoesnotfollowgoodtestingpracticesisoftenboggeddownwithfixingregressionafterregression.

We'vetalkedaboutthepotentialsimplicityofsimplyusingtheassertmodulefortesting.Whilethetestframeworks,suchasMocha,providegreatfeatures,wecangoalongwaywithasimplescript.

Thereisaplacefortestframeworks,suchasMocha,ifonlytoregularizeourtestcases,andtoproducetestresultsreports.WeusedMochaandChaiforthis,andthesetoolswerequitesuccessful.Weevenfoundacoupleofbugswithasmalltestsuite.

Whenstartingdowntheunittestingroad,onedesignconsiderationismockingoutdependencies.Butit'snotalwaysagooduseofourtimetoreplaceeverydependencywithamockversion.

Toeasetheadministrativeburdenofrunningtests,weusedDockertoautomatesettingupandtearingdownthetestinfrastructure.JustasDockerwasusefulinautomatingdeploymentoftheNotesapplication,it'salsousefulinautomatingtestinfrastructuredeployment.

Finally,wewereabletotesttheNoteswebuserinterfaceinarealwebbrowser.Wecan'ttrustthatunittestingwillfindeverybug;somebugswillonlyshowupinthewebbrowser.Evenso,we'veonlytouchedthebeginningofwhatcouldbetestedinNotes.

Inthisbook,we'vecoveredthegamutofNode.jsdevelopment,givingyouastrongfoundationfromwhichtostartdevelopingNode.jsapplications.

Page 685: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Inthenextchapter,we'llexploreanothercriticalarea–security.We'llstartbyusingHTTPStoencryptandauthenticateuseraccesstoNotes.And,we'lluseseveralNode.jspackagestolimitthechanceofsecurityintrusions.

Page 686: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SecurityWe'recomingtotheendofthisjourneyoflearningNode.js.Butthereisoneimportanttopiclefttodiscuss:security.

Cybersecurityofficialsaroundtheworldhavebeenclamoringforgreatersecurityontheinternet.Insomecases,vastbotnetshavebeenbuilt,thankstoweaksecurityimplementation,whichareweaponizedtobludgeonwebsitesorcommitothermayhem.Inothercases,rampantidentitytheftfromsecurityintrusionsareafinancialthreattousall.Almosteveryday,thenewsincludesmorerevelationsofcybersecurityproblems.

In2016,theUS-CERTissuedseveralwarningsofvulnerabilitiesinInternetofThings(IoT)devices,suchassecuritycamerasorWi-Firouters.Byexploitingvulnerabilitiesinthedevices,attackerswereabletoinjectattacksoftwareintothesedevices.TheresultwasaslavedbotnetofhundredsofthousandsofIoTdevices,whichweredeployedtosendmassiveDistributedDenialofService(DDOS)attacksagainstspecificwebsites.

Companieslargeandsmallhavesufferedsecuritybreaches.Theattackersgenerallymakeoffwithuseridentityinformation,whichiswhy,inChapter10,DeployingNode.jsApplications,wewerecarefultosegmenttheuserdatabaseintoanisolatedcontainer.Themorelayersofsecurityyouputaroundcriticalsystems,thelesslikelyattackerscangetin.

Generallyspeaking,theinternethastransitionedfrombeingtheexperimentalplaygroundofcomputerresearchersinthe1980stobecomingacentralfacetofglobalsociety.Allkindsofcriticalactivitiesarebeingconductedovertheinternet.

Forexample,theelectricalutilitiesandelectricgridoperatorsareresearchingthetransitionofelectricitygridcontrolfromprivatenetworks

Page 687: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

totheinternet.AninfluencingcauseistheincreaseinInternetofThings-baseddevicesfordistributedenergyresources,suchassmartthermostats,smartlighting,solararrays,andenergystoragedevices.Theindustryisrevisitingthecommunicationsprotocolsusedforcontrollingthesesystems,andmovingtowardsinternettechnologies.ThisincludestheIEEE2030.5standardusingHTTPSwithNSA-gradeencryption,securedwithspecificallyconstructeddigitalcertificatesidentifyingdevicesandservices,alongwithREST-basedcommunicationprotocols.

Becauseofthecriticalroletheinternetnowplaysinallourlives,itisimportantforallsoftwaredeveloperstoaddresssecurityissuesintheirproducts.

Securityshouldn'tbeanafterthought,justastestingshouldnotbeanafterthought.Bothareincrediblyimportant,ifonlytokeepyourcompanyfromgettinginthenewsforthewrongreasons.

EventhoughtheNotesapplicationisasimpletoyapplication,we'vebeenusingittoexploreproductiondeploymentissues.Let'snowturntousingNotestoexploretheimplementationofgoodsecuritypractices.Wewillcoverthefollowingtopics:

ImplementingHTTPS/SSLinanExpressapplication

AutomationofSSLcertificaterenewalwithLet'sEncrypt

UsingtheHelmetlibrarytoimplementheadersforContentSecurityPolicy,DNSPrefetchControl,FrameOptions,StrictTransportSecurity,andmitigatingXSSattacks

Preventingcross-siterequestforgeryattacksagainstforms

SQLInjectionattacks

Pre-deploymentscanningforpackageswithknownvulnerabilities

Page 688: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Ifyouhaven'tyetdoneso,duplicatetheChapter11,UnitTestingandFunctionalTestingsourcetree,whichyoumayhavecalledchap11,tomakeaChapter12,Securitysourcetree,whichyoucancallchap12.

Expresshasanexcellentsecurityresourcepageathttps://expressjs.com/en/advanced/best-practice-security.html.

Page 689: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

HTTPS/TLS/SSLusingLet'sEncryptSecuringyourwebsiteusingHTTPSisbecomingincreasinglyimportant.ThebrowsermakershavestartedissuingwarningsforHTTP-onlywebsites,forexample,andthesearchenginesaredowngradingsuchsitesinsearchrankings.Privacyconcernsdictatetheencryptionofalltrafficsentovertheinternet.Phishingattacksluringvictimstofakewebsitesfilledwithmalwaredictatewehaveamechanismtorobustlyidentifywebsiteownership.

HTTPSissimplyHTTPrunthroughTLS/SSL,whichisaninternetprotocolforencryptedconnections.TheencryptionkeysarestoredinatrustedPublicKeyInfrastructure(PKI)withencryptedcertificatesforvalidatingwebsites.Withacorrectlyissuedcertificate,HTTPSvalidatesthedomainsothatourusershavesomeassurancethey'vevisitedavalidwebsite,andthattheirdatatransfersareencryptedtoprevent(casual)eavesdropping.Thatgreenbuttoninthebrowserlocationbarismeanttoreassureourvisitorsthatthey'resafe.

Foryears,acquiringtherequiredSSLcertificateswasamanualprocessrequiringsignificantfeepaymentstoaCertificateAuthoritycompany.CertificateAuthorities(CA)arepartofthewholePublicKeyInfrastructure(PKI)behindtheSSLcertificatesusedbytheHTTPSprotocol.TheInternetPKIusesahierarchyofCAs,withhigherlevelCAscertifyingtheend-userCAs.HoweverdesirableitisforeverywebsitetobeprotectedwithHTTPS,themanualprocess,andthecostofacquiringSSLcertificates,meantfolkswerediscouragedfromdeployingHTTPSwebsites.Theinternetwasthereforemuchlesssecurethanitcouldhavebeen.

ThatchangedwiththeadventofLet'sEncrypt,anon-profitorganization

Page 690: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

offeringfreeSSLcertificates.Mostimportantly,theprocessiscompletelyautomatedandeasytosetupanduse.ThereisnownoreasontonothaveHTTPSsupportout-of-the-box.

Whatwe'reabouttodoistoimplementHTTPSusingtheLet'sEncrypttoolsintheDockerinfrastructurefortheNotesapplication.Thisrequiresafewsteps,noneofwhicharedifficult:

DeployingNotestoacloudserveraswedidinChapter10,DeployingNode.jsApplications,withtheadditionofassociatingarealdomainnamewiththedeployment

Addinganewcontainercontainingcertbot,thecommand-linetoolforLet'sEncrypt,anduseittomanageregisteringandrenewingSSLcertificates

ConfiguringtheDockerinfrastructuretocross-mountdirectoriesfromthecertbotcontainertothenotescontainer,sothatNoteshastheSSLcertificates

ImplementtheHTTPServerobjectinNotes

Let'sgetstarted.

Page 691: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

AssociatingadomainnamewithDocker-basedcloudhostingLet'sstartbydeployingourNotesapplicationtocloudhostingaswedidinChapter10,DeployingNode.jsApplications,butwithafewchanges.

Onechangeistousearealdomainnamethistime.Thisrequiresgoingtoadomainnameregistrarsuchaspairdomains.comtoregisteradomain.Someweb-hostingprovidersalsoofferdomainnameregistration,howeverit'sgenerallybetterifyourdomainnameisregisteredseparatelyfromthehostingprovider.Goaheadandregisteradomain.fooblebartz.comseemstobeavailable,forexample.

ThenextstepistoassociatethedomainnamewithavirtualservertowhichwecandeployDockercontainers.Again,we'llturntoDigitalOceanasanexampleserviceforhostingtheapplication,andshowhowtoconfigureadomainname.

Theexactmethodtoassociateadomainnamewillvarydependingonthehostingprovider.Typically,thehostingproviderwillaskyoutoassigntheNSrecordsforthedomaintolisttheDNSserversoperatedbythehostingprovider.SuchhostingproviderswillgiveyoualistofNSserverhostnames.OncetheNSrecordsareassignedtothehostingprovider,youcanusethehostingprovider'sdashboardtoconfigureyourdomain.

IntheDigitalOceandashboard,clickonNetworkingandthenDomains.Inthatpanel,youcanenterthedomainnameyou'veregistered:

Page 692: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ClickontheAddDomainbuttonandthedashboardwilltransitionyoutoanewscreeninstructingyoutoconfigurethedomainnamewiththreeNSrecords.YoumustthencopythoseNSrecordstothedomainregistrarwebsite,whereyou'llenterthemlikeso:

Now,wemustcreateaDockerhostonDigitalOcean(oryourchosencloud-hostingprovider)likeso:

$docker-machinecreate--driverdigitalocean\

--digitalocean-size2gb\

--digitalocean-access-tokenDIGITALOCEAN-API-TOKEN\

notes-https

...

$eval$(docker-machineenvnotes-https)

Thisgivesusavirtualserver,whichwecaninspectintheDigitalOcean

Page 693: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

dashboard.IfyouthennavigatetotheNetworking/Domainspanel,thedomaincanbeassociatedwiththeserver:

Shortly,yourdomainnamewillbeproperlyassociatedwiththeserver:

$pingevsoul.com

PINGevsoul.com(159.65.179.28)56(84)bytesofdata.

64bytesfrom159.65.179.28(159.65.179.28):icmp_seq=1ttl=49time=83.0ms

64bytesfrom159.65.179.28(159.65.179.28):icmp_seq=2ttl=49time=85.1ms

^C

---evsoul.compingstatistics---

2packetstransmitted,2received,0%packetloss,time3001ms

rttmin/avg/max/mdev=83.050/83.655/85.184/0.930ms

$dig-tanyevsoul.com

;<<>>DiG9.10.3-P4-Ubuntu<<>>-tanyevsoul.com

...

;;ANSWERSECTION:

evsoul.com.1800INSOAns1.digitalocean.com.hostmaster.evsoul.com.

15190921201080036006048001800

evsoul.com.3502INA159.65.179.28

evsoul.com.1800INNSns1.digitalocean.com.

evsoul.com.1800INNSns2.digitalocean.com.

evsoul.com.1800INNSns3.digitalocean.com.

...

Page 694: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ButbecausenothingontheserverwillanswertoanyHTTPport,itcannotbevisitedbyawebbrowser.Withthecommandshellonourlaptopstillassociatedwiththevirtualserver,let'sdeployNotestotheserversowecanhavevisitors:

$docker-composebuild

...

$docker-composeup--force-recreate-d

Oncethat'sdone,youcanvisittheNotesapplicationviayourdomainname.

TherearethreethingstodotoenableloggingintotheNotesservice.Oneistoruntheusers-addscriptintheuserauthcontainer,asyouwillhavedonesomanytimesalready.Theotheristomakethischangeinnotes/Dockerfile:

ENVTWITTER_CALLBACK_HOST=http://evsoul.com

Substituteyourchosendomainnamehere.ThischangeenablesusingTwittertologintotheNotesapplication.

Thelastchangeisincompose/docker-compose.yml:

notes:

build:../notes

container_name:notes

depends_on:

-db-notes

networks:

-frontnet

ports:

-"80:3000"

restart:always

environment:

-NODE_ENV="production"

Inotherwords,it'stimeforNotestobeonport80likeanyHTTPservice.

Atthispoint,youwillbeabletoviewtheNotesapplicationusing

Page 695: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

http://DOMAIN,andtologinusingeitherthelocalusername,orusingTwitter.Otherthanaddingthedomainname,thisisexactlywhatwehadinChapter10,DeployingNode.jsApplications.

What'snextisto:

UseLet'sEncrypttosetupSSLcertificates

ModifyNotestousethosecertificates

RedirectHTTPtraffictotheNotesHTTPSport

EnsurewecandeploytoHTTPorHTTPSasneeded,sincewedon'tneedHTTPonthedevelopers'laptops

Page 696: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ADockercontainertomanageLet'sEncryptSSLcertificatesYouacquireanSSLcertificatefromLet'sEncryptusinganACMEclient.ACMEisaprotocolinventedconcurrentlywiththeLet'sEncryptserviceforfetchingSSLcertificatesfromaprovider.TheprimaryACMEclientisCertbot,acommand-linetoolthathelpsyouregisteradomainwithLet'sEncrypt,andwhichautomatesrenewalofLet'sEncryptSSLcertificates.ForCertbotdocumentation,seehttps://certbot.eff.org/.

Thecontainerwe'reabouttoimplementissetuptomakeiteasytoregisteradomainwithLet'sEncrypt,andthentoautomatecertificaterenewal.It'saverysimplecontainer,consistingofacrondaemon,acrontabentry,andthecertbottool.TheonlyCPUconsumptionoccursaboutonceadaywhenthecronjobrunstoattemptcertificaterenewal.

Createadirectory,certbot,andwithinthatdirectoryaDockerfile:

FROMdebian:jessie

#Installcron,certbot,bash,plusanyotherdependencies

RUNapt-getupdate&&apt-getinstall-ycronbashwget

RUNmkdir-pwebrootsevsoul.com/.well-known&&mkdir-p/scripts

WORKDIR/scripts

RUNwgethttps://dl.eff.org/certbot-auto

RUNchmoda+x./certbot-auto

#Runcertbot-autosothatitinstallsitself

RUNscriptscertbot-auto-ncertificates

#webrootsDOMAIN.TLD/.well-known/...filesgohere

VOLUME/webroots

VOLUMEetcletsencrypt

#ThisinstallsaCrontabentrywhich

#runs"certbotrenew"onthe2ndand7thdayofeachweekat03:22AM

Page 697: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

#

#cron(8)saystheDebiancrondaemonreadsthefilesinetccron.d,

#mergingintothedatafrometccrontab,touseasthesystem-widecronjobs

#

#RUNecho"22032,7rootscriptscertbot-autorenew">etccron.d/certbot

CMD["cron","-f"]

Thissetsuptwodirectorystructures,/webrootsandetcletsencrypt,whichareexposedfromthecontainerusingtheVOLUMEcommand.ThesedirectoriescontainadministrativefilesusedbycertbotintheprocessofregisteringorrenewingSSLcertificateswiththeLet'sEncryptservice.

TheDockerfilealsoinstallscertbot-auto,whichisreallycertbotbutwithadifferentname.Runningcertbot-auto-ncertificatesisrequiredsothatcertbot-autocaninstallitsdependenciesinthecontainer.Thecertificatescommandliststhecertificatesexistingonthelocalmachine,buttherearenone,andinsteadit'sexecutedforthesideeffectofinstallingthosedependencies.

AnotherfeatureofthisDockerfileistoautomaterenewalofSSLcertificates.Thecertbot-autorenewcommandchecksallcertificatesstoredonthismachinetodetermineifanyrequirerenewal.Ifanydo,arequestisautomaticallymadewithLet'sEncrypttorenewthecertificates.

Asconfigured,thisrenewalattemptwillrunat03:22AMonthe2ndand7thdayoftheweek.

ThelasttaskwehavetohandleisregisteringwithLet'sEncryptforSSLcertificates.Inthecertbotdirectorycreateashellscript,register,orforWindowscallitregister.ps1,containing:

#!/bin/sh

scriptscertbot-autocertonly--webroot-wwebroots$1-d$1

ThisishowweregisteradomainwithLet'sEncrypt.Thescripttakesoneargument,thedomainnametoberegistered.

Page 698: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

The--webrootoptionsayswewanttousethewebrootauthenticationalgorithm.WhatthismeansisLet'sEncryptverifiesthatyouownthedomaininquestionbyrequestingaspecificURLonthedomain.Fordocumentation,seehttps://certbot.eff.org/docs/using.html#webroot.

AnexamplevalidationrequestbytheLet'sEncryptservicemightbehttp://example.com/.well-known/acme-challenge/HGr8U1IeTW4kY_Z6UIyaakzOkyQgPr_7ArlLgtZE8S

X.

Inthecertbotcontainer,we'vesaidthis.well-knowndirectorywillexistinthewebrootsDOMAIN-NAME/.well-knowndirectory.Thepurposeofthe-woptionissocertbot-autoknowsthisdirectorylocation,andthe-doptiontellsitthedomainnametoregister.

Whenwerunthecertbot-autocertonlycommand,Let'sEncrypthandsbackachallengefilewhichisinstalledinthedirectorytreespecifiedinthe-wargument.ItmeansthatwhenLet'sEncryptretrievestheURLshownpreviously,itwillfetchthechallengefile,andthereforevalidatethatyoudoindeedcontrolthegivendomainname.

Asitstands,thatdirectoryisnotvisibletoanythingthatwouldsatisfythevalidationrequest.

Forthistoworkasintended,thewebrootsDOMAIN-NAME/.well-knowndirectoryhastobevisibletotheNotesapplication,suchthatNotescansatisfytheURLrequestLet'sEncryptwillmake.Let'sseehowtodothis.

Page 699: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Cross-containermountingofLet'sEncryptdirectoriestothenotescontainerThepurposeofthecertbotcontaineristomanageLet'sEncryptSSLcertificates.ThosecertificatesaretobeusedbytheNotesapplicationtoconfigureanHTTPSserver.It'srequiredthatthecertificates,andthechallengefiles,bevisibleinsidethenotescontainer.

Insteadofcreatingaseparatecontainer,wecouldhaveinsteadintegratedcertbot-autointothenotescontainer.Butthatwouldhavepreventedscalingthenumberofnotescontainers.Wecan'thaveeachnotesinstancerunningacertbot-autoscripttogeneratecertificates.Instead,thecertificatemanagementprocessesmustinsteadbecentralized.Hence,wedevelopedthecertbotcontainer.

What'srequiredisforthewebrootsDOMAIN-NAME/.well-knowndirectoryinthecertbotcontainertobevisiblesomewhereinthenotescontainer.

Tosetthisup,wehavetwochangesrequiredincompose/docker-compose.yml.First,weaddthisstanzaforthecertbotcontainer:

certbot:

build:../certbot

container_name:certbot

networks:

-frontnet

restart:always

volumes:

-certbot-webroot-evsoul:webrootsevsoul.com/.well-known

-certbot-letsencrypt:etcletsencrypt

ThisbuildsacontainerimagefromtheDockerfiledescribedinthe

Page 700: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

previoussection.Asfarasitgoes,thisisprettystraightforwardexceptfortheentriesinthevolumessection.Thoseentriesassociatethedirectoriesshownherewithnamedvolumesweneedtodefineelsewhereindocker-compose.yml.

Whatwe'redoingisattachingthesametwovolumestoboththecertbotandnotescontainers.Inthisexample,we'remountingthosevolumestospecificdirectoriesincertbot.

Thetwonamedvolumesaredeclaredlikeso:

volumes:

...

certbot-webroot-evsoul:

certbot-letsencrypt:

Thenwemustmakeasimilarchangetothenotescontainer:

notes:

build:../notes

container_name:notes

depends_on:

-db-notes

networks:

-frontnet

ports:

-"80:3000"

-"443:3443"

restart:always

volumes:

-certbot-webroot-evsoul:notesapppublic/.well-known

-certbot-letsencrypt:etcletsencrypt

We'vemadetwochanges,thefirstofwhichistoaddaTCPportexportfortheHTTPSport(port443).Andthesecondistomountthetwonamedvolumestoappropriatelocationsinthenotescontainer.Thesearethesamenamedvolumesdeclaredearlier.We'resimplymountingthevolumesinplacesthatmakesenseforthenotescontainer.

TheSSLcertificatescouldexistatanylocationinthenotescontainer,but

Page 701: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

etcletsencryptisasgoodalocationasany.What'snecessaryisthattheNotescodebeabletoreadthecertificates.

Puttingthe.well-knowndirectoryundernotesapppublicmeansthatNoteswillautomaticallyserveanyfileinthatdirectorytoanyservicemakingarequest.That'sbecauseofthestaticmiddlewarealreadyconfiguredinapp.mjs.

Whatthissetsupisapairofdirectoriesthatarevisibleintwocontainers.Eithercontainercanwriteafileineitherdirectory,andthefilewillautomaticallyshowupintheothercontainer.

Whenweruntheregisterscriptinthecertbotcontainer,itwillwritethechallengefileprovidedbyLet'sEncryptintothewebrootsevsoul.com/.well-knowndirectorytree.Thatsamedirectoryisvisibleinthenotescontainerasnotesapppublic/.well-known,andthereforeNoteswillautomaticallyservethechallengefilejustasitservestheCSS,JavaScript,andimagefilesinthatdirectorytree.

Theprocesslookslikethis:

$dockerexec-itcertbotbash

root@05b095690414:/scripts#sh./registerevsoul.com

Savingdebuglogtovarlog/letsencrypt/letsencrypt.log

Pluginsselected:Authenticatorwebroot,InstallerNone

Enteremailaddress(usedforurgentrenewalandsecuritynotices)(Enter'c'

to

cancel):ENTERYOUREMAILADDRESSHERE

---------------------------------------------------------------

PleasereadtheTermsofServiceat

https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf.Youmust

agreeinordertoregisterwiththeACMEserverat

https://acme-v01.api.letsencrypt.org/directory

---------------------------------------------------------------

(A)gree/(C)ancel:a

---------------------------------------------------------------

WouldyoubewillingtoshareyouremailaddresswiththeElectronicFrontier

Foundation,afoundingpartneroftheLet'sEncryptprojectandthenon-

profit

organizationthatdevelopsCertbot?We'dliketosendyouemailaboutEFFand

ourworktoencrypttheweb,protectitsusersanddefenddigitalrights.

Page 702: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

--------------------------------------------------------------

(Y)es/(N)o:n

Obtaininganewcertificate

Performingthefollowingchallenges:

http-01challengeforevsoul.com

Usingthewebrootpathwebrootsevsoul.comforallunmatcheddomains.

Waitingforverification...

Cleaningupchallenges

IMPORTANTNOTES:

-Congratulations!Yourcertificateandchainhavebeensavedat:

etcletsencrypt/live/evsoul.com/fullchain.pem

Yourkeyfilehasbeensavedat:

etcletsencrypt/live/evsoul.com/privkey.pem

Yourcertwillexpireon2018-05-22.Toobtainanewortweaked

versionofthiscertificateinthefuture,simplyruncertbot-auto

again.Tonon-interactivelyrenewallofyourcertificates,run

"certbot-autorenew"

-YouraccountcredentialshavebeensavedinyourCertbot

configurationdirectoryatetcletsencrypt.Youshouldmakea

securebackupofthisfoldernow.Thisconfigurationdirectorywill

alsocontaincertificatesandprivatekeysobtainedbyCertbotso

makingregularbackupsofthisfolderisideal.

-IfyoulikeCertbot,pleaseconsidersupportingourworkby:

DonatingtoISRGLet'sEncrypt:https:/letsencrypt.org/donate

DonatingtoEFF:https://eff.org/donate-le

root@05b095690414:/scripts#

Afterrunningthecommand,it'sagoodideatoinspecttheetcletsencryptdirectorystructuretoseewhat'sthere.Thecontentsofthesedirectoriesmustbetreatedwithcare,sinceitincludestheprivatekeyscertifyingyourdomain.Thewholepointofaprivatekeyencryptionsystemisthattheprivatekeyisstrictlycontrolled,whilethepublickeyisgiventoanyone.

Thefilesendingwiththe.pemextensionarePEM-encodedcertificates.PrivacyEnhancedMail(PEM),whichwasanearlyattempttodevelopasecureencryptedemailsystemfortheinternet.Whilethatprojectfailed,thePEMcontainerformatlivesonandiswidelyusedforSSLcertificates.

Page 703: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

AddingHTTPSsupporttoNotesNowthatwehaveaprocesstoregisterwithLet'sEncrypt,andrenewtheSSLcertificateswereceive,let'slookataddingHTTPSsupporttotheNotesapplication.ThetaskisfairlysimplesincetheNode.jsplatformprovidesanHTTPSserveralongsidetheHTTPserverobjectwe'veusedallalong.

We'llrequirethesechangestonotes/app.mjs:

importhttpfrom'http';

importhttpsfrom'https';

...

constUSEHTTPS=process.env.NOTES_USE_HTTPS

&&(typeofprocess.env.NOTES_USE_HTTPS==='string')

&&(process.env.NOTES_USE_HTTPS==='true');

constCERTSDIR=process.env.NOTES_CERTS_DIR;

constoptions=USEHTTPS?{

key:fs.readFileSync(`${CERTSDIR}/privkey1.pem`),

cert:fs.readFileSync(`${CERTSDIR}/fullchain1.pem`),

ca:fs.readFileSync(`${CERTSDIR}/chain1.pem`)

}:{};

constserver=http.createServer(app);

constserverSSL=USEHTTPS?https.createServer(options,app):undefined;

importsocketiofrom'socket.io';

constio=socketio(USEHTTPS?serverSSL:server,options);

...

varport=normalizePort(process.env.PORT||'3000');

app.set('port',port);

...

server.listen(port);

server.on('error',onError);

server.on('listening',onListening);

if(USEHTTPS){

serverSSL.listen(3443);

serverSSL.on('error',onError);

Page 704: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

serverSSL.on('listening',onListening);

}

Inotherwords,we'veimportedthehttpsmodulealongsidethehttpmodule,thenwereadintheSSLcertificatesrequiredtoinitializeHTTPSsupport,thencreatedanHTTPSserverobjectusingthosecertificates,andfinallyconfiguredittolistenonport3443.TheHTTPSsupportdependsonthevalueoftheNOTES_USE_HTTPSenvironmentvariable.Ifthatvariableexistsandisequaltotrue,thentheUSEHTTPSvariableissettotrue,andtheHTTPSsupportisturnedon.

Thisleadsustoagainmodifycompose/docker-compose.ymltomatch:

notes:

build:../notes

container_name:notes

depends_on:

-db-notes

networks:

-frontnet

ports:

-"80:3000"

-"443:3443"

restart:always

environment:

-NOTES_USE_HTTPS=true

-NOTES_CERTS_DIR=/etc/letsencrypt/archive/evsoul.com

volumes:

-certbot-webroot-evsoul:/notesapp/public/.well-known

-certbot-letsencrypt:/etc/letsencrypt

BoththeHTTPandHTTPSportsareexposedfromthecontainer,andwehavesomeenvironmentvariablesfortheconfigurationsettings.

TheTWITTER_CALLBACK_URLenvironmentvariableneedstobeupdatedtohttps://DOMAIN.ThesiteisnowhostedonHTTPS,andthereforeTwittershouldredirectouruserstotheHTTPSsite.

Withallthissetup,youshouldnowbeabletovisittheNotesapplication,eitherashttp://DOMAINorhttps://DOMAIN.IfyoutellyourcustomerstosimplyusetheHTTPSversionofyourwebsite,doesthatmeanyou'redone?

Page 705: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

No.Thesearchenginesroutinelydowngradesitesforhostingduplicatecontent,andhavingbothanHTTPandHTTPSversionofyourwebsiteisduplicatecontent.Furthermore,thebrowsermakersaremovingquicklytowardswarningbrowserusersthatHTTPwebsitesareinsecure,whileHTTPSwebsitesaresafe.Inotherwords,it'sveryimportanttoredirectanyHTTPconnectionstotheHTTPSversionofyourwebsite.

Onemethodtodothisiswiththeexpress-force-sslpackageforExpress(https://www.npmjs.com/package/express-force-ssl).Asthepackagenameimplies,itintegrateswithanExpressapplication(suchasNotes)andforcesthebrowsertoredirecttotheHTTPSversionofthewebsite.

But,wehaveothersecurityfishtofry.UsingHTTPSsolvesonlypartofthesecurityproblem.Inthenextsection,we'lllookatHelmet,atoolforExpressapplicationstosetmanysecurityoptionsintheHTTPheaders.HelmetincludesatooltorequirebrowserstousetheHTTPSversionofthewebsite,andwe'llalsoshowhowtouseexpress-force-sslatthesametime.

Beforewego,headtotheQualysSSLLabstestpageforSSLimplementations.Thisservicewillexamineyourwebsite,especiallytheSSLcertificates,andgiveyouascore.UsingthestepsinthissectionwillgiveyouanAscore.Toexamineyourscore,seehttps://www.ssllabs.com/ssltest/.

Page 706: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

PutonyourHelmetforacross-the-boardsecurityHelmet(https://www.npmjs.com/package/helmet)isnotasecuritysilverbullet(doHelmet'sauthorsthinkwe'retryingtoprotectagainstvampires?).Insteaditisatoolkitforsettingvarioussecurityheadersandtakingotherprotectivemeasures.

Inthenotesdirectory,installthepackagelikeso:

$npminstallhelmet--save

Thenaddthistonotes/app.mjs:

importhelmetfrom'helmet';

...

constapp=express();

exportdefaultapp;

app.use(helmet());

That'senoughformostapplications.UsingHelmetout-of-the-boxprovidesareasonablesetofdefaultsecurityoptions.Wecouldbedonewiththissectionrightnow,exceptthatit'susefultoexaminecloselywhatHelmetdoes,anditsoptions.

Helmetisactuallyaclusterof12modulesforapplyingseveralsecuritytechniques.Eachofthetechniquescanbeindividuallyenabledordisabled,andmanyhaveconfigurationsettingstomake.

Page 707: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

UsingHelmettosettheContent-Security-PolicyheaderTheContent-Security-Policy(CSP)headercanhelptoprotectagainstinjectedmaliciousJavaScriptandotherfiletypes.

WewouldberemisstonotpointoutaglaringproblemwithservicessuchastheNotesapplication.Ouruserscouldenteranycodetheylike,andanimproperly-behavingapplicationwillsimplydisplaythatcode.SuchapplicationscanbeavectorforJavaScriptinjectionattacksamongotherthings.

Totrythisout,editanoteandentersomethinglike:

<scriptsrc="http://example.com/malicious.js"></script>

ClicktheSavebutton,andyou'llseethiscodedisplayedastext.AdangerousversionofNoteswouldinsteadinsertthe<script>taginthenotesviewpagesuchthatthemaliciousJavaScriptwouldbeloadedandcauseaproblemforourvisitors.Instead,the<script>tagisencodedassafeHTMLsoitsimplyshowsupastextonthescreen.Wedidn'tdoanythingspecialforthatbehavior,Handlebarsdidthatforus.

Actually,it'salittlemoreinterestingifwelookattheHandlebarsdocumentationhttp://handlebarsjs.com/expressions.html,welearnaboutthisdistinction:

{{encodedAsHtml}}

{{{notEncodedAsHtml}}}

InHandlebars,avalueappearinginatemplateusingtwocurlybraces

Page 708: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

({{encoded}})isencodedusingHTMLcoding.Forthepreviousexample,theanglebracketisencodedas&lt;andsoonfordisplay,renderingthatJavaScriptcodeasneutraltextratherthanasHTMLelements.Ifinsteadyouusethreecurlybraces({{{notEncoded}}}),thevalueisnotencodedandisinsteadpresentedasis.ThemaliciousJavaScriptwouldbeexecutedinyourvisitor'sbrowser,causingproblemsforyourusers.

InNotes,ifwewantedouruserstoenterHTMLandhaveitdisplayedastheHTML,thenviews/noteview.hbswouldrequirethiscode:

{{#ifnote}}<divid="notebody">{{{note.body}}}</div>{{/if}}

Most(orall)templateenginesincludethispatternfordisplayingvalues.ThetemplatedevelopershaveachoicebetweenencodinganygivenvaluewithHTMLcodesordisplayingitasis.

ReturningtoHelmet'ssupportfortheheader,it'susefultohavestrictcontroloverthelocationsfromwhichthebrowsercandownloadfiles.Theissuenamed,ourusersenteringmaliciousJavaScriptcodeisonlyonerisk.SupposeamaliciousactorbrokeinandmodifiedthetemplatestoincludemaliciousJavaScriptcode?

Withtheheader,wecantellthebrowserthatJavaScriptcancomeonlyfromourownserverandGoogle'sCDN,andeverythingelseistoberejected.ThatmaliciousJavaScriptthat'sloadedfrompiratesden.netwon'trun.WecouldevenletourusersenterHTMLwithsomecomfortthatanymaliciousJavaScriptreferencedfromathird-partywebsitewon'trun.

ToseethedocumentationforthisHelmetmodule,seehttps://helmetjs.github.io/docs/csp/.

Therearealonglistofoptions.Forinstance,youcancausethebrowsertoreportanyviolationsbacktoyourserver,inwhichcaseyou'llneedtoimplementaroutehandlerfor/report-violation.ThissnippetissufficientforNotes:

Page 709: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

app.use(helmet.contentSecurityPolicy({

directives:{

defaultSrc:["'self'"],

scriptSrc:["'self'","'unsafe-inline'"],

styleSrc:["'self'",'fonts.googleapis.com'],

fontSrc:["'self'",'fonts.gstatic.com'],

connectSrc:["'self'",'wss://evsoul.com']

}

}));

Forbetterorforworse,theNotesapplicationimplementsonesecuritybestpractice—allCSSandJavaScriptfilesareloadedfromthesameserverastheapplication.Therefore,forthemostpart,wecanusethe'self'policy.Thereareseveralexceptions:

scriptSrc:DefineswhereweareallowedtoloadJavaScript.WedouseinlineJavaScriptinnoteview.hbsandindex.hbs,whichmustbeallowed.

styleSrc,fontSrc:We'reloadingCSSfilesfromboththelocalserver,andfromGoogleFonts.

connectSrc:TheWebSocketschannelusedbySocket.IOisdeclaredhere.

Page 710: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

app.use(helmet.dnsPrefetchControl({allow:false}));//ortrue

Page 711: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

app.use(helmet.frameguard({action:'deny'}));

Thissettingdeniesallsuch<iframe>content.

Page 712: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

UsingHelmettoremovetheX-Powered-ByheaderTheX-Powered-Byheadercangivemaliciousactorsaclueaboutthesoftwarestackinuse,informingthemofattackalgorithmsthatarelikelytosucceed.TheHidePoweredBysubmoduleforHelmetsimplyremovesthatheader.

Expresscandisablethisfeatureonitsown:

app.disable('x-powered-by')

OryoucanuseHelmettodoso:

app.use(helmet.hidePoweredBy())

Page 713: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ImprovingHTTPSwithStrictTransportSecurityHavingimplementedHTTPSsupport,wearen'tcompletelydone.Aswesaidearlier,it'snecessaryforouruserstousetheHTTPSversionofNotes,butasitstandstheycanstillusetheHTTPversion.TheStrictTransportSecurityheadernotifiesthebrowserthatitshouldusetheHTTPSversionofthesite.Sincethat'ssimplyanotification,it'salsonecessarytoimplementaredirectfromtheHTTPtoHTTPSversionofNotes.

WesettheStrict-Transport-Securitylikeso:constsixtyDaysInSeconds=5184000app.use(helmet.hsts({maxAge:sixtyDaysInSeconds}));

ThistellsthebrowsertostickwiththeHTTPSversionofthesiteforthenext60days,andnevervisittheHTTPversion.

And,aslongaswe'reonthisissue,let'sgoaheadanduseexpress-force-ssltoimplementtheredirect.Afteraddingadependencytothatpackageinpackage.json,addthisinapp.mjs:importforceSSLfrom'express-force-ssl';...app.use(forceSSL);app.use(bodyParser.json());

Page 714: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

MitigatingXSSattackswithHelmetXSSattacksattempttoinjectJavaScriptcodeintowebsiteoutput.Withmaliciouscodeinjectedintoanotherwebsite,theattackercanaccessinformationtheyotherwisecouldnotretrieve.TheX-XSS-ProtectionheaderpreventscertainXSSattacks,butnotallofthem.

app.use(helmet.xssFilter());

Page 715: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

AddressingCross-SiteRequestForgery(CSRF)attacksCSRFattacksaresimilartoXSSattacksinthatbothoccuracrossmultiplesites.InaCSRFattack,malicioussoftwareforgesabogusrequestonanothersite.Topreventsuchanattack,CSRFtokensaregeneratedforeachpageview,areincludedashiddenvaluesinHTMLFORMs,andthencheckedwhentheFORMissubmitted.Amismatchonthetokenscausestherequesttobedenied.

ThecsurfpackageisdesignedtobeusedwithExpresshttps://www.npmjs.com/package/csurfInthenotesdirectory,runthis:

$npminstallcsurf--save

Theninstallthemiddlewarelikeso:

importcsrffrom'csurf';

...

app.use(cookieParser());

app.use(csrf({cookie:true}));

ThecsurfmiddlewaremustbeinstalledfollowingthecookieParsermiddleware.

Next,foreverypagethatincludesaFORM,wemustgenerateandsendatokenwiththepage.Thatrequirestwothings,intheres.rendercallwegeneratethetoken,andthenintheviewtemplateweincludethetokenasahiddenINPUTonanyforminthepage.We'regoingtobetouchingonseveralfileshere,solet'sgetstarted.

Inroutes/notes.mjs,addthefollowingasaparametertotheres.rendercallfor

Page 716: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

the/add,/edit,/view,and/destroyroutes:

csrfToken:req.csrfToken()

Likewise,dothesameforthe/loginrouteinroutes/users.mjs.ThisaddsthegeneratedCSRFtokentotheparameterssenttothetemplate.

Theninviews/noteedit.hbsandviews/notedestroy.hbs,addthefollowing:

{{#ifuser}}

<inputtype="hidden"name="_csrf"value="{{csrfToken}}">

...

{{/if}}

Inviews/login.hbs,makethesameadditionbutwithoutthe{{#ifuser}}instruction.

Inviews/noteview.hbs,there'saformforsubmittingcomments.Makethischange:

<formid="submit-comment"class="well"data-asyncdata-target="#rating-modal"

action="notesmake-comment"method="POST">

<inputtype="hidden"name="_csrf"value="{{csrfToken}}">

...

</form>

This<input>tagrenderstheCSRFtokenintotheFORM.WhentheFORMissubmitted,thecsurfmiddlewarechecksitforcorrectnessandrejectsanythatdonotmatch.

Page 717: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

DenyingSQLinjectionattacksSQLinjectionisanotherlargeclassofsecurityexploits,wheretheattackerputsSQLcommandsintoinputdata.Seehttps://www.xkcd.com/327/foranexample.

Thesqlinjectionpackagescansquerystrings,requestbodyparameters,androuteparametersforSQLcode.

Installwith:$npminstallsqlinjection--save

Theninstallitinapp.mjs:importsqlinjectionfrom'sqlinjection';...app.use(sqlinjection);

Page 718: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SequelizedeprecationwarningregardingoperatorinjectionattackYoumayhaveseenthisdeprecationwarningprintedbyNotes:

sequelizedeprecatedStringbasedoperatorsarenowdeprecated.Pleaseuse

Symbolbasedoperatorsforbettersecurity,readmoreat

http://docs.sequelizejs.com/manual/tutorial/querying.html#operators

NowhereinNotesareweusingSequelizestring-basedoperators,andthereforethiswouldseemtobeaspuriouserrormessage.Inactuality,itisarealissuewithpotentialsimilartoanSQLinjectionattack.

Thisissuequeueentryhasanin-depthdiscussionofthesecurityproblem:https://github.com/sequelize/sequelize/issues/8417withmoredetailsinthedocumentationathttp://docs.sequelizejs.com/manual/tutorial/querying.html#operators-security.

Namely,aquerysuchasthis:

db.Token.findOne({

where:{token:req.query.token}

});

Issusceptibletoaninjection-styleattackthatwouldsubvertthequery.Wedohavecodelikethisinnotes/models/notes-sequelize.mjs,thereforethisissueshouldbeaddressed.

Fortunately,thesolutionissimplyamatterofdisablingthestringaliasesfortheOperators.IntheSequelizeconfigurationfileswedefined,we

Page 719: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

disablethealiaseslikeso:

dbname:users

username:

password:

params:

dialect:sqlite

storage:users-sequelize.sqlite3

operatorAliases:false

ThischangeshouldbemadeineverySequelizeconfigurationfile.

Page 720: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

ScanningforknownvulnerabilitiesThensppackage(https://www.npmjs.com/package/nsp)scansapackage.jsonornpm-shrinkwrap.json,lookingforknownvulnerabilities.Thecompanybehindthatpackagekeepsalistofsuchpackages,whicharequeriedbythensppackage.

Startingwithnpmversion6,thensppackagefunctionalityhasbeenfoldedintonpmitselfasthenpmauditcommand.Itisacommand-linetoolyourunlikeso:

$npminstallnsp

$./node_modules/.bin/nspcheck

(+)3vulnerabilitiesfound

┌────────────┬───────────────────────────────────────────────────────────────

─────┐

││RegularExpressionDenialofService

├────────────┼───────────────────────────────────────────────────────────────

─────┤

│Name│mime

├────────────┼───────────────────────────────────────────────────────────────

─────┤

│CVSS│7.5(High)

├────────────┼───────────────────────────────────────────────────────────────

─────┤

│Installed│1.3.4

├────────────┼───────────────────────────────────────────────────────────────

─────┤

│Vulnerable│<1.4.1||>2.0.0<2.0.3

├────────────┼───────────────────────────────────────────────────────────────

─────┤

│Patched│>=1.4.1<2.0.0||>=2.0.3

Page 721: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

├────────────┼───────────────────────────────────────────────────────────────

─────┤

│Path│[email protected]>[email protected]>[email protected]>[email protected]

├────────────┼───────────────────────────────────────────────────────────────

─────┤

│MoreInfo│https://nodesecurity.io/advisories/535

└────────────┴───────────────────────────────────────────────────────────────

─────┘

...moreoutput

Thisreportsaysthemimepackagecurrentlyinstalledisvulnerable.TheVulnerablelinesayswhichversionshavetheknownproblem,andthePatchedlinesayswhichreleasesaresafe.TheMoreInfolinetellsyouwheretogetmoreinformation.

Accordingtothenpmteam,npmversion6willincludethisfeatureasabaked-incapability.Seehttps://blog.npmjs.org/post/173260195980/announcing-npm6

If,asistrueinthiscase,you'reunsurewherethepackageisbeingused,trythis:

$npmlsmime

[email protected]/chap12/notes

├─┬[email protected]

│└─┬[email protected]

│└──[email protected]

└─┬[email protected]

└──[email protected]

Therearetwoversionsofthemimemodulebeingused,oneofwhichisvulnerablegoingbytheversionnumbersshownpreviously.Thisisgoodinformation,buthowdoyouuseit?Intheory,youshouldsimplychangethedependenciestouseasafeversionofthepackage.

Inthiscase,wehaveaprobleminthatourpackage.jsondidnotcausethemimepackagetobeinstalled.Instead,it'sthesendpackage,whichinturnwasrequestedbyExpress,whichisresponsible.We'reatthemercyofthat

Page 722: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

packagemaintainertoupdatetheirdependencies.

Fortunately,there'sanewerversionofExpressavailablewhichdoesindeedupdatethedependencyonthesendpackage,whichinturnupdatesitsdependencyonthemimepackage:

$npmlsmime

[email protected]/chap12/notes

├─┬[email protected]

│└─┬[email protected]

│└──[email protected]

└─┬[email protected]

└──[email protected]

Simplyupdatingthedependenciesfixedtheproblem.Butwenowhaveanadministrativetaskthat,accordingtotheTwelveFactorApplicationmodel,wemustautomate.

Onewaytoautomatethisisbyfirstaddingnsptothepackage.jsondependenciestoinstallitinsideNotes.Thensppackagesaysitisbestinstalledglobally,butthatwouldbeanimplicitdependency.TheTwelveFactorApplicationmodelsuggestsit'sbettertohaveexplicitdependencies,andthereforeit'sbesttolistnspinthepackage.json.Onceyou'veinstallednsp,addthissteptotheDockerfile:

WORKDIR/notesapp

RUNnpminstall--unsafe-perm

RUN./node_modules/.bin/nspcheck

BuildingtheDockercontainerwillnowgivethisresult:

Step21/25:RUN./node_modules/.bin/nspcheck

--->Runningin9dedf22ec1f9

(+)2vulnerabilitiesfound

...

ERROR:Service'notes'failedtobuild:Thecommand'binsh-c

./node_modules/.bin/nspcheck'returnedanon-zerocode:1

Inotherwords,wehaveasimplemechanismtonotuseaservicewhose

Page 723: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

dependencieshaveknownvulnerabilities.

Page 724: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

UsinggoodcookiepracticesSomenutritionistssayeatingtoomanysweets,suchascookies,isbadforyourhealth.Webcookies,however,arewidelyusedformanypurposesincludingrecordingwhetherabrowserisloggedinornot.

IntheNotesapplication,we'realreadyusingsomegoodpractices:

We'reusinganExpresssessioncookienamedifferentfromthedefaultshowninthedocumentation

TheExpresssessioncookiesecretisnotthedefaultshowninthedocumentation

Takentogether,anattackercan'texploitanyknownvulnerabilitystemmingfromusingdefaultvalues.Allkindsofsoftwareproductsshowdefaultpasswordsorotherdefaults.Thosedefaultscouldbesecurityvulnerabilities,andthereforeit'sbesttonotusethedefaults.Forexample,thedefaultRaspberryPilogin/passwordispiandraspberry.Whilethat'scute,anyRaspbian-basedIoTdevicethat'sleftwiththedefaultlogin/passwordissusceptible.

Butthere'sabitmorewecandotomakethesinglecookiewe'reusing,theExpresssessioncookie,moresecure.

Thepackagehasafewoptionsavailable,seehttps://www.npmjs.com/package/express-session:

app.use(session({

store:sessionStore,

secret:sessionSecret,

resave:true,

Page 725: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

saveUninitialized:true,

name:sessionCookieName,

secure:true,

maxAge:26060*1000//2hours

}));

Theseareadditionalattributesthatlookuseful.ThesecureattributerequiresthatcookiesbesentONLYoverHTTPSconnections.ThisensuresthecookiedataisencryptedbyHTTPSencryption.ThemaxAgeattributesetsanamountoftimethatcookiesarevalid,expressedasmilliseconds.

Page 726: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

SummaryInthischapter,we'vecoveredanextremelyimportanttopic,applicationsecurity.ThankstothehardworkoftheNode.jsandExpresscommunities,we'vebeenabletotightenthesecuritysimplybyaddingafewbitsofcodehereandtheretoconfiguresecuritymodules.We'veevenworkedouthowtopreventthesystemfrombeingbuilt,ifit'susingpackageswithknownvulnerabilities.

EnablingHTTPSmeansourusershavebetterassuranceofsecurity.TheSSLcertificateisameasureofauthenticitythatprotectsagainstman-in-the-middlesecurityattacks,andthedataisencryptedfortransmissionacrosstheinternet.Withalittlebitofwork,wewereabletosetupasystemtoacquire,andcontinuouslyrenew,freeSSLcertificatesfromtheLet'sEncryptservice.

Thehelmetpackageprovidesasuiteoftoolstosetsecurityheadersthatinstructwebbrowsersonhowtotreatourcontent.Thesesettingspreventormitigatewholeclassesofsecuritybugs.Withthecsurfpackage,we'reabletopreventcross-siterequestforgeryattacks.

ThesefewstepsareagoodstartforsecuringtheNotesapplication.But,donotstopatthesemeasures,becausethereisanever-endingsetofsecurityholes.

AllYahooemployeesaretrainedinsecuritypractices,Yahoo'sinternalnetworkhaswelldefinedroutingandotherprotectionssurroundingtheall-importantuserdatabase,andYahoo'sengineeringstaffissaltedwithpeoplecarryingthejobtitleParanoid,taskedwithconstantlyscrutinizingsystemsforpotentialsecurityvulnerabilities.Yetwithallthatwork,Yahoostillsufferedthelargestbreachofuseridentitydatainthehistoryoftheinternet.Thelessonisthatnoneofuscanneglectthesecurityoftheapplicationswedeploy.

Page 727: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Overthecourseofthisbook,we'vecomealongway.Overall,thejourneyhasbeentoexaminethemajorlifecyclestepsrequiredtodevelopanddeployaNode.jswebapplication.

WestartedbylearningthebasicsofNode.js,andhowtousetheplatformtodevelopsimpleservices.Throughoutthebook,we'velearnedhowadvancedJavaScriptfeaturessuchasasyncfunctionsandES6modulesareusedinNode.jsapplications.Tostoreourdata,welearnedhowtouseseveraldatabaseengines,andamethodologytomakeiteasytoswitchbetweenengines.

Mobile-firstdevelopmentisextremelyimportantintoday'senvironment,andtofulfillthatgoal,welearnedhowtousetheBootstrapframework.

Real-timecommunicationisexpectedonawidevarietyofwebsites,becauseadvancedJavaScriptcapabilitiesmeanswecannowoffermoreinteractiveservicesinourwebapplications.Tofulfillthatgoal,welearnedhowtousetheSocket.IOreal-timecommunicationsframework.

Deployingapplicationservicestocloudhostingiswidelyused,bothforsimplifyingsystemsetup,andtoscaleservicestomeetthedemandsofouruserbase.Tofulfillthatgoal,welearnedtouseDocker.WenotonlyusedDockerforproductiondeployment,butfordeployingatestinfrastructure,withinwhichwecanrununittestsandfunctionaltests.AndwelearnedhowtoimplementHTTPSsupportbydevelopingacustomDockercontainercontainingLet'sEncrypttoolstoregisterandrenewSSLcertificates.

Page 728: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

OtherBooksYouMayEnjoyIfyouenjoyedthisbook,youmaybeinterestedintheseotherbooksbyPackt:

Node.jsDesignPatterns-SecondEditionMarioCasciaro,LucianoMammino

ISBN:978-1-78328-732-1

Designandimplementaseriesofserver-sideJavaScriptpatternssoyouunderstandwhyandwhentoapplythemindifferentusecasescenarios

Becomecomfortablewithwritingasynchronouscodebyleveragingconstructssuchascallbacks,promises,generatorsandtheasync-awaitsyntax

IdentifythemostimportantconcernsandapplyuniquetrickstoachievehigherscalabilityandmodularityinyourNode.js

Page 729: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

application

Untangleyourmodulesbyorganizingandconnectingthemcoherently

Reusewell-knowntechniquestosolvecommondesignandcodingissues

ExplorethelatesttrendsinUniversalJavaScript,learnhowtowritecodethatrunsonbothNode.jsandthebrowserandleverageReactanditsecosystemtoimplementuniversalapplications

NodeCookbook-ThirdEditionDavidMarkClements,MatthiasBuus,MatteoCollina,PeterElger

ISBN:978-1-78588-124-4

DebugNode.jsprograms

WriteandpublishyourownNode.jsmodules

DetailedcoverageofNode.jscoreAPI's

UsewebframeworkssuchasExpress,HapiandKoafor

Page 730: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

acceleratedwebapplicationdevelopment

ApplyNode.jsstreamsforlow-footprintdataprocessing

Fast-trackperformanceknowledgeandoptimizationabilities

Persistencestrategies,includingdatabaseintegrationswithMongoDB,MySQL/MariaDB,Postgres,Redis,andLevelDB

Applycritical,essentialsecurityconcepts

UseNodewithbest-of-breeddeploymenttechnologies:Docker,KubernetesandAWS

Page 731: Fourth Edition Node.js Web Development · To my partner Maggie for being my loving partner throughout our joint life-journey mapt.io Mapt is an online digital library that gives you

Leaveareview-letotherreadersknowwhatyouthinkPleaseshareyourthoughtsonthisbookwithothersbyleavingareviewonthesitethatyouboughtitfrom.IfyoupurchasedthebookfromAmazon,pleaseleaveusanhonestreviewonthisbook'sAmazonpage.Thisisvitalsothatotherpotentialreaderscanseeanduseyourunbiasedopiniontomakepurchasingdecisions,wecanunderstandwhatourcustomersthinkaboutourproducts,andourauthorscanseeyourfeedbackonthetitlethattheyhaveworkedwithPackttocreate.Itwillonlytakeafewminutesofyourtime,butisvaluabletootherpotentialcustomers,ourauthors,andPackt.Thankyou!