Cómo crear una aplicación web con Go, Gin y React

Este artículo se publicó originalmente en Mi blog.

TL; DR: En este tutorial, le mostraré lo fácil que es crear una aplicación web con Go y el marco Gin y agregarle autenticación. Consulte el repositorio de Github para ver el código que vamos a escribir.

Gin es un micro-marco de alto rendimiento. Ofrece un marco muy minimalista que lleva consigo solo las características, bibliotecas y funcionalidades más esenciales necesarias para crear aplicaciones web y microservicios. Simplifica la construcción de una tubería de manejo de solicitudes a partir de piezas modulares y reutilizables. Para ello, le permite escribir middleware que se puede conectar a uno o más controladores de solicitudes o grupos de controladores de solicitudes.

Características de la ginebra

Gin es un marco web rápido, simple pero con todas las funciones y muy eficiente para Go. Consulte algunas de las características a continuación que lo convierten en un marco digno de considerar para su próximo proyecto de Golang.

  • Velocidad: la ginebra está diseñada para la velocidad. El marco ofrece un enrutamiento basado en árbol Radix y una pequeña huella de memoria. Sin reflejo. Rendimiento de API predecible.
  • Sin choques: Gin tiene la capacidad de detectar choques o pánico durante el tiempo de ejecución, y puede recuperarse de ellos. De esta forma tu aplicación siempre estará disponible.
  • Enrutamiento: Gin proporciona una interfaz de enrutamiento que le permite expresar cómo debe verse su aplicación web o rutas API.
  • Validación JSON: Gin puede analizar y validar solicitudes JSON fácilmente, verificando la existencia de valores requeridos.
  • Gestión de errores: Gin proporciona una forma conveniente de recopilar todos los errores ocurridos durante una solicitud HTTP. Eventualmente, un middleware puede escribirlos en un archivo de registro o en una base de datos y enviarlos a través de la red.
  • Representación incorporada: Gin proporciona una API fácil de usar para la representación JSON, XML y HTML.

Prerrequisitos

Para seguir este tutorial, necesitará tener Go instalado en su máquina, un navegador web para ver la aplicación y una línea de comandos para ejecutar los comandos de compilación.

Go, o como se llama normalmente Golang , es un lenguaje de programación desarrollado por Google para crear software moderno. Go es un lenguaje diseñado para hacer las cosas de manera eficiente y rápida. Los beneficios clave de Go incluyen:

  • Fuertemente tipado y recolección de basura
  • Tiempos de compilación ultrarrápidos
  • Simultaneidad incorporada
  • Amplia biblioteca estándar

Dirígete a la sección de descargas del sitio web de Go para que Go funcione en tu máquina.

Construyendo una aplicación con Gin

Crearemos una aplicación de listado de bromas simple con Gin . Nuestra aplicación enumerará algunos chistes tontos de papá. Vamos a agregarle autenticación, para que todos los usuarios que hayan iniciado sesión tengan el privilegio de dar me gusta y ver bromas.

Esto nos permitirá ilustrar cómo se puede utilizar Gin para desarrollar aplicaciones web y / o API.

Haremos uso de las siguientes funcionalidades que ofrece Gin:

  • Middleware
  • Enrutamiento
  • Agrupación de rutas

En sus marcas, listos, fuera

Escribiremos toda nuestra aplicación Go en un main.goarchivo. Dado que es una aplicación pequeña, será fácil construir la aplicación solo go rundesde la terminal.

Crearemos un nuevo directorio golang-ginen nuestro espacio de trabajo de Go, y luego un main.goarchivo en él:

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

El contenido del main.goarchivo:

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") }

Necesitaremos crear algunos directorios más para nuestros archivos estáticos. En el mismo directorio que el main.goarchivo, creemos una viewscarpeta. En la viewscarpeta, cree una jscarpeta y un index.htmlarchivo en ella.

El index.htmlarchivo será muy simple por ahora:

   Jokeish App   

Welcome to the Jokeish App

Antes de probar lo que tenemos hasta ahora, instalemos las dependencias agregadas:

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

Para ver qué funciona, necesitaremos iniciar nuestro servidor ejecutando go run main.go.

Una vez que la aplicación se esté ejecutando, navegue hasta //localhost:3000en su navegador. Si todo salió bien, debería ver el texto del encabezado de nivel 1 Bienvenido a la aplicación de broma .

Definiendo la API

Agreguemos más código en nuestro main.goarchivo para nuestras definiciones de API. Actualizaremos nuestra mainfunción con dos rutas /jokes/y /jokes/like/:jokeIDcon el grupo de rutas /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", }) }

El contenido del main.goarchivo debería verse así:

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", }) }

