+ All Categories
Home > Documents > Building Web Apps With Go

Building Web Apps With Go

Date post: 27-Jan-2016
Category:
Upload: odgiiv-kh
View: 12 times
Download: 4 times
Share this document with a friend
Description:
Introduction to build web applications with Go programming language.
Popular Tags:
39
Transcript
Page 1: Building Web Apps With Go
Page 2: Building Web Apps With Go

0

1

2

3

4

5

6

7

7.1

7.2

7.3

8

8.1

8.2

9

10

11

12

TableofContentsIntroduction

GoMakesThingsSimple

Thenet/httppackage

CreatingaBasicWebApp

Deployment

URLRouting

Middleware

Rendering

JSON

HTMLTemplates

UsingTherenderpackage

Testing

UnitTesting

EndtoEndTesting

Controllers

Databases

TipsandTricks

MovingForward

BuildingWebAppswithGo

2

Page 3: Building Web Apps With Go

IntroductionWelcometoBuildingWebAppswithGo!Ifyouarereadingthisthenyouhavejuststartedyourjourneyfromnoobtopro.Noseriously,webprogramminginGoissofunandeasythatyouwon'tevennoticehowmuchinformationyouarelearningalongtheway!

Keepinmindthattherearestillportionsofthisbookthatareincompleteandneedsomelove.ThebeautyofopensourcepublishingisthatIcangiveyouanincompletebookanditisstillofvaluetoyou.

Beforewegetintoallthenittygrittydetails,let'sstartwithsomegroundrules:

PrerequisitesTokeepthistutorialsmallandfocused,I'massumingthatyouarepreparedinthefollowingways:

1. YouhaveinstalledtheGoProgrammingLanguage.2. YouhavesetupaGOPATHbyfollowingtheHowtoWriteGoCodetutorial.3. YouaresomewhatfamiliarwiththebasicsofGo.(TheGoTourisaprettygoodplaceto

start)4. Youhaveinstalledalltherequiredpackages5. YouhaveinstalledtheHerokuToolbelt6. YouhaveaHerokuaccount

RequiredPackagesForthemostpartwewillbeusingthebuiltinpackagesfromthestandardlibrarytobuildoutourwebapps.CertainlessonssuchasDatabases,MiddlewareandURLRoutingwillrequireathirdpartypackage.Hereisalistofallthegopackagesyouwillneedtoinstallbeforestarting:

BuildingWebAppswithGo

3Introduction

Page 4: Building Web Apps With Go

Name ImportPath Description

httprouter github.com/julienschmidt/httprouter AhighperformanceHTTPrequestrouterthatscaleswell

Negroni github.com/codegangsta/negroni IdiomaticHTTPMiddleware

BlackFriday github.com/russross/blackfriday amarkdownprocessor

Render gopkg.in/unrolled/render.v1 EasyrenderingforJSON,XML,andHTML

SQLite3 github.com/mattn/go-sqlite3 sqlite3driverforgo

Youcaninstall(orupdate)thesepackagesbyrunningthefollowingcommandinyourconsole

goget-u<import_path>

Forinstance,ifyouwishtoinstallNegroni,thefollowingcommandwouldbe:

goget-ugithub.com/codegangsta/negroni

BuildingWebAppswithGo

4Introduction

Page 5: Building Web Apps With Go

GoMakesThingsSimpleIfyouhavebuiltawebapplicationbefore,yousurelyknowthattherearequitealotofconceptstokeepinyourhead.HTTP,HTML,CSS,JSON,databases,sessions,cookies,forms,middleware,routingandcontrollersarejustafewamongthemanythingsyourwebappmayneedtointeractwith.

Whileeachoneofthesethingscanbeimportantinthebuildingofyourwebapplications,noteveryoneofthemisimportantforanygivenapp.Forinstance,awebAPImayjustuseJSONasitsserializationformat,thusmakingconceptslikeHTMLnotrelevantforthatparticularwebapp.

TheGoWayTheGocommunityunderstandsthisdilemma.Ratherthanrelyonlarge,heavyweightframeworksthattrytocoverallthebases,Goprogrammerspullinthebarenecessitiestogetthejobdone.Thisminimalistapproachtowebprogrammingmaybeoff-puttingatfirst,buttheresultofthiseffortisamuchsimplerprogramintheend.

Gomakesthingssimple,it'saseasyasthat.Ifwetrainourselvestoalignwiththe"Goway"ofprogrammingfortheweb,wewillendupwithmoresimple,flexible,andmaintainablewebapplications.

PowerinSimplicityAswegothroughtheexercisesinthisbook,Ithinkyouwillbesurprisedbyhowsimplesomeoftheseprogramscanbewhilststillaffordingabunchoffunctionality.

WhensittingdowntocraftyourownwebapplicationsinGo,thinkhardaboutthecomponentsandconceptsthatyourappwillbefocusedon,andusejustthosepieces.Thisbookwillbecoveringawidearrayofwebtopics,butdonotfeelobligatedtousethemall.InthewordsofourfriendLonestar,"Takeonlywhatyouneedtosurvive".

BuildingWebAppswithGo

