Table of Contents · Express is a Node.js Web Framework. Node.js is an amazing tool for building...

Preview:

Citation preview

TableofContentsTheExpressHandbook

Expressoverview

Requestparameters

Sendingaresponse

SendingaJSONresponse

ManageCookies

WorkwithHTTPheaders

Redirects

Routing

CORS

Templating

ThePugGuide

Middleware

Servingstaticfiles

Sendfiles

Sessions

Validatinginput

Sanitizinginput

Handlingforms

Fileuploadsinforms

AnExpressHTTPSserverwithaself-signedcertificate

SetupLet'sEncryptforExpress

2

TheExpressHandbookTheExpressHandbookfollowsthe80/20rule:learnin20%ofthetimethe80%ofatopic.

Ifindthisapproachgivesawell-roundedoverview.ThisbookdoesnottrytocovereverythingunderthesunrelatedtoExpress.Ifyouthinksomespecifictopicshouldbeincluded,tellme.

YoucanreachmeonTwitter@flaviocopes.

Ihopethecontentsofthisbookwillhelpyouachievewhatyouwant:learnthebasicsExpress.

ThisbookiswrittenbyFlavio.Ipublishwebdevelopmenttutorialseverydayonmywebsiteflaviocopes.com.

Enjoy!

TheExpressHandbook

3

ExpressoverviewExpressisaNode.jsWebFramework.Node.jsisanamazingtoolforbuildingnetworkingservicesandapplications.ExpressbuildsontopofitsfeaturestoprovideeasytousefunctionalitythatsatisfytheneedsoftheWebServerusecase.

ExpressisaNode.jsWebFramework.

Node.jsisanamazingtoolforbuildingnetworkingservicesandapplications.

ExpressbuildsontopofitsfeaturestoprovideeasytousefunctionalitythatsatisfytheneedsoftheWebServerusecase.

It'sOpenSource,free,easytoextend,veryperformant,andhaslotsandlotsofpre-builtpackagesyoucanjustdropinanduse,toperformallkindofthings.

InstallationYoucaninstallExpressintoanyprojectwithnpm:

Expressoverview

4

npminstallexpress--save

orYarn:

yarnaddexpress

Bothcommandswillalsoworkinanemptydirectory,tostartupyourprojectfromscratch,although npmdoesnotcreatea package.jsonfileatall,andYarncreatesabasicone.

Justrun npminitor yarninitifyou'restartinganewprojectfromscratch.

HelloWorldWe'rereadytocreateourfirstExpressWebServer.

Hereissomecode:

constexpress=require('express')

constapp=express()

app.get('/',(req,res)=>res.send('HelloWorld!'))

app.listen(3000,()=>console.log('Serverready'))

Savethistoan index.jsfileinyourprojectrootfolder,andstarttheserverusing

nodeindex.js

Youcanopenthebrowsertoport3000onlocalhostandyoushouldseethe HelloWorld!message.

LearnthebasicsofExpressbyunderstandingtheHelloWorldcodeThose4linesofcodedoalotbehindthescenes.

First,weimportthe expresspackagetothe expressvalue.

Weinstantiateanapplicationbycallingits app()method.

Oncewehavetheapplicationobject,wetellittolistenforGETrequestsonthe /path,usingthe get()method.

Expressoverview

5

ThereisamethodforeveryHTTPverb: get(), post(), put(), delete(), patch():

app.get('/',(req,res)=>{/**/})

app.post('/',(req,res)=>{/**/})

app.put('/',(req,res)=>{/**/})

app.delete('/',(req,res)=>{/**/})

app.patch('/',(req,res)=>{/**/})

Thosemethodsacceptacallbackfunction,whichiscalledwhenarequestisstarted,andweneedtohandleit.

Wepassinanarrowfunction:

(req,res)=>res.send('HelloWorld!')

Expresssendsustwoobjectsinthiscallback,whichwecalled reqand res,thatrepresenttheRequestandtheResponseobjects.

RequestistheHTTPrequest.Itcangiveusalltheinfoaboutthat,includingtherequestparameters,theheaders,thebodyoftherequest,andmore.

ResponseistheHTTPresponseobjectthatwe'llsendtotheclient.

Whatwedointhiscallbackistosendthe'HelloWorld!'stringtotheclient,usingtheResponse.send()method.

Thismethodsetsthatstringasthebody,anditclosestheconnection.

Thelastlineoftheexampleactuallystartstheserver,andtellsittolistenonport 3000.Wepassinacallbackthatiscalledwhentheserverisreadytoacceptnewrequests.

Expressoverview

6

RequestparametersAhandyreferencetoalltherequestobjectpropertiesandhowtousethem

RequestparametersImentionedhowtheRequestobjectholdsalltheHTTPrequestinformation.

Thesearethemainpropertiesyou'lllikelyuse:

Property Description

.app holdsareferencetotheExpressappobject

.baseUrl thebasepathonwhichtheappresponds

.body containsthedatasubmittedintherequestbody(mustbeparsedandpopulatedmanuallybeforeyoucanaccessit)

.cookies containsthecookiessentbytherequest(needsthe cookie-parsermiddleware)

.hostname theserverhostname

.ip theserverIP

.method theHTTPmethodused

.params theroutenamedparameters

.path theURLpath

.protocol therequestprotocol

.query anobjectcontainingallthequerystringsusedintherequest

.secure trueiftherequestissecure(usesHTTPS)

.signedCookies containsthesignedcookiessentbytherequest(needsthe cookie-parsermiddleware)

.xhr trueiftherequestisanXMLHttpRequest

HowtoretrievetheGETquerystringparametersusingExpressThequerystringisthepartthatcomesaftertheURLpath,andstartswithanexclamationmark ?.

Requestparameters

7

Example:

?name=flavio

Multiplequeryparameterscanbeaddedusing &:

?name=flavio&age=35

HowdoyougetthosequerystringvaluesinExpress?

Expressmakesitveryeasybypopulatingthe Request.queryobjectforus:

constexpress=require('express')

constapp=express()

app.get('/',(req,res)=>{

console.log(req.query)

})

app.listen(8080)

Thisobjectisfilledwithapropertyforeachqueryparameter.

Iftherearenoqueryparams,it'sanemptyobject.

Thismakesiteasytoiterateonitusingthefor...inloop:

for(constkeyinreq.query){

console.log(key,req.query[key])

}

Thiswillprintthequerypropertykeyandthevalue.

Youcanaccesssinglepropertiesaswell:

req.query.name//flavio

req.query.age//35

HowtoretrievethePOSTquerystringparametersusingExpressPOSTqueryparametersaresentbyHTTPclientsforexamplebyforms,orwhenperformingaPOSTrequestsendingdata.

Requestparameters

8

Howcanyouaccessthisdata?

IfthedatawassentasJSON,using Content-Type:application/json,youwillusetheexpress.json()middleware:

constexpress=require('express')

constapp=express()

app.use(express.json())

IfthedatawassentasJSON,using Content-Type:application/x-www-form-urlencoded,youwillusethe express.urlencoded()middleware:

constexpress=require('express')

constapp=express()

app.use(express.urlencoded())

Inbothcasesyoucanaccessthedatabyreferencingitfrom Request.body:

app.post('/form',(req,res)=>{

constname=req.body.name

})

Note:olderExpressversionsrequiredtheuseofthe body-parsermoduletoprocessPOSTdata.ThisisnolongerthecaseasofExpress4.16(releasedinSeptember2017)andlaterversions.