Ejecutemos nuestra aplicación nuevamente go run main.goy accedamos a nuestras rutas. //localhost:3000/api/jokesdevolverá una 200 OKrespuesta de encabezado, con el mensaje jokes handler not implemented yet. Una solicitud POST //localhost:3000/api/jokes/like/1devuelve un 200 OKencabezado y el mensaje Likejoke handler not implemented yet.

Datos de chistes

Dado que ya tenemos nuestra definición de rutas establecida, que hace solo una cosa (devolver una respuesta JSON), le daremos un poco de sabor a nuestra base de código agregando algo más de código.

// ... 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

Con nuestro código en buen estado, sigamos adelante y probemos nuestra API. Podemos probar con cURLo postman, y luego enviar una GETsolicitud a //localhost:3000/jokespara obtener la lista completa de chistes y una POSTsolicitud //localhost:3000/jokes/like/{jokeid}para incrementar los gustos de un chiste.

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

Construyendo la UI (React)

Tenemos nuestra API en su lugar, así que construyamos una interfaz para presentar los datos de nuestra API. Para esto, usaremos React. No profundizaremos demasiado en React, ya que estará fuera del alcance de este tutorial. Si necesita obtener más información sobre React, consulte el tutorial oficial. Puede implementar la interfaz de usuario con cualquier marco de interfaz con el que se sienta cómodo.

Preparar

Editaremos el index.htmlarchivo para agregar las bibliotecas externas necesarias para ejecutar React. Luego, necesitaremos crear un app.jsxarchivo en el views/jsdirectorio, que contendrá nuestro código React.

Nuestro index.htmlarchivo debería verse así:

     Jokeish App 

Construyendo nuestros componentes

En React, las vistas se dividen en componentes. Necesitaremos construir algunos componentes:

  • un Appcomponente como la entrada principal que inicia la aplicación
  • un Homecomponente que enfrentará a los usuarios no registrados
  • un LoggedIncomponente con contenido solo visible para usuarios autenticados
  • y un Jokecomponente para mostrar una lista de chistes.

Escribiremos todos estos componentes en el app.jsxarchivo.

El componente de la aplicación

Este componente arranca toda nuestra aplicación React. Decide qué componente mostrar en función de si un usuario está autenticado o no. Comenzaremos solo con su base y luego lo actualizaremos con más funcionalidad.

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

El componente de inicio

Este componente se muestra a los usuarios que no han iniciado sesión, junto con un botón que abre una pantalla de bloqueo alojada donde pueden registrarse o iniciar sesión. Agregaremos esta funcionalidad más adelante.

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

Jokeish

A load of Dad jokes XD

Sign in to get access

Sign In ) } }

Componente LoggedIn

Este componente se muestra cuando un usuario está autenticado. Almacena en su stateconjunto de chistes que se rellenan cuando se monta el componente.

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 (); })} ) } }

El componente de broma

El Jokecomponente contendrá información sobre cada elemento de la respuesta de bromas que se mostrará.

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 ) } }

Hemos escrito nuestros componentes, así que ahora digamos a React dónde renderizar la aplicación. Agregaremos el bloque de código a continuación al final de nuestro app.jsxarchivo.

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

Reiniciemos nuestro servidor Go go run main.goy vayamos a la URL de nuestra aplicación //localhost:3000/. Verá que el Homecomponente se está renderizando.

Asegurando nuestra aplicación de chistes con Auth0

Auth0 emite tokens web JSON en cada inicio de sesión de sus usuarios. Esto significa que puede tener una infraestructura de identidad sólida, que incluye inicio de sesión único, administración de usuarios, soporte para proveedores de identidad social (Facebook, Github, Twitter, etc.), proveedores de identidad empresarial (Active Directory, LDAP, SAML, etc.) y su propia base de datos de usuarios, con solo unas pocas líneas de código.

Podemos configurar fácilmente la autenticación en nuestra aplicación GIN usando Auth0. Necesitará una cuenta para seguir esta parte. Si aún no tiene una cuenta Auth0, regístrese para obtener una ahora.

Descargo de responsabilidad: este no es contenido patrocinado.

Creando el cliente API

Nuestros tokens se generarán con Auth0, por lo que necesitamos crear una API y un Cliente desde nuestro panel de Auth0. Nuevamente, si aún no lo ha hecho, regístrese para obtener una cuenta Auth0.

Para crear una nueva API, navegue a la sección de API en su panel de control y haga clic en el botón Crear API .

Elija un nombre de API y un identificador . El identificador será la audiencia del middleware. El algoritmo de firma debe ser RS256 .

Para crear un nuevo cliente, navegue a la sección de clientes en su panel de control y haga clic en el botón Crear cliente . Seleccione el tipo Regular Web Applications.