5GoMakesThingsSimple

Page 6: Building Web Apps With Go

BuildingWebAppswithGo

6GoMakesThingsSimple

Page 7: Building Web Apps With Go

Thenet/httpPackageYouhaveprobablyheardthatGoisfantasticforbuildingwebapplicationsofallshapesandsizes.Thisispartlyduetothefantasticworkthathasbeenputintomakingthestandardlibraryclean,consistent,andeasytouse.

PerhapsoneofthemostimportantpackagesforanybuddingGowebdeveloperisthenet/httppackage.ThispackageallowsyoutobuildHTTPserversinGowithitspowerfulcompositionalconstructs.Beforewestartcoding,let'sdoanextremelyquickoverviewofHTTP.

HTTPBasicsWhenwetalkaboutbuildingwebapplications,weusuallymeanthatwearebuildingHTTPservers.HTTPisaprotocolthatwasoriginallydesignedtotransportHTMLdocumentsfromaservertoaclientwebbrowser.Today,HTTPisusedtotransportawholelotmorethanHTML.

TheimportantthingtonoticeinthisdiagramisthetwopointsofinteractionbetweentheServerandtheBrowser.TheBrowsermakesanHTTPrequestwithsomeinformation,theServerthenprocessesthatrequestandreturnsaResponse.

BuildingWebAppswithGo

7Thenet/httppackage

Page 8: Building Web Apps With Go

Thispatternofrequest-responseisoneofthekeyfocalpointsinbuildingwebapplicationsinGo.Infact,thenet/httppackage'smostimportantpieceisthehttp.HandlerInterface.

Thehttp.HandlerInterfaceAsyoubecomemorefamiliarwithGo,youwillnoticehowmuchofanimpactinterfacesmakeinthedesignofyourprograms.Thenet/httpinterfaceencapsulatestherequest-responsepatterninonemethod:

typeHandlerinterface{

ServeHTTP(ResponseWriter,*Request)

}

Implementorsofthisinterfaceareexpectedtoinspectandprocessdatacomingfromthehttp.Requestobjectandwriteoutaresponsetothehttp.ResponseWriterobject.

Thehttp.ResponseWriterinterfacelookslikethis:

typeResponseWriterinterface{

Header()Header

Write([]byte)(int,error)

WriteHeader(int)

}

ComposingWebServicesBecausemuchofthenet/httppackageisbuiltoffofwelldefinedinterfacetypes,wecan(andareexpectedto)buildourwebapplicationswithcompositioninmind.Eachhttp.Handlerimplementationcanbethoughtofasitsownwebserver.

Manypatternscanbefoundinthatsimplebutpowerfulassumption.Throughoutthisbookwewillcoversomeofthesepatternsandhowwecanusethemtosolverealworldproblems.

Exercise:1LineFileServerLet'ssolvearealworldproblemin1lineofcode.

BuildingWebAppswithGo

8Thenet/httppackage

Page 9: Building Web Apps With Go

Mostofthetimepeoplejustneedtoservestaticfiles.MaybeyouhaveastaticHTMLlandingpageandjustwanttoserveupsomeHTML,images,andCSSandcallitaday.Sure,youcouldpullinApacheorPython'sSimpleHTTPServer,butApacheistoomuchforthislittlesiteandSimpleHTTPServeris,well,tooslow.

WewillbeginbycreatinganewprojectinourGOPATH.

cdGOPATH/src

mkdirfileserver&&cdfileserver

Createamain.gowithourtypicalgoboilerplate.

packagemain

import"net/http"

funcmain(){

}

Allweneedtoimportisthenet/httppackageforthistowork.RememberthatthisisallpartofthestandardlibraryinGo.

Let'swriteourfileservercode:

http.ListenAndServe(":8080",http.FileServer(http.Dir(".")))

Thehttp.ListenAndServefunctionisusedtostarttheserver,itwillbindtotheaddresswegaveit(:8080)andwhenitreceivesanHTTPrequest,itwillhanditofftothehttp.Handlerthatwesupplyasthesecondargument.Inourcaseitisthebuilt-inhttp.FileServer.

Thehttp.FileServerfunctionbuildsanhttp.Handlerthatwillserveanentiredirectoryoffilesandfigureoutwhichfiletoservebasedontherequestpath.WetoldtheFileServertoservethecurrentworkingdirectorywithhttp.Dir(".").

Theentireprogramlookslikethis:

packagemain

import"net/http"

funcmain(){

http.ListenAndServe(":8080",http.FileServer(http.Dir(".")))

}

BuildingWebAppswithGo

9Thenet/httppackage

Page 10: Building Web Apps With Go

Let'sbuildandrunourfileserverprogram:

gobuild

./fileserver

Ifwevisitlocalhost:8080/main.goweshouldseethecontentsofourmain.gofileinourwebbrowser.Wecanrunthisprogramfromanydirectoryandservethetreeasastaticfileserver.Allin1lineofGocode.

BuildingWebAppswithGo

10Thenet/httppackage

Page 11: Building Web Apps With Go

CreatingaBasicWebAppNowthatwearedonegoingoverthebasicsofHTTP,let'screateasimplebutusefulwebapplicationinGo.