Requestparameters

9

SendingaresponseHowtosendaresponsebacktotheclientusingExpress

IntheHelloWorldexampleweusedthe Response.send()methodtosendasimplestringasaresponse,andtoclosetheconnection:

(req,res)=>res.send('HelloWorld!')

Ifyoupassinastring,itsetsthe Content-Typeheaderto text/html.

ifyoupassinanobjectoranarray,itsetsthe application/json Content-Typeheader,andparsesthatparameterintoJSON.

send()automaticallysetsthe Content-LengthHTTPresponseheader.

send()alsoautomaticallyclosestheconnection.

Useend()tosendanemptyresponse

Analternativewaytosendtheresponse,withoutanybody,it'sbyusingthe Response.end()method:

res.end()

SettheHTTPresponsestatus

Usethe Response.status():

res.status(404).end()

or

res.status(404).send('Filenotfound')

sendStatus()isashortcut:

res.sendStatus(200)

//===res.status(200).send('OK')

res.sendStatus(403)

//===res.status(403).send('Forbidden')

Sendingaresponse

10

res.sendStatus(404)

//===res.status(404).send('NotFound')

res.sendStatus(500)

//===res.status(500).send('InternalServerError')

Sendingaresponse

11

SendingaJSONresponseHowtoserveJSONdatausingtheNode.jsExpresslibrary

WhenyoulistenforconnectionsonarouteinExpress,thecallbackfunctionwillbeinvokedoneverynetworkcallwithaRequestobjectinstanceandaResponseobjectinstance.

Example:

app.get('/',(req,res)=>res.send('HelloWorld!'))

Hereweusedthe Response.send()method,whichacceptsanystring.

YoucansendJSONtotheclientbyusing Response.json(),ausefulmethod.

Itacceptsanobjectorarray,andconvertsittoJSONbeforesendingit:

res.json({username:'Flavio'})

SendingaJSONresponse

12

ManageCookiesHowtousethe`Response.cookie()`methodtomanipulateyourcookies

Usethe Response.cookie()methodtomanipulateyourcookies.

Examples:

res.cookie('username','Flavio')

Thismethodacceptsathirdparameterwhichcontainsvariousoptions:

res.cookie('username','Flavio',{domain:'.flaviocopes.com',path:'/administrator',sec

ure:true})

res.cookie('username','Flavio',{expires:newDate(Date.now()+900000),httpOnly:true

})

Themostusefulparametersyoucansetare:

Value Description

domain thecookiedomainname

expiressetthecookieexpirationdate.Ifmissing,or0,thecookieisasessioncookie

httpOnly setthecookietobeaccessibleonlybythewebserver.SeeHttpOnly

maxAge settheexpirytimerelativetothecurrenttime,expressedinmilliseconds

path thecookiepath.Defaultsto/

secure MarksthecookieHTTPSonly

signed setthecookietobesigned

sameSite Valueof SameSite

Acookiecanbeclearedwith

res.clearCookie('username')

ManageCookies

13

WorkwithHTTPheadersLearnhowtoaccessandchangeHTTPheadersusingExpress

AccessHTTPheadersvaluesfromarequestYoucanaccessalltheHTTPheadersusingthe Request.headersproperty:

app.get('/',(req,res)=>{

console.log(req.headers)

})

Usethe Request.header()methodtoaccessoneindividualrequestheadervalue:

app.get('/',(req,res)=>{

req.header('User-Agent')

})

ChangeanyHTTPheadervalueofaresponseYoucanchangeanyHTTPheadervalueusing Response.set():

res.set('Content-Type','text/html')

ThereisashortcutfortheContent-Typeheaderhowever:

res.type('.html')

//=>'text/html'

res.type('html')

//=>'text/html'

res.type('json')

//=>'application/json'

res.type('application/json')

//=>'application/json'

res.type('png')

//=>image/png:

WorkwithHTTPheaders

14

WorkwithHTTPheaders

15

RedirectsHowtoredirecttootherpagesserver-side

RedirectsarecommoninWebDevelopment.YoucancreatearedirectusingtheResponse.redirect()method:

res.redirect('/go-there')

Thiscreatesa302redirect.

A301redirectismadeinthisway:

res.redirect(301,'/go-there')