Una vez creado el cliente, tome nota de los client_idy client_secret, ya que los necesitaremos más adelante.

Tenemos que agregar las credenciales necesarias para nuestra API a una variable de entorno. En el directorio raíz, cree un nuevo archivo .envy agregue lo siguiente, con los detalles del panel de Auth0:

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

Asegurar nuestros puntos finales de API

Actualmente, nuestra API está abierta al mundo. Necesitamos proteger nuestros puntos finales, de modo que solo los usuarios autorizados puedan acceder a ellos.

Vamos a hacer uso de un Middleware JWT para verificar un token web JSON válido de cada solicitud que llegue a nuestros puntos finales.

Creemos nuestro 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 } } }

En el código anterior, tenemos una nueva jwtMiddleWarevariable que se inicializa en la mainfunción. Se utiliza en la authMiddlewarefunción intermedia.

Si se da cuenta, estamos extrayendo nuestras credenciales del lado del servidor de una variable de entorno (uno de los principios de una aplicación de 12 factores ). Nuestro middleware verifica y recibe un token de una solicitud y llama al jwtMiddleWare.CheckJWTmétodo para validar el token enviado.

También escribamos la función para devolver las claves web JSON:

// ... 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 }

Usando el middleware JWT

Usar el middleware es muy sencillo. Simplemente lo pasamos como parámetro a nuestra definición de rutas.

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

Nuestro main.goarchivo debería verse así:

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) } }

Instalemos las jwtmiddlewarebibliotecas:

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

Obtengamos nuestro archivo de entorno y reiniciemos nuestro servidor de aplicaciones:

$ source .env $ go run main.go

Ahora, si intentamos acceder a cualquiera de los puntos finales, nos enfrentaremos a un 401 Unauthorizederror. Eso es porque necesitamos enviar un token con la solicitud.

Iniciar sesión con Auth0 y reaccionar

Implementemos un sistema de inicio de sesión para que los usuarios puedan iniciar sesión o crear cuentas y tener acceso a nuestros chistes. app.jsxAgregaremos a nuestro archivo las siguientes credenciales de Auth0:

  • AUTH0_CLIENT_ID
  • AUTH0_DOMAIN
  • AUTH0_CALLBACK_URL - La URL de su aplicación
  • AUTH0_API_AUDIENCE
Puede encontrar el AUTH0_CLIENT_ID, AUTH0_DOMAINy AUTH0_API_AUDIENCElos datos de su panel de gestión Auth0.

Necesitamos establecer a callbackqué redirecciona Auth0. Navega a la sección Clientes en tu tablero. En la configuración, configuremos la devolución de llamada a //localhost:3000:

Con las credenciales en su lugar, actualice nuestros componentes React.

Componente de la aplicación

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 ; } }

Hemos actualizado el componente de aplicación con tres métodos de componentes ( setup, parseHashy setState), y un método de ciclo de vida componentWillMount. El parseHashmétodo inicializa el auth0webAuthcliente y analiza el hash a un formato más legible, guardándolos en localSt. Para mostrar la pantalla de bloqueo, capture y almacene el token de usuario y agregue el encabezado de autorización correcto a cualquier solicitud a nuestra API

Componente de inicio

Nuestro componente Inicio se actualizará. Agregaremos la funcionalidad para el authenticatemétodo, que activará la pantalla de bloqueo alojada para mostrar y permitirá a nuestros usuarios iniciar sesión o registrarse.

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 ); } }

Componente LoggedIn

Actualizaremos el LoggedIncomponente para comunicarnos con nuestra API y sacar todos los chistes. Se pasará cada broma como propal Jokecomponente, lo que hace que un panel de bootstrap. Escribamos esos:

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 ; })} ); } }

Componente de broma

También actualizaremos el Jokecomponente para formatear cada elemento de broma que se le pase desde el componente principal ( LoggedIn). También agregaremos un likemétodo, que incrementará los gustos de una broma.

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 ) } }

Poniendolo todo junto

Con la interfaz de usuario y la API completas, podemos probar nuestra aplicación. Comenzaremos iniciando nuestro servidor source .env && go run main.goy luego navegaremos //localhost:3000desde cualquier navegador. Debería ver el Homecomponente con un botón de inicio de sesión. Al hacer clic en el botón de inicio de sesión, se redireccionará a una página de bloqueo alojada (cree una cuenta o inicie sesión) para continuar usando la aplicación.

Casa:

Pantalla de bloqueo alojada en Auth0:

Vista de aplicación conectada:

Conclusión

¡Felicidades! Ha aprendido a crear una aplicación y una API con Go y el marco Gin.

¿Me perdí algo importante? Házmelo saber en los comentarios.

Puedes saludarme en Twitter @codehakase