Pullingfromourfileserverprogramthatweimplementedlastchapter,wewillimplementaMarkdowngeneratorusingthegithub.com/russross/blackfridaypackage.

HTMLFormForstarters,wewillneedabasicHTMLformforthemarkdowninput:

<html>

<head>

<linkhref="/css/bootstrap.min.css"rel="stylesheet">

</head>

<body>

<divclass="container">

<divclass="page-title">

<h1>MarkdownGenerator</h1>

<pclass="lead">GenerateyourmarkdownwithGo</p>

<hr/>

</div>

<formaction="/markdown"method="POST">

<divclass="form-group">

<textareaclass="form-control"name="body"cols="30"rows="10"></textarea>

</div>

<divclass="form-group">

<inputtype="submit"class="btnbtn-primarypull-right"/>

</div>

</form>

</div>

<scriptsrc="/js/bootstrap.min.js"></script>

</body>

</html>

PutthisHTMLintoafilenamedindex.htmlinthe"public"folderofourapplicationandthebootstrap.min.cssfromhttp://getbootstrap.com/inthe"public/css"folder.NoticethattheformmakesanHTTPPOSTtothe"/markdown"endpointofourapplication.Wedon'tactuallyhandlethatrouterightnow,solet'saddit.

BuildingWebAppswithGo

11CreatingaBasicWebApp

Page 12: Building Web Apps With Go

The"/markdown"routeTheprogramtohandlethe'/markdown'routeandservethepublicindex.htmlfilelookslikethis:

packagemain

import(

"net/http"

"github.com/russross/blackfriday"

)

funcmain(){

http.HandleFunc("/markdown",GenerateMarkdown)

http.Handle("/",http.FileServer(http.Dir("public")))

http.ListenAndServe(":8080",nil)

}

funcGenerateMarkdown(rwhttp.ResponseWriter,r*http.Request){

markdown:=blackfriday.MarkdownCommon([]byte(r.FormValue("body")))

rw.Write(markdown)

}

Let'sbreakitdownintosmallerpiecestogetabetterideaofwhatisgoingon.

http.HandleFunc("/markdown",GenerateMarkdown)

http.Handle("/",http.FileServer(http.Dir("public")))

Weareusingthehttp.HandleFuncandhttp.Handlemethodstodefinesomesimpleroutingforourapplication.Itisimportanttonotethatcallinghttp.Handleonthe"/"patternwillactasacatch-allroute,sowedefinethatroutelast.http.FileServerreturnsanhttp.Handlersoweusehttp.Handletomapapatternstringtoahandler.Thealternativemethod,http.HandleFunc,usesanhttp.HandlerFuncinsteadofanhttp.Handler.Thismaybemoreconvenient,tothinkofhandlingroutesviaafunctioninsteadofanobject.

funcGenerateMarkdown(rwhttp.ResponseWriter,r*http.Request){

markdown:=blackfriday.MarkdownCommon([]byte(r.FormValue("body")))

rw.Write(markdown)

}

OurGenerateMarkdownfunctionimplementsthestandardhttp.HandlerFuncinterfaceandrendersHTMLfromaformfieldcontainingmarkdown-formattedtext.Inthiscase,thecontentisretrievedwithr.FormValue("body").Itisverycommontogetinputfromthe

BuildingWebAppswithGo

12CreatingaBasicWebApp

Page 13: Building Web Apps With Go

http.Requestobjectthatthehttp.HandlerFuncreceivesasanargument.Someotherexamplesofinputarether.Header,r.Body,andr.URLmembers.

Wefinalizetherequestbywritingitouttoourhttp.ResponseWriter.Noticethatwedidn'texplicitlysendaresponsecode.Ifwewriteouttotheresponsewithoutacode,thenet/httppackagewillassumethattheresponseisa200OK.Thismeansthatifsomethingdidhappentogowrong,weshouldsettheresponsecodeviatherw.WriteHeader()method.

http.ListenAndServe(":8080",nil)

Thelastbitofthisprogramstartstheserver,wepassnilasourhandler,whichassumesthattheHTTPrequestswillbehandledbythenet/httppackagesdefaulthttp.ServeMux,whichisconfiguredusinghttp.Handleandhttp.HandleFunc,respectively.

AndthatisallyouneedtobeabletogeneratemarkdownasaserviceinGo.Itisasurprisinglysmallamountofcodefortheamountofheavyliftingitdoes.InthenextchapterwewilllearnhowtodeploythisapplicationtothewebusingHeroku.

BuildingWebAppswithGo

13CreatingaBasicWebApp

Page 14: Building Web Apps With Go

DeploymentHerokumakesdeployingapplicationseasy.Itisaperfectplatformforsmalltomediumsizewebapplicationsthatarewillingtosacrificealittlebitofflexibilityininfrastructuretogainafairlypain-freeenvironmentfordeployingandmaintainingwebapplications.