Youcanspecifyanabsolutepath( /go-there),anabsoluteurl( https://anothersite.com),arelativepath( go-there)orusethe ..togobackonelevel:

res.redirect('../go-there')

res.redirect('..')

YoucanalsoredirectbacktotheRefererHTTPheadervalue(defaultingto /ifnotset)using

res.redirect('back')

Redirects

16

RoutingRoutingistheprocessofdeterminingwhatshouldhappenwhenaURLiscalled,oralsowhichpartsoftheapplicationshouldhandleaspecificincomingrequest.

RoutingistheprocessofdeterminingwhatshouldhappenwhenaURLiscalled,oralsowhichpartsoftheapplicationshouldhandleaspecificincomingrequest.

IntheHelloWorldexampleweusedthiscode

app.get('/',(req,res)=>{/**/})

ThiscreatesaroutethatmapsaccessingtherootdomainURL /usingtheHTTPGETmethodtotheresponsewewanttoprovide.

Namedparameters

Whatifwewanttolistenforcustomrequests,maybewewanttocreateaservicethatacceptsastring,andreturnsthatuppercase,andwedon'twanttheparametertobesentasaquerystring,butpartoftheURL.Weusenamedparameters:

app.get('/uppercase/:theValue',(req,res)=>res.send(req.params.theValue.toUpperCase()))

Ifwesendarequestto /uppercase/test,we'llget TESTinthebodyoftheresponse.

YoucanusemultiplenamedparametersinthesameURL,andtheywillallbestoredinreq.params.

Usearegularexpressiontomatchapath

Youcanuseregularexpressionstomatchmultiplepathswithonestatement:

app.get(/post/,(req,res)=>{/**/})

willmatch /post, /post/first, /thepost, /posting/something,andsoon.

Routing

17

CORSHowtoallowcrosssiterequestsbysettingupCORS

AJavaScriptapplicationrunninginthebrowsercanusuallyonlyaccessHTTPresourcesonthesamedomain(origin)thatservesit.

Loadingimagesorscripts/stylesalwaysworks,butXHRandFetchcallstoanotherserverwillfail,unlessthatserverimplementsawaytoallowthatconnection.

ThiswayiscalledCORS,Cross-OriginResourceSharing.

AlsoloadingWebFontsusing @font-facehassame-originpolicybydefault,andotherlesspopularthings(likeWebGLtexturesand drawImageresourcesloadedintheCanvasAPI).

OneveryimportantthingthatneedsCORSisESModules,recentlyintroducedinmodernbrowsers.

Ifyoudon'tsetupaCORSpolicyontheserverthatallowstoserve3rdpartorigins,therequestwillfail.

Fetchexample:

XHRexample:

CORS

18

ACross-Originresourcefailsifit's:

toadifferentdomaintoadifferentsubdomaintoadifferentporttoadifferentprotocol

andit'sthereforyoursecurity,topreventmalicioususerstoexploittheWebPlatform.

Butifyoucontrolboththeserverandtheclient,youhaveallthegoodreasonstoallowthemtotalktoeachother.

How?

Itdependsonyourserver-sidestack.

BrowsersupportPrettygood(basicallyallexceptIE<10):

ExamplewithExpressIfyouareusingNode.jsandExpressasaframework,usetheCORSmiddlewarepackage.

Here'sasimpleimplementationofanExpressNode.jsserver:

constexpress=require('express')

constapp=express()

CORS

19

app.get('/without-cors',(req,res,next)=>{

res.json({msg:'ۘ noCORS,noparty!'})

})

constserver=app.listen(3000,()=>{

console.log('Listeningonport%s',server.address().port)

})

Ifyouhit /without-corswithafetchrequestfromadifferentorigin,it'sgoingtoraisetheCORSissue.

Allyouneedtodotomakethingsworkoutistorequirethe corspackagelinkedabove,andpassitinasamiddlewarefunctiontoanendpointrequesthandler:

constexpress=require('express')

constcors=require('cors')

constapp=express()

app.get('/with-cors',cors(),(req,res,next)=>{

res.json({msg:'WHOAHwithCORSitworks!ث ʦ '})

})

/*therestoftheapp*/

ImadeasimpleGlitchexample.Hereistheclientworking,andhere'sitscode:https://glitch.com/edit/#!/flavio-cors-client.

ThisistheNode.jsExpressserver:https://glitch.com/edit/#!/flaviocopes-cors-example-express

NotehowtherequestthatfailsbecauseitdoesnothandletheCORSheadingscorrectlyisstillreceived,asyoucanseeintheNetworkpanel,whereyoufindthemessagetheserversent:

CORS

20

AllowonlyspecificoriginsThisexamplehasaproblemhowever:ANYrequestwillbeacceptedbytheserverascross-origin.

AsyoucanseeintheNetworkpanel,therequestthatpassedhasaresponseheader access-control-allow-origin:*:

Youneedtoconfiguretheservertoonlyallowoneorigintoserve,andblockalltheothers.

Usingthesame corsNodelibrary,here'showyouwoulddoit:

constcors=require('cors')

constcorsOptions={

origin:'https://yourdomain.com'

}

app.get('/products/:id',cors(corsOptions),(req,res,next)=>{

//...

})

Youcanservemoreaswell:

constwhitelist=['http://example1.com','http://example2.com']

constcorsOptions={

origin:function(origin,callback){

if(whitelist.indexOf(origin)!==-1){

callback(null,true)

}else{

callback(newError('NotallowedbyCORS'))

}

}

}

CORS

21

PreflightTherearesomerequeststhatarehandledina"simple"way.All GETrequestsbelongtothisgroup.

Alsosome POSTand HEADrequestsdoaswell.

POSTrequestsarealsointhisgroup,iftheysatisfytherequirementofusingaContent-Typeof

application/x-www-form-urlencoded

multipart/form-data

text/plain

Allotherrequestsmustrunthroughapre-approvalphase,calledpreflight.Thebrowserdoesthistodetermineifithasthepermissiontoperformanaction,byissuingan OPTIONSrequest.

Apreflightrequestcontainsafewheadersthattheserverwillusetocheckpermissions(irrelevantfieldsomitted):

OPTIONS/the/resource/you/request

Access-Control-Request-Method:POST

Access-Control-Request-Headers:origin,x-requested-with,accept

Origin:https://your-origin.com

Theserverwillrespondwithsomethinglikethis(irrelevantfieldsomitted):

HTTP/1.1200OK

Access-Control-Allow-Origin:https://your-origin.com

Access-Control-Allow-Methods:POST,GET,OPTIONS,DELETE

WecheckedforPOST,buttheservertellsuswecanalsoissueotherHTTPrequesttypesforthatparticularresource.

FollowingtheNode.jsExpressexampleabove,theservermustalsohandletheOPTIONSrequest:

varexpress=require('express')

varcors=require('cors')

varapp=express()

//allowOPTIONSonjustoneresource

app.options('/the/resource/you/request',cors())

//allowOPTIONSonallresources

app.options('*',cors())

CORS

22

CORS

23

TemplatingExpressiscapableofhandlingserver-sidetemplateengines.Templateenginesallowustoadddatatoaview,andgenerateHTMLdynamically.

Expressiscapableofhandlingserver-sidetemplateengines.

Templateenginesallowustoadddatatoaview,andgenerateHTMLdynamically.

ExpressusesJadeasthedefault.JadeistheoldversionofPug,specificallyPug1.0.

ThenamewaschangedfromJadetoPugduetoatrademarkissuein2016,whentheprojectreleasedversion2.YoucanstilluseJade,akaPug1.0,butgoingforward,it'sbesttousePug2.0

AlthoughthelastversionofJadeis3yearsold(atthetimeofwriting,summer2018),it'sstillthedefaultinExpressforbackwardcompatibilityreasons.

Inanynewproject,youshouldusePugoranotherengineofyourchoice.TheofficialsiteofPugishttps://pugjs.org/.

Youcanusemanydifferenttemplateengines,includingPug,Handlebars,Mustache,EJSandmore.

UsingPugTousePugwemustfirstinstallit:

npminstallpug

andwheninitializingtheExpressapp,weneedtosetit:

constexpress=require('express')

constapp=express()

app.set('viewengine','pug')

Wecannowstartwritingourtemplatesin .pugfiles.

Createanaboutview:

app.get('/about',(req,res)=>{

res.render('about')

})

Templating

24

andthetemplatein views/about.pug:

pHellofromFlavio

Thistemplatewillcreatea ptagwiththecontent HellofromFlavio.

Youcaninterpolateavariableusing

app.get('/about',(req,res)=>{

res.render('about',{name:'Flavio'})

})

pHellofrom#{name}

ThisisaveryshortintroductiontoPug,inthecontextofusingitwithExpress.LookatthePugguideformoreinformationonhowtousePug.

IfyouareusedtotemplateenginesthatuseHTMLandinterpolatevariables,likeHandlebars(describednext),youmightrunintoissues,especiallywhenyouneedtoconvertexistingHTMLtoPug.ThisonlineconverterfromHTMLtoJade(whichisverysimilar,butalittledifferentthanPug)willbeagreathelp:https://jsonformatter.org/html-to-jade

AlsoseethedifferencesbetweenJadeandPug

UsingHandlebarsLet'stryanduseHandlebarsinsteadofPug.

Youcaninstallitusing npminstallhbs.

Putan about.hbstemplatefileinthe views/folder:

Hellofrom{{name}}

andthenusethisExpressconfigurationtoserveiton /about:

constexpress=require('express')

constapp=express()

consthbs=require('hbs')

app.set('viewengine','hbs')

app.set('views',path.join(__dirname,'views'))

app.get('/about',(req,res)=>{

Templating

25

res.render('about',{name:'Flavio'})

})

app.listen(3000,()=>console.log('Serverready'))

YoucanalsorenderaReactapplicationserver-side,usingthe express-react-viewspackage.

Startwith npminstallexpress-react-viewsreactreact-dom.

Nowinsteadofrequiring hbswerequire express-react-viewsandusethatastheengine,using jsxfiles:

constexpress=require('express')

constapp=express()

app.set('viewengine','jsx')

app.engine('jsx',require('express-react-views').createEngine())

app.get('/about',(req,res)=>{

res.render('about',{name:'Flavio'})

})

app.listen(3000,()=>console.log('Serverready'))

Justputan about.jsxfilein views/,andcalling /aboutshouldpresentyouan"HellofromFlavio"string:

constReact=require('react')

classHelloMessageextendsReact.Component{

render(){

return<div>Hellofrom{this.props.name}</div>

}

}

module.exports=HelloMessage

Templating

26

ThePugGuideHowtousethePugtemplatingengine

IntroductiontoPugHowdoesPuglooklikeInstallPugSetupPugtobethetemplateengineinExpressYourfirstPugtemplateInterpolatingvariablesinPugInterpolateafunctionreturnvalueAddingidandclassattributestoelementsSetthedoctypeMetatagsAddingscriptsandstylesInlinescriptsLoopsConditionalsSetvariablesIncrementingvariablesAssigningvariablestoelementvaluesIteratingovervariablesIncludingotherPugfilesDefiningblocksExtendingabasetemplateComments

VisibleInvisible

IntroductiontoPugWhatisPug?It'satemplateengineforserver-sideNode.jsapplications.

Expressiscapableofhandlingserver-sidetemplateengines.Templateenginesallowustoadddatatoaview,andgenerateHTMLdynamically.

Pugisanewnameforanoldthing.It'sJade2.0.

ThePugGuide

27

ThenamewaschangedfromJadetoPugduetoatrademarkissuein2016,whentheprojectreleasedversion2.YoucanstilluseJade,akaPug1.0,butgoingforward,it'sbesttousePug2.0

AlsoseethedifferencesbetweenJadeandPug

ExpressusesJadeasthedefault.JadeistheoldversionofPug,specificallyPug1.0.

AlthoughthelastversionofJadeis3yearsold(atthetimeofwriting,summer2018),it'sstillthedefaultinExpressforbackwardcompatibilityreasons.

Inanynewproject,youshouldusePugoranotherengineofyourchoice.TheofficialsiteofPugishttps://pugjs.org/.

HowdoesPuglooklike

pHellofromFlavio

Thistemplatewillcreatea ptagwiththecontent HellofromFlavio.

Asyoucansee,Pugisquitespecial.Ittakesthetagnameasthefirstthinginaline,andtherestisthecontentthatgoesinsideit.

IfyouareusedtotemplateenginesthatuseHTMLandinterpolatevariables,likeHandlebars(describednext),youmightrunintoissues,especiallywhenyouneedtoconvertexistingHTMLtoPug.ThisonlineconverterfromHTMLtoJade(whichisverysimilar,butalittledifferentthanPug)willbeagreathelp:https://jsonformatter.org/html-to-jade

InstallPugInstallingPugisassimpleasrunning npminstall:

npminstallpug

SetupPugtobethetemplateengineinExpressandwheninitializingtheExpressapp,weneedtosetit:

constexpress=require('express')

constapp=express()

app.set('viewengine','pug')

ThePugGuide

28

app.set('views',path.join(__dirname,'views'))

YourfirstPugtemplateCreateanaboutview:

app.get('/about',(req,res)=>{

res.render('about')

})

andthetemplatein views/about.pug:

pHellofromFlavio

Thistemplatewillcreatea ptagwiththecontent HellofromFlavio.

InterpolatingvariablesinPugYoucaninterpolateavariableusing

app.get('/about',(req,res)=>{

res.render('about',{name:'Flavio'})

})

pHellofrom#{name}

InterpolateafunctionreturnvalueYoucaninterpolateafunctionreturnvalueusing

app.get('/about',(req,res)=>{

res.render('about',{getName:()=>'Flavio'})

})

pHellofrom#{getName()}

Addingidandclassattributestoelements

ThePugGuide

29

p#title

p.title

Setthedoctype

doctypehtml

Metatags

html

head

meta(charset='utf-8')

meta(http-equiv='X-UA-Compatible',content='IE=edge')

meta(name='description',content='Somedescription')

meta(name='viewport',content='width=device-width,initial-scale=1')

Addingscriptsandstyles

html

head

script(src="script.js")

script(src='//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js')

link(rel='stylesheet',href='css/main.css')

Inlinescripts

scriptalert('test')

script

(function(b,o,i,l,e,r){b.GoogleAnalyticsObject=l;b[l]||(b[l]=

function(){(b[l].q=b[l].q||[]).push(arguments)});b[l].l=+newDate;

e=o.createElement(i);r=o.getElementsByTagName(i)[0];

e.src='//www.google-analytics.com/analytics.js';

r.parentNode.insertBefore(e,r)}(window,document,'script','ga'));

ga('create','UA-XXXXX-X');ga('send','pageview');

Loops

ThePugGuide

30

ul

eachcolorin['Red','Yellow','Blue']

li=color

ul

eachcolor,indexin['Red','Yellow','Blue']

li='Colornumber'+index+':'+color

Conditionals

ifname

h2Hellofrom#{name}

else

h2Hello

else-ifworkstoo:

ifname

h2Hellofrom#{name}

elseifanotherName

h2Hellofrom#{anotherName}

else

h2Hello

SetvariablesYoucansetvariablesinPugtemplates:

-varname='Flavio'

-varage=35

-varroger={name:'Roger'}

-vardogs=['Roger','Syd']

IncrementingvariablesYoucanincrementanumericvariableusing ++:

age++

Assigningvariablestoelementvalues

ThePugGuide

31

p=name

span.age=age

IteratingovervariablesYoucanuse foror each.Thereisnodifference.

fordogindogs

li=dog

ul

eachdogindogs

li=dog

Youcanuse .lengthtogetthenumberofitems:

pThereare#{values.length}

whileisanotherkindofloop:

-varn=0;

ul

whilen<=5

li=n++

IncludingotherPugfilesInaPugfileyoucanincludeotherPugfiles:

includeotherfile.pug

DefiningblocksAwellorganizedtemplatesystemwilldefineabasetemplate,andthenalltheothertemplatesextendfromit.

ThePugGuide

32

Thewayapartofatemplatecanbeextendedisbyusingblocks:

html

head

script(src="script.js")

script(src='//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js')

link(rel='stylesheet',href='css/main.css')

blockhead

body

blockbody

h1Homepage

pwelcome

Inthiscaseoneblock, body,hassomecontent,while headdoesnot. headisintendedtobeusedtoaddadditionalcontenttotheheading,whilethe bodycontentismadetobeoverriddenbyotherpages.

ExtendingabasetemplateAtemplatecanextendabasetemplatebyusingthe extendskeyword:

extendshome.pug

Oncethisisdone,youneedtoredefineblocks.Allthecontentofthetemplatemustgointoblocks,otherwisetheenginedoesnotknowwheretoputthem.

Example:

extendshome.pug

blockbody

h1Anotherpage

pHey!

ul

liSomething

liSomethingelse

Youcanredefineoneormoreblocks.Theonesnotredefinedwillbekeptwiththeoriginaltemplatecontent.

CommentsCommentsinPugcanbeoftwotypes:visibleornotvisibleintheresultingHTML.

ThePugGuide

33

Visible

Inline:

//somecomment

Block:

//

some

comment

Invisible

Inline:

//-somecomment

Block:

//-

some

comment

ThePugGuide

34

MiddlewareAmiddlewareisafunctionthathooksintotheroutingprocess,andperformssomeoperationatsomepoint,dependingonwhatitwanttodo.

Amiddlewareisafunctionthathooksintotheroutingprocess,andperformssomeoperationatsomepoint,dependingonwhatitwanttodo.

It'scommonlyusedtoedittherequestorresponseobjects,orterminatetherequestbeforeitreachestheroutehandlercode.

It'saddedtotheexecutionstacklikethis:

app.use((req,res,next)=>{/**/})

Thisissimilartodefiningaroute,butinadditiontotheRequestandResponseobjectsinstances,wealsohaveareferencetothenextmiddlewarefunction,whichweassigntothevariable next.

Wealwayscall next()attheendofourmiddlewarefunction,topasstheexecutiontothenexthandler,unlesswewanttoprematurelyendtheresponse,andsenditbacktotheclient.

Youtypicallyusepre-mademiddleware,intheformof npmpackages.Abiglistoftheavailableonesishere.

Oneexampleis cookie-parser,whichisusedtoparsethecookiesintothe req.cookiesobject.Youinstallitusing npminstallcookie-parserandyoucanuseitlikethis:

constexpress=require('express')

constapp=express()

constcookieParser=require('cookie-parser')

app.get('/',(req,res)=>res.send('HelloWorld!'))

app.use(cookieParser())

app.listen(3000,()=>console.log('Serverready'))

Youcanalsosetamiddlewarefunctiontorunforspecificroutesonly,notforall,byusingitasthesecondparameteroftheroutedefinition:

constmyMiddleware=(req,res,next)=>{

/*...*/

next()

}

app.get('/',myMiddleware,(req,res)=>res.send('HelloWorld!'))

Middleware

35

Ifyouneedtostoredatathat'sgeneratedinamiddlewaretopassitdowntosubsequentmiddlewarefunctions,ortotherequesthandler,youcanusethe Request.localsobject.Itwillattachthatdatatothecurrentrequest:

req.locals.name='Flavio'

Middleware

36

ServingstaticfilesHowtoservestaticassetsdirectlyfromafolderinExpress

It'scommontohaveimages,CSSandmoreina publicsubfolder,andexposethemtotherootlevel:

constexpress=require('express')

constapp=express()

app.use(express.static('public'))

/*...*/

app.listen(3000,()=>console.log('Serverready'))

Ifyouhavean index.htmlfilein public/,thatwillbeservedifyounowhittherootdomainURL( http://localhost:3000)

Servingstaticfiles

37

SendfilesExpressprovidesahandymethodtotransferafileasattachment:`Response.download()`

Expressprovidesahandymethodtotransferafileasattachment: Response.download().

Onceauserhitsaroutethatsendsafileusingthismethod,browserswillprompttheuserfordownload.

The Response.download()methodallowsyoutosendafileattachedtotherequest,andthebrowserinsteadofshowingitinthepage,itwillsaveittodisk.

app.get('/',(req,res)=>res.download('./file.pdf'))

Inthecontextofanapp:

constexpress=require('express')

constapp=express()

app.get('/',(req,res)=>res.download('./file.pdf'))

app.listen(3000,()=>console.log('Serverready'))

Youcansetthefiletobesentwithacustomfilename:

res.download('./file.pdf','user-facing-filename.pdf')

Thismethodprovidesacallbackfunctionwhichyoucanusetoexecutecodeoncethefilehasbeensent:

res.download('./file.pdf','user-facing-filename.pdf',(err)=>{

if(err){

//handleerror

return

}else{

//dosomething

}

})

Sendfiles

38

SessionsHowtousesessionstoidentifyusersacrossrequests

BydefaultExpressrequestsaresequentialandnorequestcanbelinkedtoeachother.Thereisnowaytoknowifthisrequestcomesfromaclientthatalreadyperformedarequestpreviously.

Userscannotbeidentifiedunlessusingsomekindofmechanismthatmakesitpossible.

That'swhatsessionsare.

Whenimplemented,everyuserofyouAPIorwebsitewillbeassignedauniquesession,andthisallowsyoutostoretheuserstate.

We'llusethe express-sessionmodule,whichismaintainedbytheExpressteam.

Youcaninstallitusing

npminstallexpress-session

andonceyou'redone,youcaninstantiateitinyourapplicationwith

constsession=require('express-session')

Thisisamiddleware,soyouinstallitinExpressusing

constexpress=require('express')

constsession=require('express-session')

constapp=express()

app.use(session(

'secret':'343ji43j4n3jn4jk3n'

))

Afterthisisdone,alltherequeststotheapproutesarenowusingsessions.

secretistheonlyrequiredparameter,buttherearemanymoreyoucanuse.Itshouldbearandomlyuniquestringforyouapplication.

Thesessionisattachedtotherequest,soyoucanaccessitusing req.sessionhere:

app.get('/',(req,res,next)=>{

//req.session

}

Sessions

39

Thisobjectcanbeusedtogetdataoutofthesession,andalsotosetdata:

req.session.name='Flavio'

console.log(req.session.name)//'Flavio'

ThisdataisserializedasJSONwhenstored,soyouaresafetousenestedobjects.

Youcanusesessionstocommunicatedatatomiddlewarethat'sexecutedlater,ortoretrieveitlaterononsubsequentrequests.

Whereisthesessiondatastored?itdependsonhowyousetupthe express-sessionmodule.

Itcanstoresessiondatain

memory,notmeantforproductionadatabaselikeMySQLorMongoamemorycachelikeRedisorMemcached

Thereisabiglistof3rdpackagesthatimplementawidevarietyofdifferentcompatiblecachingstoresinhttps://github.com/expressjs/session

Allsolutionsstorethesessionidinacookie,andkeepthedataserver-side.Theclientwillreceivethesessionidinacookie,andwillsenditalongwitheveryHTTPrequest.

We'llreferencethatserver-sidetoassociatethesessionidwiththedatastoredlocally.

Memoryisthedefault,itrequiresnospecialsetuponyourpart,it'sthesimplestthingbutit'smeantonlyfordevelopmentpurposes.

ThebestchoiceisamemorycachelikeRedis,forwhichyouneedtosetupitsowninfrastructure.

AnotherpopularpackagetomanagesessionsinExpressis cookie-session,whichhasabigdifference:itstoresdataclient-sideinthecookie.Idonotrecommenddoingthatbecausestoringdataincookiesmeansthatit'sstoredclient-side,andsentbackandforthineverysinglerequestmadebytheuser.It'salsolimitedinsize,asitcanonlystore4kilobytesofdata.Cookiesalsoneedtobesecured,butbydefaulttheyarenot,sincesecureCookiesarepossibleonHTTPSsitesandyouneedtoconfigurethemifyouhaveproxies.

Sessions

40

ValidatinginputLearnhowtovalidateanydatacominginasinputinyourExpressendpoints

SayyouhaveaPOSTendpointthatacceptsthename,emailandageparameters:

constexpress=require('express')

constapp=express()

app.use(express.json())

app.post('/form',(req,res)=>{

constname=req.body.name

constemail=req.body.email

constage=req.body.age

})

Howdoyouserver-sidevalidatethoseresultstomakesure

nameisastringofatleast3characters?emailisarealemail?ageisanumber,between0and110?

ThebestwaytohandlevalidatinganykindofinputcomingfromoutsideinExpressisbyusingthe express-validatorpackage:

npminstallexpress-validator

Yourequirethe checkobjectfromthepackage:

const{check}=require('express-validator/check')

Wepassanarrayof check()callsasthesecondargumentofthe post()call.Everycheck()callacceptstheparameternameasargument:

app.post('/form',[

check('name').isLength({min:3}),

check('email').isEmail(),

check('age').isNumeric()

],(req,res)=>{

constname=req.body.name

constemail=req.body.email

constage=req.body.age

})

Validatinginput

41

NoticeIused

isLength()

isEmail()

isNumeric()

Therearemanymoreofthesemethods,allcomingfromvalidator.js,including:

contains(),checkifvaluecontainsthespecifiedvalueequals(),checkifvalueequalsthespecifiedvalueisAlpha()

isAlphanumeric()

isAscii()

isBase64()

isBoolean()

isCurrency()

isDecimal()

isEmpty()

isFQDN(),isafullyqualifieddomainname?isFloat()

isHash()

isHexColor()

isIP()

isIn(),checkifthevalueisinanarrayofallowedvaluesisInt()

isJSON()

isLatLong()

isLength()

isLowercase()

isMobilePhone()

isNumeric()

isPostalCode()

isURL()

isUppercase()

isWhitelisted(),checkstheinputagainstawhitelistofallowedcharacters

Youcanvalidatetheinputagainstaregularexpressionusing matches().

Datescanbecheckedusing

isAfter(),checkiftheentereddateisaftertheoneyoupassisBefore(),checkiftheentereddateisbeforetheoneyoupassisISO8601()

Validatinginput

42

isRFC3339()

Forexactdetailsonhowtousethosevalidators,refertohttps://github.com/chriso/validator.js#validators.

Allthosecheckscanbecombinedbypipingthem:

check('name')

.isAlpha()

.isLength({min:10})

Ifthereisanyerror,theserverautomaticallysendsaresponsetocommunicatetheerror.Forexampleiftheemailisnotvalid,thisiswhatwillbereturned:

{

"errors":[{

"location":"body",

"msg":"Invalidvalue",

"param":"email"

}]

}

Thisdefaulterrorcanbeoverriddenforeachcheckyouperform,using withMessage():

check('name')

.isAlpha()

.withMessage('Mustbeonlyalphabeticalchars')

.isLength({min:10})

.withMessage('Mustbeatleast10charslong')

Whatifyouwanttowriteyourownspecial,customvalidator?Youcanusethe customvalidator.

Inthecallbackfunctionyoucanrejectthevalidationeitherbythrowinganexception,orbyreturningarejectedpromise:

app.post('/form',[

check('name').isLength({min:3}),

check('email').custom(email=>{

if(alreadyHaveEmail(email)){

thrownewError('Emailalreadyregistered')

}

}),

check('age').isNumeric()

],(req,res)=>{

constname=req.body.name

constemail=req.body.email

constage=req.body.age

})

Validatinginput

43

Thecustomvalidator:

check('email').custom(email=>{

if(alreadyHaveEmail(email)){

thrownewError('Emailalreadyregistered')

}

})

canberewrittenas

check('email').custom(email=>{

if(alreadyHaveEmail(email)){

returnPromise.reject('Emailalreadyregistered')

}

})

Validatinginput

44

SanitizinginputYou'veseenhowtovalidateinputthatcomesfromtheoutsideworldtoyourExpressapp.

There'sonethingyouquicklylearnwhenyourunapublic-facingserver:nevertrusttheinput.

Evenifyousanitizeandmakesurethatpeoplecan'tenterweirdthingsusingclient-sidecode,you'llstillbesubjecttopeopleusingtools(evenjustthebrowserdevtools)toPOSTdirectlytoyourendpoints.

Orbotstryingeverypossiblecombinationofexploitknowntohumans.

Whatyouneedtodoissanitizingyourinput.

The express-validatorpackageyoualreadyusetovalidateinputcanalsoconvenientlyusedtoperformsanitization.

SayyouhaveaPOSTendpointthatacceptsthename,emailandageparameters:

constexpress=require('express')

constapp=express()

app.use(express.json())

app.post('/form',(req,res)=>{

constname=req.body.name

constemail=req.body.email

constage=req.body.age

})

Youmightvalidateitusing:

constexpress=require('express')

constapp=express()

app.use(express.json())

app.post('/form',[

check('name').isLength({min:3}),

check('email').isEmail(),

check('age').isNumeric()

],(req,res)=>{

constname=req.body.name

constemail=req.body.email

constage=req.body.age

})

Youcanaddsanitizationbypipingthesanitizationmethodsafterthevalidationones:

Sanitizinginput

45

app.post('/form',[

check('name').isLength({min:3}).trim().escape(),

check('email').isEmail().normalizeEmail(),

check('age').isNumeric().trim().escape()

],(req,res)=>{

//...

})

HereIusedthemethods:

trim()trimscharacters(whitespacebydefault)atthebeginningandattheendofastringescape()replaces <, >, &, ', "and /withtheircorrespondingHTMLentitiesnormalizeEmail()canonicalizesanemailaddress.Acceptsseveraloptionstolowercaseemailaddressesorsubaddresses(e.g. flavio+newsletters@gmail.com)

Othersanitizationmethods:

blacklist()removecharactersthatappearintheblacklistwhitelist()removecharactersthatdonotappearinthewhitelistunescape()replacesHTMLencodedentitieswith <, >, &, ', "and /ltrim()liketrim(),butonlytrimscharactersatthestartofthestringrtrim()liketrim(),butonlytrimscharactersattheendofthestringstripLow()removeASCIIcontrolcharacters,whicharenormallyinvisible

Forceconversiontoaformat:

toBoolean()converttheinputstringtoaboolean.Everythingexceptfor'0','false'and''returnstrue.Instrictmodeonly'1'and'true'returntruetoDate()converttheinputstringtoadate,ornulliftheinputisnotadatetoFloat()converttheinputstringtoafloat,orNaNiftheinputisnotafloattoInt()converttheinputstringtoaninteger,orNaNiftheinputisnotaninteger

Likewithcustomvalidators,youcancreateacustomsanitizer.

Inthecallbackfunctionyoujustreturnthesanitizedvalue:

constsanitizeValue=value=>{

//sanitize...

}

app.post('/form',[

check('value').customSanitizer(value=>{

returnsanitizeValue(value)

}),

],(req,res)=>{

constvalue=req.body.value

})

Sanitizinginput

46

Sanitizinginput

47

HandlingformsHowtoprocessformsusingExpress

ThisisanexampleofanHTMLform:

<formmethod="POST"action="/submit-form">

<inputtype="text"name="username"/>

<inputtype="submit"/>

</form>

Whentheuserpressthesubmitbutton,thebrowserwillautomaticallymakea POSTrequesttothe /submit-formURLonthesameoriginofthepage,sendingthedataitcontains,encodedas application/x-www-form-urlencoded.Inthiscase,theformdatacontainstheusernameinputfieldvalue.

Formscanalsosenddatausingthe GETmethod,butthevastmajorityoftheformsyou'llbuildwilluse POST.

TheformdatawillbesentinthePOSTrequestbody.

Toextractit,youwillusethe express.urlencoded()middleware,providedbyExpress:

constexpress=require('express')

constapp=express()

app.use(express.urlencoded())

Nowyouneedtocreatea POSTendpointonthe /submit-formroute,andanydatawillbeavailableon Request.body:

app.post('/submit-form',(req,res)=>{

constusername=req.body.username

//...

res.end()

})

Don'tforgettovalidatethedatabeforeusingit,using express-validator.

Handlingforms

48

FileuploadsinformsHowtomanagestoringandhandlingfilesuploadedviaforms,inExpress

ThisisanexampleofanHTMLformthatallowsausertouploadafile:

<formmethod="POST"action="/submit-form">

<inputtype="file"name="document"/>

<inputtype="submit"/>

</form>

Whentheuserpressthesubmitbutton,thebrowserwillautomaticallymakea POSTrequesttothe /submit-formURLonthesameoriginofthepage,sendingthedataitcontains,notencodedas application/x-www-form-urlencodedasanormalform,butas multipart/form-data.

Server-side,handlingmultipartdatacanbetrickyanderrorprone,sowearegoingtouseautilitylibrarycalledformidable.Here'stheGitHubrepo,ithasover4000starsandwellmaintained.

Youcaninstallitusing:

npminstallformidable

TheninyourNode.jsfile,includeit:

constexpress=require('express')

constapp=express()

constformidable=require('formidable')

Nowinthe POSTendpointonthe /submit-formroute,weinstantiateanewFormidableformusing formidable.IncomingFrom():

app.post('/submit-form',(req,res)=>{

newformidable.IncomingFrom()

})

Afterdoingso,weneedtoparsetheform.Wecandososynchronouslybyprovidingacallback,whichmeansallfilesareprocessed,andonceformidableisdone,itmakesthemavailable:

app.post('/submit-form',(req,res)=>{

newformidable.IncomingFrom().parse(req,(err,fields,files)=>{

if(err){

Fileuploadsinforms

49

console.error('Error',err)

throwerr

}

console.log('Fields',fields)

console.log('Files',files)

files.map(file=>{

console.log(file)

})

})

})

Oryoucanuseeventsinsteadofacallback,tobenotifiedwheneachfileisparsed,andotherevents,likeendingprocessing,receivinganon-filefield,oranerroroccurred:

app.post('/submit-form',(req,res)=>{

newformidable.IncomingFrom().parse(req)

.on('field',(name,field)=>{

console.log('Field',name,field)

})

.on('file',(name,file)=>{

console.log('Uploadedfile',name,file)

})

.on('aborted',()=>{

console.error('Requestabortedbytheuser')

})

.on('error',(err)=>{

console.error('Error',err)

throwerr

})

.on('end',()=>{

res.end()

})

})

Whateverwayyouchoose,you'llgetoneormoreFormidable.Fileobjects,whichgiveyouinformationaboutthefileuploaded.Thesearesomeofthemethodsyoucancall:

file.size,thefilesizeinbytesfile.path,thepaththisfileiswrittentofile.name,thenameofthefilefile.type,theMIMEtypeofthefile

Thepathdefaultstothetemporaryfolderandcanbemodifiedifyoulistentothe fileBeginevent:

app.post('/submit-form',(req,res)=>{

newformidable.IncomingFrom().parse(req)

.on('fileBegin',(name,file)=>{

form.on('fileBegin',(name,file)=>{

file.path=__dirname+'/uploads/'+file.name

})

Fileuploadsinforms

50

})

.on('file',(name,file)=>{

console.log('Uploadedfile',name,file)

})

//...

})

Fileuploadsinforms

51

AnExpressHTTPSserverwithaself-signedcertificateHowtocreateaself-signedHTTPScertificateforNode.jstotestappslocally

TobeabletoserveasiteonHTTPSfromlocalhostyouneedtocreateaself-signedcertificate.

Aself-signedcertificatewillbeenoughtoestablishasecureHTTPSconnection,althoughbrowserswillcomplainthatthecertificateisself-signedandassuchit'snottrusted.It'sgreatfordevelopmentpurposes.

TocreatethecertificateyoumusthaveOpenSSLinstalledonyoursystem.

Youmighthaveitinstalledalready,justtestbytyping opensslinyourterminal.

Ifnot,onaMacyoucaninstallitusing brewinstallopensslifyouuseHomebrew.OtherwisesearchonGoogle"howtoinstallopensslon".

OnceOpenSSLisinstalled,runthiscommand:

opensslreq-nodes-new-x509-keyoutserver.key-outserver.cert

Itwillasyouafewquestions.Thefirstisthecountryname:

Generatinga1024bitRSAprivatekey

...........++++++

.........++++++

writingnewprivatekeyto'server.key'

-----

Youareabouttobeaskedtoenterinformationthatwillbeincorporatedintoyourcertifi

caterequest.

WhatyouareabouttoenteriswhatiscalledaDistinguishedNameoraDN.

Therearequiteafewfieldsbutyoucanleavesomeblank

Forsomefieldstherewillbeadefaultvalue,

Ifyouenter'.',thefieldwillbeleftblank.

-----

CountryName(2lettercode)[AU]:

Thenyourstateorprovince:

StateorProvinceName(fullname)[Some-State]:

yourcity:

AnExpressHTTPSserverwithaself-signedcertificate

52

LocalityName(eg,city)[]:

andyourorganizationname:

OrganizationName(eg,company)[InternetWidgitsPtyLtd]:

OrganizationalUnitName(eg,section)[]:

Youcanleavealloftheseempty.

Justremembertosetthisto localhost:

CommonName(e.g.serverFQDNorYOURname)[]:localhost

andtoaddyouremailaddress:

EmailAddress[]:

That'sit!Nowyouhave2filesinthefolderwhereyouranthiscommand:

server.certistheself-signedcertificatefileserver.keyistheprivatekeyofthecertificate

BothfileswillbeneededtoestablishtheHTTPSconnection,anddependingonhowyouaregoingtosetupyourserver,theprocesstousethemwillbedifferent.

Thosefilesneedtobeputinaplacereachablebytheapplication,thenyouneedtoconfiguretheservertousethem.

Thisisanexampleusingthe httpscoremoduleandExpress:

consthttps=require('https')

constapp=express()

app.get('/',(req,res)=>{

res.send('HelloHTTPS!')

})

https.createServer({},app).listen(3000,()=>{

console.log('Listening...')

})

withoutaddingthecertificate,ifIconnectto https://localhost:3000thisiswhatthebrowserwillshow:

AnExpressHTTPSserverwithaself-signedcertificate

53

Withthecertificateinplace:

constfs=require('fs')

//...

https.createServer({

key:fs.readFileSync('server.key'),

cert:fs.readFileSync('server.cert')

},app).listen(3000,()=>{

console.log('Listening...')

})

Chromewilltellusthecertificateisinvalid,sinceit'sself-signed,andwillaskustoconfirmtocontinue,buttheHTTPSconnectionwillwork:

AnExpressHTTPSserverwithaself-signedcertificate

54

AnExpressHTTPSserverwithaself-signedcertificate

55

SetupLet'sEncryptforExpressHowtosetupHTTPSusingthepopularfreesolutionLet'sEncrypt

IfyourunaNode.jsapplicationonyourownVPS,youneedtomanagegettinganSSLcertificate.

TodaythestandardfordoingthisistouseLet'sEncryptandCertbot,atoolfromEFF,akaElectronicFrontierFoundation,theleadingnonprofitorganizationfocusedonprivacy,freespeech,andingeneralcivillibertiesinthedigitalworld.

Thesearethestepswe'llfollow:

InstallCertbotGeneratetheSSLcertificateusingCertbotAllowExpresstoservestaticfilesConfirmthedomainObtainthecertificateSetuptherenewal

InstallCertbotThoseinstructionsassumeyouareusingUbuntu,DebianoranyotherLinuxdistributionthatuses apt-get:

sudoadd-aptrepositoryppa:certbot/certbot

sudoapt-getupdate

sudoapt-getinstallcertbot

YoucanalsoinstallCertbotonaMactotest:

brewinstallcertbot

butyouwillneedtolinkthattoarealdomainname,inorderforittobeuseful.

GeneratetheSSLcertificateusingCertbotNowthatCertbotisinstalled,youcaninvokeittogeneratethecertificate.Youmustrunthisasroot:

SetupLet'sEncryptforExpress

56

certbotcertonly--manual

orcallsudo

sudocertbotcertonly--manual

Theinstallerwillaskyouthedomainofyourwebsite.

Thisistheprocessindetail.

Itasksfortheemail

➜sudocertbotcertonly--manualPassword:XXXXXXXXXXXXXXXXXX

Savingdebuglogto/var/log/letsencrypt/letsencrypt.log

Pluginsselected:Authenticatormanual,InstallerNone

Enteremailaddress(usedforurgentrenewalandsecuritynotices)(Enter'c'to

cancel):flavio@flaviocopes.com

ItaskstoaccepttheToS:

PleasereadtheTermsofServiceat

https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf.Youmust

agreeinordertoregisterwiththeACMEserverat

https://acme-v02.api.letsencrypt.org/directory

(A)gree/(C)ancel:A

Itaskstosharetheemailaddress

WouldyoubewillingtoshareyouremailaddresswiththeElectronicFrontier

Foundation,afoundingpartneroftheLet'sEncryptprojectandthenon-profit

organizationthatdevelopsCertbot?We'dliketosendyouemailaboutourwork

encryptingtheweb,EFFnews,campaigns,andwaystosupportdigitalfreedom.

----------------------------------------

(Y)es/(N)o:Y

AndfinallywecanenterthedomainwherewewanttousetheSSLcertificate:

Pleaseenterinyourdomainname(s)(commaand/orspaceseparated)(Enter'c'

tocancel):copesflavio.com

Itasksifit'soktologyourIP:

Obtaininganewcertificate

Performingthefollowingchallenges:

SetupLet'sEncryptforExpress

57

http-01challengeforcopesflavio.com

----------------------------------------

NOTE:TheIPofthismachinewillbepubliclyloggedashavingrequestedthis

certificate.Ifyou'rerunningcertbotinmanualmodeonamachinethatisnot

yourserver,pleaseensureyou'reokaywiththat.

AreyouOKwithyourIPbeinglogged?

----------------------------------------

(Y)es/(N)o:y

Andfinallywegettotheverificationphase!

----------------------------------------

Createafilecontainingjustthisdata:

TS_oZ2-ji23jrio3j2irj3iroj_U51u1o0x7rrDY2E.1DzOo_voCOsrpddP_2kpoek2opeko2pke-UAPb21sW1c

AndmakeitavailableonyourwebserveratthisURL:

http://copesflavio.com/.well-known/acme-challenge/TS_oZ2-ji23jrio3j2irj3iroj_U51u1o0x7rrDY

2E

Nowlet'sleaveCertbotaloneforacoupleminutes.

Weneedtoverifyweownthedomain,bycreatingafilenamed TS_oZ2-ji23jrio3j2irj3iroj_U51u1o0x7rrDY2Einthe .well-known/acme-challenge/folder.Payattention!TheweirdstringIjustpastedchangeeverysingletime.

You'llneedtocreatethefolderandthefile,sincetheydonotexistbydefault.

InthisfileyouneedtoputthecontentthatCertbotprinted:

TS_oZ2-ji23jrio3j2irj3iroj_U51u1o0x7rrDY2E.1DzOo_voCOsrpddP_2kpoek2opeko2pke-UAPb21sW1c

Asforthefilename,thisstringisuniqueeachtimeyourunCertbot.

AllowExpresstoservestaticfilesInordertoservethatfilefromExpress,youneedtoenableservingstaticfiles.Youcancreatea staticfolder,andaddtherethe .well-knownsubfolder,thenconfigureExpresslikethis:

constexpress=require('express')

constapp=express()

//...

SetupLet'sEncryptforExpress

58

app.use(express.static(__dirname+'/static',{dotfiles:'allow'}))

//...

The dotfilesoptionismandatoryotherwise .well-known,whichisadotfileasitstartswithadot,won'tbemadevisible.Thisisasecuritymeasure,becausedotfilescancontainsensitiveinformationandtheyarebetteroffpreservedbydefault.

ConfirmthedomainNowruntheapplicationandmakesurethefileisreachablefromthepublicinternet,andgobacktoCertbot,whichisstillrunning,andpressENTERtogoonwiththescript.

ObtainthecertificateThat'sit!Ifallwentwell,Certbotcreatedthecertificate,andtheprivatekey,andmadethemavailableinafolderonyourcomputer(anditwilltellyouwhichfolder,ofcourse).

Nowcopy/pastethepathsintoyourapplication,tostartusingthemtoserveyourrequests:

constfs=require('fs')

consthttps=require('https')

constapp=express()

app.get('/',(req,res)=>{

res.send('HelloHTTPS!')

})

https.createServer({

key:fs.readFileSync('/etc/letsencrypt/path/to/key.pem'),

cert:fs.readFileSync('/etc/letsencrypt/path/to/cert.pem'),

ca:fs.readFileSync('/etc/letsencrypt/path/to/chain.pem')

},app).listen(443,()=>{

console.log('Listening...')

})

NotethatImadethisserverlistenonport443,soyouneedtorunitwithrootpermissions.

Also,theserverisexclusivelyrunninginHTTPS,becauseIused https.createServer().YoucanalsorunanHTTPserveralongsidethis,byrunning:

http.createServer(app).listen(80,()=>{

console.log('Listening...')

})

https.createServer({

SetupLet'sEncryptforExpress

59

key:fs.readFileSync('/etc/letsencrypt/path/to/key.pem'),

cert:fs.readFileSync('/etc/letsencrypt/path/to/cert.pem'),

ca:fs.readFileSync('/etc/letsencrypt/path/to/chain.pem')

},app).listen(443,()=>{

console.log('Listening...')

})

SetuptherenewalTheSSLcertificateisnotgoingtobevalidfor90days.Youneedtosetupanautomatedsystemforrenewingit.

How?Usingacronjob.

Acronjobisawaytoruntaskseveryintervaloftime.Itcanbeeeryweek,everyminute,everymonth.

Inourcasewe'llruntherenewalscripttwiceperday,asrecommendedintheCertbotdocumentation.

Firstfindouttheabsolutepathof certbotonyousystem.Iuse typecertbotonmacOStogetit,andinmycaseit's /usr/local/bin/certbot.

Here'sthescriptweneedtorun:

certbotrenew

Thisisthecronjobentry:

0*/12***root/usr/local/bin/certbotrenew>/dev/null2>&1

Itmeansrunitevery12hours,everyday:at00:00andat12:00.

Tip:Igeneratedthislineusinghttps://crontab-generator.org/

Addthisscripttoyourcrontab,byusingthecommand:

envEDITOR=picocrontab-e

Thisopensthe picoeditor(youcanchoosetheoneyouprefer).Youentertheline,save,andthecronjobisinstalled.

Oncethisisdone,youcanseethelistofcronjobsactiveusing

crontab-l

SetupLet'sEncryptforExpress

60

SetupLet'sEncryptforExpress

61

Recommended