Sådan oprettes en webapp med Go, Gin og React

Denne artikel blev oprindeligt udgivet på Min blog

TL; DR: I denne vejledning viser jeg dig, hvor let det er at oprette en webapplikation med Go og Gin-rammen og tilføje godkendelse til den. Tjek Github-repoen for den kode, vi skal skrive.

Gin er en højtydende mikroramme. Det leverer en meget minimalistisk ramme, der kun indeholder de mest væsentlige funktioner, biblioteker og funktioner, der er nødvendige for at opbygge webapplikationer og mikrotjenester. Det gør det nemt at oprette en anmodningshåndteringsrørledning fra modulære, genanvendelige stykker. Det gør det ved at lade dig skrive middleware, der kan tilsluttes en eller flere anmodningshåndterere eller grupper af anmodningshåndterere.

Gin-funktioner

Gin er en hurtig, enkel men alligevel fuldt udstyret og meget effektiv webramme til Go. Tjek nogle af nedenstående funktioner, der gør det til en værdig ramme at overveje til dit næste Golang-projekt.

  • Hastighed: Gin er bygget til hastighed. Rammen tilbyder en Radix-træbaseret routing og et lille hukommelsesfodaftryk. Ingen refleksion. Forudsigelig API-ydeevne.
  • Crash-Free : Gin har evnen til at fange nedbrud eller panik under kørsel og kan komme sig efter dem. På denne måde vil din ansøgning altid være tilgængelig.
  • Routing: Gin giver en routing-interface, der giver dig mulighed for at udtrykke, hvordan din webapplikation eller API-ruter skal se ud.
  • JSON-validering: Gin kan let analysere og validere JSON-anmodninger og kontrollere for eksistensen af ​​krævede værdier.
  • Fejladministration: Gin giver en bekvem måde at indsamle alle de fejl, der opstod under en HTTP-anmodning. Til sidst kan en middleware skrive dem til en logfil eller til en database og sende dem gennem netværket.
  • Indbygget gengivelse: Gin giver en brugervenlig API til JSON-, XML- og HTML-gengivelse.

Forudsætninger

For at følge denne vejledning skal du have Go installeret på din maskine, en webbrowser for at se appen og en kommandolinje til at udføre build-kommandoer.

Go, eller som det normalt kaldes Golang , er et programmeringssprog udviklet af Google til opbygning af moderne software. Go er et sprog designet til at få ting gjort effektivt og hurtigt. De vigtigste fordele ved Go inkluderer:

  • Stærkt skrevet og affald indsamlet
  • Flammende hurtige kompileringstider
  • Samtidig indbygget
  • Omfattende standardbibliotek

Gå over til downloads-sektionen på Go-webstedet for at komme i gang på din maskine.

Opbygning af en app med Gin

Vi bygger en simpel vittighedsapp med Gin . Vores app viser nogle fjollede farvittigheder. Vi vil tilføje godkendelse til det, så alle indloggede brugere har privilegiet til at kunne lide og se vittigheder.

Dette giver os mulighed for at illustrere, hvordan Gin kan bruges til at udvikle webapplikationer og / eller API'er.

Vi bruger følgende funktioner, der tilbydes af Gin:

  • Middleware
  • Routing
  • Ruter gruppering

Klar parat start

Vi skriver hele vores Go-applikation i en main.gofil. Da det er en lille applikation, bliver det let at oprette applikationen med lige go runfra terminalen.

Vi opretter en ny mappe golang-gini vores Go-arbejdsområde og derefter en main.gofil i det:

$ mkdir -p $GOPATH/src/github.com/user/golang-gin $ cd $GOPATH/src/github.com/user/golang-gin $ touch main.go

Indholdet af main.gofilen:

package main import ( "net/http" "github.com/gin-gonic/contrib/static" "github.com/gin-gonic/gin" ) func main() { // Set the router as the default one shipped with Gin router := gin.Default() // Serve frontend static files router.Use(static.Serve("/", static.LocalFile("./views", true))) // Setup route group for the API api := router.Group("/api") { api.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H { "message": "pong", }) }) } // Start and run the server router.Run(":3000") }

Vi bliver nødt til at oprette nogle flere mapper til vores statiske filer. main.goLad os oprette en viewsmappe i samme bibliotek som filen . I viewsmappen, skal du oprette en jsmappe og en index.htmlfil i det.

Den index.htmlfil vil være meget simpelt for nu:

   Jokeish App   

Welcome to the Jokeish App

Før vi tester, hvad vi hidtil har, lad os installere de tilføjede afhængigheder:

$ go get -u github.com/gin-gonic/gin $ go get -u github.com/gin-gonic/contrib/static

For at se hvad der fungerer, skal vi starte vores server ved at køre go run main.go.

Når applikationen kører, skal du navigere til //localhost:3000i din browser. Hvis alt gik godt, skulle du se niveau 1-overskriftstekst Velkommen til Jokeish-appen vist.

Definition af API

Lad os tilføje mere kode i vores main.gofil til vores API-definitioner. Vi opdaterer vores mainfunktion med to ruter /jokes/og /jokes/like/:jokeIDtil rutegruppen /api/.

func main() { // ... leave the code above untouched... // Our API will consit of just two routes // /jokes - which will retrieve a list of jokes a user can see // /jokes/like/:jokeID - which will capture likes sent to a particular joke api.GET("/jokes", JokeHandler) api.POST("/jokes/like/:jokeID", LikeJoke) } // JokeHandler retrieves a list of available jokes func JokeHandler(c *gin.Context) { c.Header("Content-Type", "application/json") c.JSON(http.StatusOK, gin.H { "message":"Jokes handler not implemented yet", }) } // LikeJoke increments the likes of a particular joke Item func LikeJoke(c *gin.Context) { c.Header("Content-Type", "application/json") c.JSON(http.StatusOK, gin.H { "message":"LikeJoke handler not implemented yet", }) }

Indholdet af main.gofilen skal se sådan ud:

package main import ( "net/http" "github.com/gin-gonic/contrib/static" "github.com/gin-gonic/gin" ) func main() { // Set the router as the default one shipped with Gin router := gin.Default() // Serve frontend static files router.Use(static.Serve("/", static.LocalFile("./views", true))) // Setup route group for the API api := router.Group("/api") { api.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H { "message": "pong", }) }) } // Our API will consit of just two routes // /jokes - which will retrieve a list of jokes a user can see // /jokes/like/:jokeID - which will capture likes sent to a particular joke api.GET("/jokes", JokeHandler) api.POST("/jokes/like/:jokeID", LikeJoke) // Start and run the server router.Run(":3000") } // JokeHandler retrieves a list of available jokes func JokeHandler(c *gin.Context) { c.Header("Content-Type", "application/json") c.JSON(http.StatusOK, gin.H { "message":"Jokes handler not implemented yet", }) } // LikeJoke increments the likes of a particular joke Item func LikeJoke(c *gin.Context) { c.Header("Content-Type", "application/json") c.JSON(http.StatusOK, gin.H { "message":"LikeJoke handler not implemented yet", }) }

Lad os køre vores app igen go run main.goog få adgang til vores ruter. //localhost:3000/api/jokesreturnerer et 200 OKheader-svar med beskeden jokes handler not implemented yet. En POST-anmodning om at //localhost:3000/api/jokes/like/1returnere et 200 OKoverskrift og meddelelsen Likejoke handler not implemented yet.

Vittigheder data

Da vi allerede har vores rutedefinitionssæt, som kun gør en ting (returnerer et JSON-svar), krydrer vi vores codebase lidt ved at tilføje noget mere kode til det.

// ... leave the code above untouched... // Let's create our Jokes struct. This will contain information about a Joke // Joke contains information about a single Joke type Joke struct { ID int `json:"id" binding:"required"` Likes int `json:"likes"` Joke string `json:"joke" binding:"required"` } // We'll create a list of jokes var jokes = []Joke{ Joke{1, 0, "Did you hear about the restaurant on the moon? Great food, no atmosphere."}, Joke{2, 0, "What do you call a fake noodle? An Impasta."}, Joke{3, 0, "How many apples grow on a tree? All of them."}, Joke{4, 0, "Want to hear a joke about paper? Nevermind it's tearable."}, Joke{5, 0, "I just watched a program about beavers. It was the best dam program I've ever seen."}, Joke{6, 0, "Why did the coffee file a police report? It got mugged."}, Joke{7, 0, "How does a penguin build it's house? Igloos it together."}, } func main() { // ... leave this block untouched... } // JokeHandler retrieves a list of available jokes func JokeHandler(c *gin.Context) { c.Header("Content-Type", "application/json") c.JSON(http.StatusOK, jokes) } // LikeJoke increments the likes of a particular joke Item func LikeJoke(c *gin.Context) { // confirm Joke ID sent is valid // remember to import the `strconv` package if jokeid, err := strconv.Atoi(c.Param("jokeID")); err == nil { // find joke, and increment likes for i := 0; i < len(jokes); i++ { if jokes[i].ID == jokeid { jokes[i].Likes += 1 } } // return a pointer to the updated jokes list c.JSON(http.StatusOK, &jokes) } else { // Joke ID is invalid c.AbortWithStatus(http.StatusNotFound) } } // NB: Replace the JokeHandler and LikeJoke functions in the previous version to the ones above

Når vores kode ser godt ud, lad os gå videre og teste vores API. Vi kan teste med cURLeller postmanog derefter sende en GETanmodning om //localhost:3000/jokesat få den fulde liste over vittigheder og en POSTanmodning om //localhost:3000/jokes/like/{jokeid}at øge en vits.

$ curl //localhost:3000/api/jokes $ curl -X POST //localhost:3000/api/jokes/like/4

Opbygning af brugergrænsefladen (React)

Vi har vores API på plads, så lad os bygge en frontend til at præsentere dataene fra vores API. Til dette bruger vi React. Vi går ikke for dybt ind i React, da det vil være uden for omfanget af denne tutorial. Hvis du har brug for at lære mere om React, skal du tjekke den officielle tutorial. Du kan implementere brugergrænsefladen med enhver frontend-ramme, du er fortrolig med.

Opsætning

Vi redigerer index.htmlfilen for at tilføje de eksterne biblioteker, der er nødvendige for at køre React. Så bliver vi nødt til at oprette en app.jsxfil i views/jsbiblioteket, der indeholder vores React-kode.

Vores index.htmlfil skal se sådan ud:

     Jokeish App 

Opbygning af vores komponenter

I React er visninger opdelt i komponenter. Vi bliver nødt til at bygge nogle komponenter:

  • en Appkomponent som hovedindgang, der starter applikationen
  • en Homekomponent, der står over for ikke-loggede brugere
  • en LoggedInkomponent med indhold, der kun er synligt af godkendte brugere
  • og en Jokekomponent, der viser en liste med vittigheder.

Vi skriver alle disse komponenter i app.jsxfilen.

App-komponenten

Denne komponent bootstrapper vores hele React-app. Det bestemmer, hvilken komponent der skal vises baseret på, om en bruger er godkendt eller ej. Vi starter med kun dens base og opdaterer den senere med mere funktionalitet.

class App extends React.Component { render() { if (this.loggedIn) { return (); } else { return (); } } }

Hjemmekomponenten

Denne komponent vises til ikke-loggede brugere sammen med en knap, der åbner en Hosted-låseskærm, hvor de kan tilmelde sig eller logge ind. Vi tilføjer denne funktionalitet senere.

class Home extends React.Component { render() { return ( 

Jokeish

A load of Dad jokes XD

Sign in to get access

Sign In ) } }

LoggedIn-komponent

Denne komponent vises, når en bruger er godkendt. Det gemmer i stateen række vittigheder, der befolkes, når komponenten monteres.

class LoggedIn extends React.Component { constructor(props) { super(props); this.state = { jokes: [] } } render() { return (

Log out

Jokeish

Let's feed you with some funny Jokes!!!

{this.state.jokes.map(function(joke, i){ return (); })} ) } }

Joke-komponenten

Den Jokekomponent vil indeholde oplysninger om hvert element fra de vittigheder respons, der skal vises.

class Joke extends React.Component { constructor(props) { super(props); this.state = { liked: "" } this.like = this.like.bind(this); } like() { // ... we'll add this block later } render() { return ( #{this.props.joke.id} {this.state.liked} {this.props.joke.joke} {this.props.joke.likes} Likes ) } }

Vi har skrevet vores komponenter, så lad os nu fortælle Reager, hvor appen skal gengives. Vi tilføjer blokken kode nedenfor til bunden af ​​vores app.jsxfil.

ReactDOM.render(, document.getElementById('app'));

Lad os genstarte vores Go-server go run main.goog gå over til vores apps URL //localhost:3000/. Du vil se, at Homekomponenten gengives.

Sikring af vores vittighedsapp med Auth0

Auth0 udsteder JSON Web Tokens på hvert login til dine brugere. Dette betyder, at du kan have en solid identitetsinfrastruktur, herunder enkelt login, brugeradministration, support til sociale identitetsudbydere (Facebook, Github, Twitter osv.), Virksomhedsidentitetsudbydere (Active Directory, LDAP, SAML osv.) og din egen database med brugere med kun et par linier kode.

Vi kan nemt konfigurere godkendelse i vores GIN-app ved hjælp af Auth0. Du skal bruge en konto for at følge denne del. Hvis du ikke allerede har en Auth0-konto, kan du tilmelde dig en nu.

Ansvarsfraskrivelse: Dette er ikke sponsoreret indhold.

Oprettelse af API-klienten

Vores tokens genereres med Auth0, så vi skal oprette en API og en klient fra vores Auth0-dashboard. Igen, hvis du ikke allerede har gjort det, skal du tilmelde dig en Auth0-konto.

For at oprette en ny API skal du navigere til afsnittet API'er i dit dashboard og klikke på knappen Opret API .

Vælg et API- navn og en identifikator . Identifikatoren vil være publikum for middleware. Den Signing Algoritme bør være RS256 .

For at oprette en ny klient skal du navigere til klientsektionen i dit dashboard og klikke på knappen Opret klient . Vælg typen Regular Web Applications.

Når klienten er oprettet, skal du være opmærksom på client_idog client_secret, da vi har brug for dem senere.

Vi er nødt til at tilføje de legitimationsoplysninger, der er nødvendige for vores API, til en miljøvariabel. Opret en ny fil i rodmappen .envog tilføj følgende til den med detaljerne fra Auth0-dashboardet:

export export export AUTH0_DOMAIN="yourdomain.auth0.com" export

Sikring af vores API-slutpunkter

I øjeblikket er vores API åben for verden. Vi er nødt til at sikre vores slutpunkter, så kun autoriserede brugere har adgang til dem.

Vi vil bruge en JWT Middleware til at kontrollere, om der er et gyldigt JSON-webtoken fra hver anmodning, der rammer vores slutpunkter.

Lad os oprette vores middleware:

// ... var jwtMiddleWare *jwtmiddleware.JWTMiddleware func main() { jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{ ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) { aud := os.Getenv("AUTH0_API_AUDIENCE") checkAudience := token.Claims.(jwt.MapClaims).VerifyAudience(aud, false) if !checkAudience { return token, errors.New("Invalid audience.") } // verify iss claim iss := os.Getenv("AUTH0_DOMAIN") checkIss := token.Claims.(jwt.MapClaims).VerifyIssuer(iss, false) if !checkIss { return token, errors.New("Invalid issuer.") } cert, err := getPemCert(token) if err != nil { log.Fatalf("could not get cert: %+v", err) } result, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(cert)) return result, nil }, SigningMethod: jwt.SigningMethodRS256, }) // register our actual jwtMiddleware jwtMiddleWare = jwtMiddleware // ... the rest of the code below this function doesn't change yet } // authMiddleware intercepts the requests, and check for a valid jwt token func authMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // Get the client secret key err := jwtMiddleWare.CheckJWT(c.Writer, c.Request) if err != nil { // Token not found fmt.Println(err) c.Abort() c.Writer.WriteHeader(http.StatusUnauthorized) c.Writer.Write([]byte("Unauthorized")) return } } }

I ovenstående kode har vi en ny jwtMiddleWarevariabel, der initialiseres i mainfunktionen. Det bruges i den authMiddlewaremidterste funktion.

Hvis du bemærker, trækker vi vores serveroplysninger fra en miljøvariabel (en af ​​principperne i en 12-faktor app ). Vores middleware kontrollerer og modtager et token fra en anmodning og kalder jwtMiddleWare.CheckJWTmetoden for at validere det sendte token.

Lad os også skrive funktionen til at returnere JSON Web Keys:

// ... the code above is untouched... // Jwks stores a slice of JSON Web Keys type Jwks struct { Keys []JSONWebKeys `json:"keys"` } type JSONWebKeys struct { Kty string `json:"kty"` Kid string `json:"kid"` Use string `json:"use"` N string `json:"n"` E string `json:"e"` X5c []string `json:"x5c"` } func main() { // ... the code in this method is untouched... } func getPemCert(token *jwt.Token) (string, error) { cert := "" resp, err := http.Get(os.Getenv("AUTH0_DOMAIN") + ".well-known/jwks.json") if err != nil { return cert, err } defer resp.Body.Close() var jwks = Jwks{} err = json.NewDecoder(resp.Body).Decode(&jwks) if err != nil { return cert, err } x5c := jwks.Keys[0].X5c for k, v := range x5c { if token.Header["kid"] == jwks.Keys[k].Kid { cert = "-----BEGIN CERTIFICATE-----\n" + v + "\n-----END CERTIFICATE-----" } } if cert == "" { return cert, errors.New("unable to find appropriate key.") } return cert, nil }

Brug af JWT middleware

Brug af middleware er meget ligetil. Vi sender det bare som en parameter til vores rutedefinition.

... api.GET("/jokes", authMiddleware(), JokeHandler) api.POST("/jokes/like/:jokeID", authMiddleware(), LikeJoke) ...

Vores main.gofil skal se sådan ud:

package main import ( "encoding/json" "errors" "fmt" "log" "net/http" "os" "strconv" jwtmiddleware "github.com/auth0/go-jwt-middleware" jwt "github.com/dgrijalva/jwt-go" "github.com/gin-gonic/contrib/static" "github.com/gin-gonic/gin" ) type Response struct { Message string `json:"message"` } type Jwks struct { Keys []JSONWebKeys `json:"keys"` } type JSONWebKeys struct { Kty string `json:"kty"` Kid string `json:"kid"` Use string `json:"use"` N string `json:"n"` E string `json:"e"` X5c []string `json:"x5c"` } type Joke struct { ID int `json:"id" binding:"required"` Likes int `json:"likes"` Joke string `json:"joke" binding:"required"` } /** we'll create a list of jokes */ var jokes = []Joke{ Joke{1, 0, "Did you hear about the restaurant on the moon? Great food, no atmosphere."}, Joke{2, 0, "What do you call a fake noodle? An Impasta."}, Joke{3, 0, "How many apples grow on a tree? All of them."}, Joke{4, 0, "Want to hear a joke about paper? Nevermind it's tearable."}, Joke{5, 0, "I just watched a program about beavers. It was the best dam program I've ever seen."}, Joke{6, 0, "Why did the coffee file a police report? It got mugged."}, Joke{7, 0, "How does a penguin build it's house? Igloos it together."}, } var jwtMiddleWare *jwtmiddleware.JWTMiddleware func main() { jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{ ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) { aud := os.Getenv("AUTH0_API_AUDIENCE") checkAudience := token.Claims.(jwt.MapClaims).VerifyAudience(aud, false) if !checkAudience { return token, errors.New("Invalid audience.") } // verify iss claim iss := os.Getenv("AUTH0_DOMAIN") checkIss := token.Claims.(jwt.MapClaims).VerifyIssuer(iss, false) if !checkIss { return token, errors.New("Invalid issuer.") } cert, err := getPemCert(token) if err != nil { log.Fatalf("could not get cert: %+v", err) } result, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(cert)) return result, nil }, SigningMethod: jwt.SigningMethodRS256, }) jwtMiddleWare = jwtMiddleware // Set the router as the default one shipped with Gin router := gin.Default() // Serve the frontend router.Use(static.Serve("/", static.LocalFile("./views", true))) api := router.Group("/api") { api.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "pong", }) }) api.GET("/jokes", authMiddleware(), JokeHandler) api.POST("/jokes/like/:jokeID", authMiddleware(), LikeJoke) } // Start the app router.Run(":3000") } func getPemCert(token *jwt.Token) (string, error) { cert := "" resp, err := http.Get(os.Getenv("AUTH0_DOMAIN") + ".well-known/jwks.json") if err != nil { return cert, err } defer resp.Body.Close() var jwks = Jwks{} err = json.NewDecoder(resp.Body).Decode(&jwks) if err != nil { return cert, err } x5c := jwks.Keys[0].X5c for k, v := range x5c { if token.Header["kid"] == jwks.Keys[k].Kid { cert = "-----BEGIN CERTIFICATE-----\n" + v + "\n-----END CERTIFICATE-----" } } if cert == "" { return cert, errors.New("unable to find appropriate key") } return cert, nil } // authMiddleware intercepts the requests, and check for a valid jwt token func authMiddleware() gin.HandlerFunc { return func(c *gin.Context) { // Get the client secret key err := jwtMiddleWare.CheckJWT(c.Writer, c.Request) if err != nil { // Token not found fmt.Println(err) c.Abort() c.Writer.WriteHeader(http.StatusUnauthorized) c.Writer.Write([]byte("Unauthorized")) return } } } // JokeHandler returns a list of jokes available (in memory) func JokeHandler(c *gin.Context) { c.Header("Content-Type", "application/json") c.JSON(http.StatusOK, jokes) } func LikeJoke(c *gin.Context) { // Check joke ID is valid if jokeid, err := strconv.Atoi(c.Param("jokeID")); err == nil { // find joke and increment likes for i := 0; i < len(jokes); i++ { if jokes[i].ID == jokeid { jokes[i].Likes = jokes[i].Likes + 1 } } c.JSON(http.StatusOK, &jokes) } else { // the jokes ID is invalid c.AbortWithStatus(http.StatusNotFound) } }

Lad os installere jwtmiddlewarebibliotekerne:

$ go get -u github.com/auth0/go-jwt-middleware $ go get -u github.com/dgrijalva/jwt-go

Lad os kilde vores miljøfil og genstarte vores app-server:

$ source .env $ go run main.go

Hvis vi nu prøver at få adgang til et af slutpunkterne, står vi over for en 401 Unauthorizedfejl. Det er fordi vi skal sende et token sammen med anmodningen.

Log ind med Auth0 og React

Lad os implementere et login-system, så brugerne kan logge ind eller oprette konti og få adgang til vores vittigheder. Vi tilføjer app.jsxfølgende Auth0-legitimationsoplysninger til vores fil:

  • AUTH0_CLIENT_ID
  • AUTH0_DOMAIN
  • AUTH0_CALLBACK_URL - URL'en til din app
  • AUTH0_API_AUDIENCE
Du kan finde den AUTH0_CLIENT_ID, AUTH0_DOMAINog AUTH0_API_AUDIENCEdata fra din Auth0 management instrumentbræt.

Vi er nødt til at indstille en, callbacksom Auth0 omdirigerer til. Naviger til sektionen Kunder i dit dashboard. Lad os i indstillingerne indstille tilbagekaldet til //localhost:3000:

Med legitimationsoplysninger på plads, lad os opdatere vores React-komponenter.

APP-komponent

const AUTH0_CLIENT_ID = "aIAOt9fkMZKrNsSsFqbKj5KTI0ObTDPP"; const AUTH0_DOMAIN = "hakaselabs.auth0.com"; const AUTH0_CALLBACK_URL = location.href; const AUTH0_API_AUDIENCE = "golang-gin"; class App extends React.Component { parseHash() { this.auth0 = new auth0.WebAuth({ domain: AUTH0_DOMAIN, clientID: AUTH0_CLIENT_ID }); this.auth0.parseHash(window.location.hash, (err, authResult) => { if (err) { return console.log(err); } if ( authResult !== null && authResult.accessToken !== null && authResult.idToken !== null ) { localStorage.setItem("access_token", authResult.accessToken); localStorage.setItem("id_token", authResult.idToken); localStorage.setItem( "profile", JSON.stringify(authResult.idTokenPayload) ); window.location = window.location.href.substr( 0, window.location.href.indexOf("#") ); } }); } setup() { $.ajaxSetup({ beforeSend: (r) => { if (localStorage.getItem("access_token")) { r.setRequestHeader( "Authorization", "Bearer " + localStorage.getItem("access_token") ); } } }); } setState() { let idToken = localStorage.getItem("id_token"); if (idToken) { this.loggedIn = true; } else { this.loggedIn = false; } } componentWillMount() { this.setup(); this.parseHash(); this.setState(); } render() { if (this.loggedIn) { return ; } return ; } }

Vi har opdateret App komponent med tre komponent metoder ( setup, parseHash, og setState), og en livscyklus metode componentWillMount. Den parseHashmetode initialiserer auth0webAuthklienten og parser hash til en mere læsbart format, gemme dem til localSt. For at vise låseskærmen skal du fange og gemme brugertokenet og føje den korrekte autorisationsoverskrift til eventuelle anmodninger til vores API

Hjemmekomponent

Vores Home-komponent opdateres. Vi tilføjer funktionaliteten til authenticatemetoden, som vil udløse den hostede låseskærm til at blive vist og tillade vores brugere at logge ind eller tilmelde sig.

class Home extends React.Component { constructor(props) { super(props); this.authenticate = this.authenticate.bind(this); } authenticate() { this.WebAuth = new auth0.WebAuth({ domain: AUTH0_DOMAIN, clientID: AUTH0_CLIENT_ID, scope: "openid profile", audience: AUTH0_API_AUDIENCE, responseType: "token id_token", redirectUri: AUTH0_CALLBACK_URL }); this.WebAuth.authorize(); } render() { return ( 

Jokeish

A load of Dad jokes XD

Sign in to get access

Sign In ); } }

LoggedIn-komponent

Vi opdaterer LoggedInkomponenten til at kommunikere med vores API og trække alle vittigheder. Det videregiver hver joke som en proptil Jokekomponenten, som gengiver et bootstrap-panel. Lad os skrive dem:

class LoggedIn extends React.Component { constructor(props) { super(props); this.state = { jokes: [] }; this.serverRequest = this.serverRequest.bind(this); this.logout = this.logout.bind(this); } logout() { localStorage.removeItem("id_token"); localStorage.removeItem("access_token"); localStorage.removeItem("profile"); location.reload(); } serverRequest() { $.get("//localhost:3000/api/jokes", res => { this.setState({ jokes: res }); }); } componentDidMount() { this.serverRequest(); } render() { return (

Log out

Jokeish

Let's feed you with some funny Jokes!!!

{this.state.jokes.map(function(joke, i) { return ; })} ); } }

Joke-komponent

Vi opdaterer også Jokekomponenten til at formatere hvert Joke-element, der sendes til det fra forældrekomponenten ( LoggedIn). Vi tilføjer også en likemetode, der øger en joke.

class Joke extends React.Component { constructor(props) { super(props); this.state = { liked: "", jokes: [] }; this.like = this.like.bind(this); this.serverRequest = this.serverRequest.bind(this); } like() { let joke = this.props.joke; this.serverRequest(joke); } serverRequest(joke) { $.post( "//localhost:3000/api/jokes/like/" + joke.id, { like: 1 }, res => { console.log("res... ", res); this.setState({ liked: "Liked!", jokes: res }); this.props.jokes = res; } ); } render() { return ( #{this.props.joke.id}{" "} {this.state.liked} {this.props.joke.joke} {this.props.joke.likes} Likes ) } }

Samler det hele

Når brugergrænsefladen og API er komplet, kan vi teste vores app. Vi starter med at starte vores server source .env && go run main.go, og derefter navigerer vi til //localhost:3000fra enhver browser. Du skal se Homekomponenten med en login-knap. Ved at klikke på loginsknappen omdirigeres til en hostet låseside (opret en konto eller login) for at fortsætte med at bruge applikationen.

Hjem:

Auth0-hostet låseskærm:

LoggedIn App-visning:

Konklusion

Tillykke! Du har lært, hvordan man bygger en applikation og en API med Go og Gin-rammen.

Savnede jeg noget vigtigt? Lad mig vide af det i kommentarerne.

Du kan sige Hej til mig på Twitter @codehakase