IamchoosingtodeployourwebapplicationtoHerokuforthesakeofthistutorialbecauseinmyexperienceithasbeenthefastestwaytogetawebapplicationupandrunninginnotime.RememberthatthefocusofthistutorialishowtobuildwebapplicationsinGoandnotgettingcaughtupinallofthedistractionofprovisioning,configuring,deploying,andmaintainingthemachinesthatourGocodewillberunon.

GettingsetupIfyoudon'talreadyhaveaHerokuaccount,signupatid.heroku.com/signup.It'squick,easyandfree.

ApplicationmanagementandconfigurationisdonethroughtheHerokutoolbelt,whichisafreecommandlinetoolmaintainedbyHeroku.WewillbeusingittocreateourapplicationonHeroku.Youcangetitfromtoolbelt.heroku.com.

ChangingtheCodeTomakesuretheapplicationfromourlastchapterwillworkonHeroku,wewillneedtomakeafewchanges.HerokugivesusaPORTenvironmentvariableandexpectsourwebapplicationtobindtoit.Let'sstartbyimportingthe"os"packagesowecangrabthatPORTenvironmentvariable:

import(

"net/http"

"os"

"github.com/russross/blackfriday"

)

Next,weneedtograbthePORTenvironmentvariable,checkifitisset,andifitisweshouldbindtothatinsteadofourhardcodedport(8080).

BuildingWebAppswithGo

14Deployment

Page 15: Building Web Apps With Go

port:=os.Getenv("PORT")

ifport==""{

port="8080"

}

Lastly,wewanttobindtothatportinourhttp.ListenAndServecall:

http.ListenAndServe(":"+port,nil)

Thefinalcodeshouldlooklikethis:

packagemain

import(

"net/http"

"os"

"github.com/russross/blackfriday"

)

funcmain(){

port:=os.Getenv("PORT")

ifport==""{

port="8080"

}

http.HandleFunc("/markdown",GenerateMarkdown)

http.Handle("/",http.FileServer(http.Dir("public")))

http.ListenAndServe(":"+port,nil)

}

funcGenerateMarkdown(rwhttp.ResponseWriter,r*http.Request){

markdown:=blackfriday.MarkdownCommon([]byte(r.FormValue("body")))

rw.Write(markdown)

}

ConfigurationWeneedacouplesmallconfigurationfilestotellHerokuhowitshouldrunourapplication.ThefirstoneistheProcfile,whichallowsustodefinewhichprocessesshouldberunforourapplication.Bydefault,Gowillnametheexecutableafterthecontainingdirectoryofyourmainpackage.Forinstance,ifmywebapplicationlivedinGOPATH/github.com/codegangsta/bwag/deployment,myProcfilewilllooklikethis:

BuildingWebAppswithGo

15Deployment

Page 16: Building Web Apps With Go

web:deployment

SpecificallytorunGoapplications,weneedtoalsospecifya.godirfiletotellHerokuwhichdirisinfactourpackagedirectory.

deployment

DeploymentOnceallthesethingsinplace,Herokumakesiteasytodeploy.

InitializetheprojectasaGitrepository:

gitinit

gitadd-A

gitcommit-m"InitialCommit"

CreateyourHerokuapplication(specifyingtheGobuildpack):

herokucreate-bhttps://github.com/kr/heroku-buildpack-go.git

PushittoHerokuandwatchyourapplicationbedeployed!

gitpushherokumaster

Viewyourapplicationinyourbrowser:

herokuopen

BuildingWebAppswithGo

16Deployment

Page 17: Building Web Apps With Go

URLRoutingForsomesimpleapplications,thedefaulthttp.ServeMuxcantakeyouprettyfar.IfyouneedmorepowerinhowyouparseURLendpointsandroutethemtotheproperhandler,youmayneedtopullinathirdpartyroutingframework.Forthistutorial,wewillusethepopulargithub.com/julienschmidt/httprouterlibraryasourrouter.github.com/julienschmidt/httprouterisagreatchoiceforarouterasitisaverysimpleimplementationwithoneofthebestperformancebenchmarksoutofallthethirdpartyGorouters.

Inthisexample,wewillcreatesomeroutingforaRESTfulresourcecalled"posts".Belowwedefinemechanismstoviewindex,show,create,update,destroy,andeditposts.

packagemain

import(

"fmt"

"net/http"

"github.com/julienschmidt/httprouter"

)

funcmain(){

r:=httprouter.New()

r.GET("/",HomeHandler)

//Postscollection

r.GET("/posts",PostsIndexHandler)

r.POST("/posts",PostsCreateHandler)

//Postssingular

r.GET("/posts/:id",PostShowHandler)

r.PUT("/posts/:id",PostUpdateHandler)

r.GET("/posts/:id/edit",PostEditHandler)

fmt.Println("Startingserveron:8080")

http.ListenAndServe(":8080",r)

}

funcHomeHandler(rwhttp.ResponseWriter,r*http.Request,phttprouter.Params){

fmt.Fprintln(rw,"Home")

}

funcPostsIndexHandler(rwhttp.ResponseWriter,r*http.Request,phttprouter.Params){

fmt.Fprintln(rw,"postsindex")

}

BuildingWebAppswithGo

17URLRouting

Page 18: Building Web Apps With Go

funcPostsCreateHandler(rwhttp.ResponseWriter,r*http.Request,phttprouter.Params){

fmt.Fprintln(rw,"postscreate")

}

funcPostShowHandler(rwhttp.ResponseWriter,r*http.Request,phttprouter.Params){

id:=p.ByName("id")

fmt.Fprintln(rw,"showingpost",id)

}

funcPostUpdateHandler(rwhttp.ResponseWriter,r*http.Request,phttprouter.Params){

fmt.Fprintln(rw,"postupdate")

}

funcPostDeleteHandler(rwhttp.ResponseWriter,r*http.Request,phttprouter.Params){

fmt.Fprintln(rw,"postdelete")

}

funcPostEditHandler(rwhttp.ResponseWriter,r*http.Request,phttprouter.Params){

fmt.Fprintln(rw,"postedit")

}

Exercises1. Explorethedocumentationforgithub.com/julienschmidt/httprouter.2. Findouthowwellgithub.com/julienschmidt/httprouterplaysnicelywithexisting

http.Handlerslikehttp.FileServer3. httprouterhasaverysimpleinterface.Explorewhatkindofabstractionscanbebuilt

ontopofthisfastroutertomakebuildingthingslikeRESTfulroutingeasier.

BuildingWebAppswithGo

18URLRouting

Page 19: Building Web Apps With Go

MiddlewareIfyouhavesomecodethatneedstoberunforeveryrequest,regardlessoftheroutethatitwilleventuallyendupinvoking,youneedsomewaytostackhttp.Handlersontopofeachotherandruntheminsequence.Thisproblemissolvedelegantlythroughmiddlewarepackages.NegroniisapopularmiddlewarepackagethatmakesbuildingandstackingmiddlewareveryeasywhilekeepingthecomposablenatureoftheGowebecosystemintact.

NegronicomeswithsomedefaultmiddlewaresuchasLogging,ErrorRecovery,andStaticfileserving.SooutoftheboxNegroniwillprovideyouwithalotofvaluewithoutalotofoverhead.

TheexamplebelowshowshowtouseaNegronistackwiththebuiltinmiddlewareandhowtocreateyourowncustommiddleware.

BuildingWebAppswithGo

19Middleware

Page 20: Building Web Apps With Go

packagemain

import(

"log"

"net/http"

"github.com/codegangsta/negroni"

)

funcmain(){

//Middlewarestack

n:=negroni.New(

negroni.NewRecovery(),

negroni.HandlerFunc(MyMiddleware),

negroni.NewLogger(),

negroni.NewStatic(http.Dir("public")),

)

n.Run(":8080")

}

funcMyMiddleware(rwhttp.ResponseWriter,r*http.Request,nexthttp.HandlerFunc){

log.Println("Loggingonthewaythere...")

ifr.URL.Query().Get("password")=="secret123"{

next(rw,r)

}else{

http.Error(rw,"NotAuthorized",401)

}

log.Println("Loggingonthewayback...")

}

Exercises1. ThinkofsomecoolmiddlewareideasandtrytoimplementthemusingNegroni.2. ExplorehowNegronicanbecomposedwithgithub.com/gorilla/muxusingthe

http.Handlerinterface.3. PlaywithcreatingNegronistacksforcertaingroupsofroutesinsteadoftheentire

application.

BuildingWebAppswithGo

20Middleware

Page 21: Building Web Apps With Go

RenderingRenderingistheprocessoftakingdatafromyourapplicationordatabaseandpresentingitfortheclient.TheclientcanbeabrowserthatrendersHTML,oritcanbeanotherapplicationthatconsumesJSONasitsserializationformat.InthischapterwewilllearnhowtorenderbothoftheseformatsusingthemethodsthatGoprovidesforusinthestandardlibrary.

BuildingWebAppswithGo

21Rendering

Page 22: Building Web Apps With Go

JSONJSONisquicklybecomingtheubiquitousserializationformatforwebAPIs,soitmaybethemostrelevantwhenlearninghowtobuildwebappsusingGo.Fortunately,GomakesitsimpletoworkwithJSON--itisextremelyeasytoturnexistingGostructsintoJSONusingtheencoding/jsonpackagefromthestandardlibrary.

packagemain

import(

"encoding/json"

"net/http"

)

typeBookstruct{

Titlestring`json:"title"`

Authorstring`json:"author"`

}

funcmain(){

http.HandleFunc("/",ShowBooks)

http.ListenAndServe(":8080",nil)

}

funcShowBooks(whttp.ResponseWriter,r*http.Request){

book:=Book{"BuildingWebAppswithGo","JeremySaenz"}

js,err:=json.Marshal(book)

iferr!=nil{

http.Error(w,err.Error(),http.StatusInternalServerError)

return

}

w.Header().Set("Content-Type","application/json")

w.Write(js)

}

Exercises1. ReadthroughtheJSONAPIdocsandfindouthowtorenameandignorefieldsfor

JSONserialization.2. Insteadofusingthejson.Marshalmethod,tryusingthejson.EncoderAPI.3. FigureourhowtoprettyprintJSONwiththeencoding/jsonpackage.

BuildingWebAppswithGo

22JSON

Page 23: Building Web Apps With Go

HTMLTemplatesServingHTMLisanimportantjobforsomewebapplications.Gohasoneofmyfavoritetemplatinglanguagestodate.Notforitsfeatures,butforitssimplicityandoutoftheboxsecurity.RenderingHTMLtemplatesisalmostaseasyasrenderingJSONusingthe'html/template'packagefromthestandardlibrary.HereiswhatthesourcecodeforrenderingHTMLtemplateslookslike:

packagemain

import(

"html/template"

"net/http"

"path"

)

typeBookstruct{

Titlestring

Authorstring

}

funcmain(){

http.HandleFunc("/",ShowBooks)

http.ListenAndServe(":8080",nil)

}

funcShowBooks(whttp.ResponseWriter,r*http.Request){

book:=Book{"BuildingWebAppswithGo","JeremySaenz"}

fp:=path.Join("templates","index.html")

tmpl,err:=template.ParseFiles(fp)

iferr!=nil{

http.Error(w,err.Error(),http.StatusInternalServerError)

return

}

iferr:=tmpl.Execute(w,book);err!=nil{

http.Error(w,err.Error(),http.StatusInternalServerError)

}

}

Thisisthefollowingtemplatewewillbeusing.Itshouldbeplacedinatemplates/index.htmlfileinthedirectoryyourprogramisrunfrom:

BuildingWebAppswithGo

23HTMLTemplates

Page 24: Building Web Apps With Go

<html>

<h1>{{.Title}}</h1>

<h3>by{{.Author}}</h3>

</html>

Exercises1. Lookthroughthedocsfortext/templateandhtml/templatepackage.Playwiththe

templatinglanguageabittogetafeelforitsgoals,strengths,andweaknesses.2. Intheexampleweparsethefilesoneveryrequest,whichcanbealotofperformance

overhead.Experimentwithparsingthefilesatthebeginningofyourprogramandexecutingtheminyourhttp.Handler(hint:makeuseoftheCopy()methodonhtml.Template).

3. Experimentwithparsingandusingmultipletemplates.

BuildingWebAppswithGo

24HTMLTemplates

Page 25: Building Web Apps With Go

UsingtherenderpackageIfyouwantrenderingJSONandHTMLtobeevensimpler,thereisthegithub.com/unrolled/renderpackage.Thispackagewasinspiredbythemartini-contrib/renderpackageandismygotowhenitcomestorenderingdataforpresentationinmywebapplications.

packagemain

import(

"net/http"

"gopkg.in/unrolled/render.v1"

)

funcmain(){

r:=render.New(render.Options{})

mux:=http.NewServeMux()

mux.HandleFunc("/",func(whttp.ResponseWriter,req*http.Request){

w.Write([]byte("Welcome,visitsubpagesnow."))

})

mux.HandleFunc("/data",func(whttp.ResponseWriter,req*http.Request){

r.Data(w,http.StatusOK,[]byte("Somebinarydatahere."))

})

mux.HandleFunc("/json",func(whttp.ResponseWriter,req*http.Request){

r.JSON(w,http.StatusOK,map[string]string{"hello":"json"})

})

mux.HandleFunc("/html",func(whttp.ResponseWriter,req*http.Request){

//Assumesyouhaveatemplatein./templatescalled"example.tmpl"

//$mkdir-ptemplates&&echo"<h1>Hello{{.}}.</h1>">templates/example.tmpl

r.HTML(w,http.StatusOK,"example",nil)

})

http.ListenAndServe(":8080",mux)

}

Exercises1. Havefunplayingwithalloftheoptionsavailablewhencallingrender.New()2. Tryusingthe.yieldhelperfunction(withthecurlybraces)andalayoutwithHTML

BuildingWebAppswithGo

25UsingTherenderpackage

Page 26: Building Web Apps With Go

templates.

BuildingWebAppswithGo

26UsingTherenderpackage

Page 27: Building Web Apps With Go

TestingTestingisanimportantpartofanyapplication.TherearetwoapproacheswecantaketotestingGowebapplications.Thefirstapproachisaunit-teststyleapproach.Theotherismoreofanend-to-endapproach.Inthischapterwe'llcoverbothapproaches.

BuildingWebAppswithGo

27Testing

Page 28: Building Web Apps With Go

UnitTestingUnittestingallowsustotestahttp.HandlerFuncdirectlywithoutrunninganymiddleware,routers,oranyothertypeofcodethatmightotherwisewrapthefunction.

packagemain

import(

"fmt"

"net/http"

)

funcHelloWorld(reshttp.ResponseWriter,req*http.Request){

fmt.Fprint(res,"HelloWorld")

}

funcmain(){

http.HandleFunc("/",HelloWorld)

http.ListenAndServe(":3000",nil)

}

Thisisthetestfile.Itshouldbeplacedinthesamedirectoryasyourapplicationandnamemain_test.go.

BuildingWebAppswithGo

28UnitTesting

Page 29: Building Web Apps With Go

packagemain

import(

"net/http"

"net/http/httptest"

"testing"

)

funcTest_HelloWorld(t*testing.T){

req,err:=http.NewRequest("GET","http://example.com/foo",nil)

iferr!=nil{

t.Fatal(err)

}

res:=httptest.NewRecorder()

HelloWorld(res,req)

exp:="HelloWorld"

act:=res.Body.String()

ifexp!=act{

t.Fatalf("Expected%sgog%s",exp,act)

}

}

Exercises1. ChangetheoutputofHelloWorldtoprintaparameterandthentestthattheparameter

isrendered.2. CreateaPOSTrequestandtestthattherequestisproperlyhandled.

BuildingWebAppswithGo

29UnitTesting

Page 30: Building Web Apps With Go

EndToEndTestingEndtoendallowsustotestapplicationsthroughthewholerequestcycle.Whereunittestingismeanttojusttestaparticularfunction,endtoendtestswillrunthemiddleware,router,andotherthatarequestmypassthrough.

packagemain

import(

"fmt"

"net/http"

"github.com/codegangsta/negroni"

"github.com/julienschmidt/httprouter"

)

funcHelloWorld(reshttp.ResponseWriter,req*http.Request,phttprouter.Params){

fmt.Fprint(res,"HelloWorld")

}

funcApp()http.Handler{

n:=negroni.Classic()

m:=func(reshttp.ResponseWriter,req*http.Request,nexthttp.HandlerFunc){

fmt.Fprint(res,"Before...")

next(res,req)

fmt.Fprint(res,"...After")

}

n.Use(negroni.HandlerFunc(m))

r:=httprouter.New()

r.GET("/",HelloWorld)

n.UseHandler(r)

returnn

}

funcmain(){

http.ListenAndServe(":3000",App())

}

Thisisthetestfile.Itshouldbeplacedinthesamedirectoryasyourapplicationandnamemain_test.go.

BuildingWebAppswithGo

30EndtoEndTesting

Page 31: Building Web Apps With Go

packagemain

import(

"io/ioutil"

"net/http"

"net/http/httptest"

"testing"

)

funcTest_App(t*testing.T){

ts:=httptest.NewServer(App())

deferts.Close()

res,err:=http.Get(ts.URL)

iferr!=nil{

t.Fatal(err)

}

body,err:=ioutil.ReadAll(res.Body)

res.Body.Close()

iferr!=nil{

t.Fatal(err)

}

exp:="Before...HelloWorld...After"

ifexp!=string(body){

t.Fatalf("Expected%sgot%s",exp,body)

}

}

Exercises1. Createanotherpieceofmiddlewarethatmutatesthestatusoftherequest.2. CreateaPOSTrequestandtestthattherequestisproperlyhandled.

BuildingWebAppswithGo

31EndtoEndTesting

Page 32: Building Web Apps With Go

ControllersControllersareafairlyfamiliartopicinotherwebdevelopmentcommunities.Sincemostwebdevelopersrallyaroundthemightynet/httpinterface,notmanycontrollerimplementationshavecaughtonstrongly.However,thereisgreatbenefitinusingacontrollermodel.Itallowsforclean,welldefinedabstractionsaboveandbeyondwhatthenet/httphandlerinterfacecanaloneprovide.

HandlerDependenciesInthisexamplewewillexperimentwithbuildingourowncontrollerimplementationusingsomestandardfeaturesinGo.Butfirst,letsstartwiththeproblemswearetryingtosolve.Sayweareusingtherenderlibrarythatwetalkedaboutinpreviouschapters:

varRender=render.New(render.Options{})

Ifwewantourhttp.Handlerstobeableaccessourrender.Renderinstance,wehaveacoupleoptions.

1.Useaglobalvariable:Thisisn'ttoobadforsmallprograms,butwhentheprogramgetslargeritquicklybecomesamaintenancenightmare.

2.Passthevariablethroughaclosuretothehttp.Handler:Thisisagreatidea,andweshouldbeusingitmostofthetime.Theimplementationendsuplookinglikethis:

funcMyHandler(r*render.Render)http.Handler{

returnhttp.HandlerFunc(func(rwhttp.ResponseWriter,r*http.Request){

//nowwecanaccessr

})

}

CaseforControllersWhenyourprogramgrowsinsize,youwillstarttonoticethatmanyofyourhttp.Handlerswillsharethesamedependenciesandyouwillhavealotoftheseclosurizedhttp.Handlerswiththesamearguments.ThewayIliketocleanthisupistowritealittlebasecontrollerimplementationthataffordsmeafewwins:

1. Allowsmetosharethedependenciesacrosshttp.Handlersthathavesimilargoalsor

BuildingWebAppswithGo

32Controllers

Page 33: Building Web Apps With Go

concepts.2. Avoidsglobalvariablesandfunctionsforeasytesting/mocking.3. GivesmeamorecentralizedandGo-likemechanismforhandlingerrors.Thegreatpartaboutcontrollersisthatitgivesusallthesethingswithoutimportinganexternalpackage!MostofthisfunctionalitycomesfromcleveruseoftheGofeatureset,namelyGostructsandembedding.Let'stakealookattheimplementation.

packagemain

import"net/http"

//Actiondefinesastandardfunctionsignatureforustousewhencreating

//controlleractions.Acontrolleractionisbasicallyjustamethodattachedto

//acontroller.

typeActionfunc(rwhttp.ResponseWriter,r*http.Request)error

//ThisisourBaseController

typeAppControllerstruct{}

//Theactionfunctionhelpswitherrorhandlinginacontroller

func(c*AppController)Action(aAction)http.Handler{

returnhttp.HandlerFunc(func(rwhttp.ResponseWriter,r*http.Request){

iferr:=a(rw,r);err!=nil{

http.Error(rw,err.Error(),500)

}

})

}

Thatsit!Thatisalltheimplementationthatweneedtohavethepowerofcontrollersatourfingertips.Allwehavelefttodoisimplementanexamplecontroller:

BuildingWebAppswithGo

33Controllers

Page 34: Building Web Apps With Go

packagemain

import(

"net/http"

"gopkg.in/unrolled/render.v1"

)

typeMyControllerstruct{

AppController

*render.Render

}

func(c*MyController)Index(rwhttp.ResponseWriter,r*http.Request)error{

c.JSON(rw,200,map[string]string{"Hello":"JSON"})

returnnil

}

funcmain(){

c:=&MyController{Render:render.New(render.Options{})}

http.ListenAndServe(":8080",c.Action(c.Index))

}

Exercises1. ExtendMyControllertohavemultipleactionsfordifferentroutesinyourapplication.2. Playwithmorecontrollerimplementations,getcreative.3. OverridetheActionmethodonMyControllertorenderaerrorHTMLpage.

BuildingWebAppswithGo

34Controllers

Page 35: Building Web Apps With Go

DatabasesOneofthemostaskedquestionsIgetaboutwebdevelopmentinGoishowtoconnecttoaSQLdatabase.Thankfully,GohasafantasticSQLpackageinthestandardlibrarythatallowsustouseawholeslewofdriversfordifferentSQLdatabases.InthisexamplewewillconnecttoaSQLitedatabase,butthesyntax(minussomesmallSQLsemantics)isthesameforaMySQLorPostgreSQLdatabase.

BuildingWebAppswithGo

35Databases

Page 36: Building Web Apps With Go

packagemain

import(

"database/sql"

"fmt"

"log"

"net/http"

_"github.com/mattn/go-sqlite3"

)

funcmain(){

db:=NewDB()

log.Println("Listeningon:8080")

http.ListenAndServe(":8080",ShowBooks(db))

}

funcShowBooks(db*sql.DB)http.Handler{

returnhttp.HandlerFunc(func(rwhttp.ResponseWriter,r*http.Request){

vartitle,authorstring

err:=db.QueryRow("selecttitle,authorfrombooks").Scan(&title,&author)

iferr!=nil{

panic(err)

}

fmt.Fprintf(rw,"Thefirstbookis'%s'by'%s'",title,author)

})

}

funcNewDB()*sql.DB{

db,err:=sql.Open("sqlite3","example.sqlite")

iferr!=nil{

panic(err)

}

_,err=db.Exec("createtableifnotexistsbooks(titletext,authortext)")

iferr!=nil{

panic(err)

}

returndb

}

Exercises1. MakeuseoftheQueryfunctiononoursql.DBinstancetoextractacollectionofrows

andmapthemtostructs.2. AddtheabilitytoinsertnewrecordsintoourdatabasebyusinganHTMLform.

BuildingWebAppswithGo

36Databases

Page 37: Building Web Apps With Go

3. gogetgithub.com/jmoiron/sqlxandobservetheimprovementsmadeovertheexistingdatabase/sqlpackageinthestandardlibrary.

BuildingWebAppswithGo

37Databases

Page 38: Building Web Apps With Go

TipsandTricks

Wrapahttp.HandlerFuncclosureSometimesyouwanttopassdatatoahttp.HandlerFunconinitialization.Thiscaneasilybedonebycreatingaclosureofthehttp.HandlerFunc:

funcMyHandler(database*sql.DB)http.Handler{

returnhttp.HandlerFunc(func(rwhttp.ResponseWriter,r*http.Request){

//younowhaveaccesstothe*sql.DBhere

})

}

Usinggorilla/contextforrequest-specificdataItisprettyoftenthatweneedtostoreandretrievedatathatisspecifictothecurrentHTTPrequest.Usegorilla/contexttomapvaluesandretrievethemlater.Itcontainsaglobalmutexonamapofrequestobjects.

funcMyHandler(whttp.ResponseWriter,r*http.Request){

val:=context.Get(r,"myKey")

//returns("bar",true)

val,ok:=context.GetOk(r,"myKey")

//...

}

BuildingWebAppswithGo

38TipsandTricks

Page 39: Building Web Apps With Go

MovingForwardYou'vedoneit!YouhavegottenatasteofGowebdevelopmenttoolsandlibraries.Atthetimeofthiswriting,thisbookisstillinflux.ThissectionisreservedformoreGowebresourcestocontinueyourlearning.

BuildingWebAppswithGo

39MovingForward


Recommended