Desarrollo Web#

Logo GO

Trabajar con Go para desarrollo de aplicaciones web.

Crear servidor#

Creación y arranque de main#

  • Paso 1: Crear carpeta para el proyecto pruebaweb

  • Paso 2: Crear archivo main.go con el código:

 1package main
 2
 3import (
 4    "log"
 5    "net/http"
 6)
 7
 8func main() {
 9    // crear nuevo servidor:
10    server := http.ListenAndServe("localhost:8000", nil)
11
12    // analizamos si nos devuelve un error:
13    log.Fatal(server)
14}

Paso 3: Acceder a la carpeta raiz pruebaweb desde terminal y ejecutar go run main.go

Atención

El servidor se ejecutará pero nos dará error 404, eso es debido a que no tenemos rutas o handlers asignados.

Crear un handle#

Los handlers (manejadores) reciben una ruta y retornan un comportamiento a través del navegador web.

 1package main
 2
 3import (
 4    "fmt"
 5    "log"
 6    "net/http"
 7)
 8
 9// función handle que recibe un parametro para escribir una respuesta y otro para recibir información:
10func Index(rw http.ResponseWriter, r *http.Request) {
11    // imprimos al navegador un mensaje:
12    fmt.Fprintln(rw, "<h1>Hola desde GO</h1>")
13}
14
15func main() {
16    // crear una ruta con un handle para llamar una función handle:
17    http.HandleFunc("/", Index)
18
19    server := http.ListenAndServe("localhost:8000", nil)
20
21    log.Fatal(server)
22}

Nota

Dicho en otro lenguaje o framework, la función HandleFunc sería un router y la función que recibe con el write y el request sería un controlador.

Métodos Request#

 1package main
 2
 3import (
 4    "fmt"
 5    "log"
 6    "net/http"
 7)
 8
 9func Index(rw http.ResponseWriter, r *http.Request) {
10    fmt.Fprintln(rw, "<h1>Hola desde GO</h1>")
11    // el método está guardado en el parámetro r:
12    fmt.Fprintln(rw, "<h2>Por defecto se trabaja con "+r.Method+" </h2>")
13}
14
15func main() {
16    http.HandleFunc("/", Index)
17
18    server := http.ListenAndServe("localhost:8000", nil)
19
20    log.Fatal(server)
21}

Nota

No se ahondará demasiado en los métodos aquí porque más adelante se utilizarán con mux.

Error 404#

Según el sitio de desarrollo de Mozilla los código de estado son: 1. Respuestas informativas (100-199) 2. Respuestas satisfactorias (200-299) 3. Redirecciones (300-399) 4. Errores de clientes (400-499) 5. Errores del servidor (500-599)

 1package main
 2
 3import (
 4    "fmt"
 5    "log"
 6    "net/http"
 7)
 8
 9func Index(rw http.ResponseWriter, r *http.Request) {
10    fmt.Fprintln(rw, "<h1>Hola desde GO</h1>")
11    fmt.Fprintln(rw, "<h2>Por defecto se trabaja con "+r.Method+" </h2>")
12}
13
14// crear un handle para el 404:
15func Error(rw http.ResponseWriter, r *http.Request) {
16    // se utiliza la función notfound para lanzar correctamente el 404:
17    http.Error(rw, "La página no se encuentra", http.StatusNotFound)
18}
19
20func main() {
21    http.HandleFunc("/", Index)
22    // se crea una ruta para el error:
23    http.HandleFunc("/error", Error)
24
25    server := http.ListenAndServe("localhost:8000", nil)
26
27    log.Fatal(server)
28}

Nota

Se puede encontrar más información de errores en la documentación de la librería http: https://pkg.go.dev/net/http#Error

Argumentos URL#

Suponiendo que tenemos la siguiente ruta: http://localhost:8000/saludar?nombre=Guillermo&apellidos=Granados%20G%C3%B3mez

 1package main
 2
 3import (
 4    "fmt"
 5    "log"
 6    "net/http"
 7)
 8
 9func Index(rw http.ResponseWriter, r *http.Request) {
10    fmt.Fprintln(rw, "<h1>Hola desde GO</h1>")
11    fmt.Fprintln(rw, "<h2>Por defecto se trabaja con "+r.Method+" </h2>")
12}
13
14func Saludar(rw http.ResponseWriter, r *http.Request) {
15    // recuperar url (/saludar en este caso):
16    fmt.Fprintln(rw, r.URL)
17    // separar y mapear argumentos en un array:
18    fmt.Fprintln(rw, r.URL.Query())
19
20    // recuperar individualmente los parámetros:
21    nombre := r.URL.Query().Get("nombre")
22    apellidos := r.URL.Query().Get("apellidos")
23    fmt.Fprintln(rw, "Hola", nombre, apellidos)
24
25}
26
27func main() {
28    http.HandleFunc("/", Index)
29    http.HandleFunc("/saludar", Saludar)
30
31    server := http.ListenAndServe("localhost:8000", nil)
32
33    log.Fatal(server)
34}

Gorilla Mux#

Mux es una librería que se utiliza para gestionar rutas en GO:

Uso de Mux:

  1. Instalar Gorilla mux: go get -u github.com/gorilla/mux

  2. Implementar su uso:

 1package main
 2
 3import (
 4    "fmt"
 5    "log"
 6    "net/http"
 7
 8    "github.com/gorilla/mux" // se carga la libreria mux
 9)
10
11func main() {
12    // creamos el enrutador:
13    router := mux.NewRouter().StrictSlash(true) // con la función strictslash hacemos rutas amigables
14
15    // crear manejadores para rutas:
16    router.HandleFunc("/", Index)
17
18    fmt.Println("Servidor ejecutandose en http://localhost:8000")
19    server := http.ListenAndServe(":8000", router) // cargamos el router donde antes estaba nil
20
21    log.Fatal(server)
22
23}
24
25func Index(w http.ResponseWriter, r *http.Request) {
26    fmt.Fprintf(w, "Ejecutando ruta raiz desde Mux")
27}
attention::

Recuerda inicializar el proyecto con el comando go mod init nombre_proyecto

Parametros url#

 1package main
 2
 3import (
 4    "fmt"
 5    "log"
 6    "net/http"
 7
 8    "github.com/gorilla/mux"
 9)
10
11func main() {
12    router := mux.NewRouter().StrictSlash(true)
13
14    router.HandleFunc("/", Index)
15    // se crea una nueva ruta con un parametro:
16    router.HandleFunc("/saludar/{nombre}", Saludar)
17
18    fmt.Println("Servidor ejecutandose en http://localhost:8000")
19    server := http.ListenAndServe(":8000", router)
20
21    log.Fatal(server)
22
23}
24
25func Index(w http.ResponseWriter, r *http.Request) {
26    fmt.Fprintf(w, "Ejecutando ruta raiz desde Mux")
27}
28
29// se crea la función que manejará la respuesta:
30func Saludar(w http.ResponseWriter, r *http.Request) {
31    // recuperar parametros url:
32    params := mux.Vars(r)
33    // asignar el nombre recuperado a una variable:
34    nombre := params["nombre"]
35
36    fmt.Fprintf(w, "Hola %s", nombre)
37}

Server Hot Reload#

Para realizar hot reload y no tener que estar compilando cada cambio y lanzando el servidor existe la librería fresh:

  • Paso 1: ejecutar en la terminal go get github.com/pilu/fresh

  • Paso 2: ejecutar el proyecto escribiendo en terminal fresh

attention::

Recuerda inicializar el proyecto con el comando go mod init nombre_proyecto

Templates#

Configuración del proyecto#

Para trabajar con templates tenemos paquetes oficiales como Text/template o HTML/template

Estas librerías ya vienen instaladas con GO.

Cargar plantilla en ruta#

  • En la carpeta del proyecto se crea el archivo main.go:

 1package main
 2
 3import (
 4    "fmt"
 5    "html/template" // cargamos la librería template
 6    "log"
 7    "net/http"
 8)
 9
10func Index(rw http.ResponseWriter, r *http.Request) {
11    // utilizamos la librería template para cargar el archivo index.html:
12    template, err := template.ParseFiles("index.html")
13
14    if err != nil {
15        panic(err)
16    }
17    // ejecutamos el template al que pasamos responsewrite, la data (nil en este caso)
18    template.Execute(rw, nil)
19}
20
21func main() {
22    mux := http.NewServeMux()
23
24    mux.HandleFunc("/", Index)
25
26    server := http.ListenAndServe("localhost:8000", mux)
27
28    fmt.Println("Servidor corriendo en puerto 8000")
29    log.Fatal(server)
30}
  • Ahora se crea un archivo index.html en la raiz:

 1<!DOCTYPE html>
 2<html lang="es">
 3<head>
 4    <meta charset="UTF-8">
 5    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7    <title>Go Main</title>
 8</head>
 9<body>
10    <h1>Bienvenido al Game Room</h1>
11</body>
12</html>

Comunicarse con el template#

Para pasar datos al template lo hacemos cargando en el lugar donde pusimos nil:

 1package main
 2
 3package main
 4
 5import (
 6    "fmt"
 7    "html/template"
 8    "log"
 9    "net/http"
10)
11
12// se crea una estructura (siempre mayúsculas):
13type Consolas struct {
14    Marca  string
15    Modelo string
16    Lanzamiento int
17}
18
19func Index(rw http.ResponseWriter, r *http.Request) {
20    template, err := template.ParseFiles("index.html")
21
22    // crear un valor consola:
23    megadrive := Consolas{"Sega", "Mega Drive", 1989}
24
25    if err != nil {
26        panic(err)
27    }
28
29    // se carga en el lugar del nil:
30    template.Execute(rw, megadrive)
31}
32
33func main() {
34    mux := http.NewServeMux()
35
36    mux.HandleFunc("/", Index)
37
38    server := http.ListenAndServe("localhost:8000", mux)
39
40    fmt.Println("Servidor corriendo en puerto 8000")
41    log.Fatal(server)
42}
  • Ahora el template index.html puede trabajar con los datos:

 1<!DOCTYPE html>
 2<html lang="es">
 3<head>
 4    <meta charset="UTF-8">
 5    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7    <title>Go Main</title>
 8</head>
 9<body>
10    <h1>Bienvenido al Game Room</h1>
11    <!-- Se carga con llaves dobles el valor como un objeto: -->
12    <h2>Esta disponible la consola: {{ .Marca }} {{ .Modelo }}</h2>
13</body>
14</html>

Crear variables en plantillas#

Para crear variables en templates se usa la palabra reservada with:

 1<!DOCTYPE html>
 2<html lang="es">
 3<head>
 4    <meta charset="UTF-8">
 5    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7    <title>Go Main</title>
 8</head>
 9<body>
10    <h1>Bienvenido al Game Room</h1>
11    <h2>Esta disponible la consola: {{ .Marca }} {{ .Modelo }}</h2>
12
13    <!-- Crear variables: -->
14    {{ with $cosa := "algo" }}
15    <!-- Ejecutar variable algo: -->
16    {{ $cosa }}
17
18
19    <!-- Definir donde acaba la variable (bloque fijo) -->
20    {{ end }}
21</body>
22</html>

Condicional if-else#

Las condicionales en los templates se aprovechan de una serie de operadores lógicos y condicionales modificados.

Operadores Lógicos#

  • && es sustituido por and

  • || es sustituido por or

  • ! es sustituido por not

Operadores Condicionales#

  • == es sustituido por eq

  • != es sustituido por ne

  • < es sustituido por lt

  • <= es sustituido por le

  • > es sustituido por gt

  • >= es sustituido por ge

Uso de condicional#

 1<!DOCTYPE html>
 2<html lang="es">
 3<head>
 4    <meta charset="UTF-8">
 5    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7    <title>Go Main</title>
 8</head>
 9<body>
10    <h1>Bienvenido al Game Room</h1>
11    <h2>Esta disponible la consola: {{ .Marca }} {{ .Modelo }}</h2>
12
13    <!-- La condición se pone antes del dato a comparar -->
14    {{ if le .Lanzamiento 1990}}
15        <h3>La consola es de los 80</h3>
16    {{ else }}
17        <h3>La consola es de {{ .Lanzamiento }}</h3>
18    {{ end }}
19</body>
20</html>

Bucles#

En primer lugar se va a añadir un nuevo campo al struct de Consolas con un mapa de juegos:

 1package main
 2
 3import (
 4    "fmt"
 5    "html/template"
 6    "log"
 7    "net/http"
 8)
 9
10// se añade un array con juegos:
11type Consolas struct {
12    Marca       string
13    Modelo      string
14    Lanzamiento int
15    Juegos      []Juego
16}
17
18// crear la estructura del array:
19type Juego struct {
20    Titulo string
21}
22
23func Index(rw http.ResponseWriter, r *http.Request) {
24    template, err := template.ParseFiles("index.html")
25
26    // crear juegos:
27    j1 := Juego{"Sonic the Hedgehog"}
28    j2 := Juego{"Alex Kidd"}
29    j3 := Juego{"Virtua Fighter"}
30
31    // crear lista de juegos:
32    juegos := []Juego{j1, j2, j3}
33
34    // actualizar el objeto megadrive:
35    megadrive := Consolas{"Sega", "Mega Drive", 1989, juegos}
36
37    if err != nil {
38        panic(err)
39    }else {
40        template.Execute(rw, nil)
41    }
42}
43
44func main() {
45    mux := http.NewServeMux()
46
47    mux.HandleFunc("/", Index)
48
49    server := http.ListenAndServe("localhost:8000", mux)
50
51    fmt.Println("Servidor corriendo en puerto 8000")
52    log.Fatal(server)
53}

Ahora se va a recorrer el listado en el template:

 1<!DOCTYPE html>
 2<html lang="es">
 3<head>
 4    <meta charset="UTF-8">
 5    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7    <title>Go Main</title>
 8</head>
 9<body>
10    <h1>Bienvenido al Game Room</h1>
11    <h2>Esta disponible la consola: {{ .Marca }} {{ .Modelo }}</h2>
12
13    <!-- recorrer y añadir a la lista los juegos: -->
14    <ul>
15        {{ range .Juegos}}
16            <li>{{ .Titulo }}</li>
17        {{ end }}
18    </ul>
19</body>
20</html>

Funciones en plantillas#

  • En main.go se crea una función que saluda:

 1package main
 2
 3import (
 4    "fmt"
 5    "html/template"
 6    "log"
 7    "net/http"
 8)
 9
10// crear funciones:
11func Saludar(nombre string) string {
12    return "Bienvenid@ " + nombre + " al game room"
13}
14
15func Index(rw http.ResponseWriter, r *http.Request) {
16    // se crea un mapa de funciones para templates:
17    funciones := template.FuncMap{
18        "saludar": Saludar,
19    }
20
21    // cambiamos el cargador de template por new y le pasamos la función:
22    template, err := template.New("index.html").Funcs(funciones).ParseFiles("index.html")
23
24    if err != nil {
25        panic(err)
26    }else {
27        template.Execute(rw, nil)
28    }
29}
30
31func main() {
32    mux := http.NewServeMux()
33
34    mux.HandleFunc("/", Index)
35
36    server := http.ListenAndServe("localhost:8000", mux)
37
38    fmt.Println("Servidor corriendo en puerto 8000")
39    log.Fatal(server)
40}
  • Ahora a probar el template:

 1<!DOCTYPE html>
 2<html lang="es">
 3<head>
 4    <meta charset="UTF-8">
 5    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7    <title>Go Main</title>
 8</head>
 9<body>
10    <!-- se carga tal cual el nombre de la key del mapa y si lleva parametros se pone su valor entre comillas: -->
11    {{ saludar "Guillermo" }}
12</body>
13</html>

Herencia de plantillas#

Como en otros sistemas de plantillas, las plantillas pueden heredar de otras:

  • Se crea una nueva plantilla llamada por ejemplo base.html:

 1{{ define "head" }}
 2<!DOCTYPE html>
 3<html lang="es">
 4<head>
 5    <meta charset="UTF-8">
 6    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 7    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 8    <title>Go Main</title>
 9</head>
10<body>
11{{ ende }}
12
13{{define "footer"}}
14</body>
15</html>
16{{ ende }}
  • Se añade en main.go la carga de la plantilla:

 1package main
 2
 3import (
 4    "fmt"
 5    "html/template"
 6    "log"
 7    "net/http"
 8)
 9
10func Saludar(nombre string) string {
11    return "Bienvenid@ " + nombre + " al game room"
12}
13
14func Index(rw http.ResponseWriter, r *http.Request) {
15    funciones := template.FuncMap{
16        "saludar": Saludar,
17    }
18
19    // hay que pasar por parsefiles el base.html:
20    template, err := template.New("index.html").Funcs(funciones).ParseFiles("index.html", "base.html")
21
22    if err != nil {
23        panic(err)
24    }else {
25        template.Execute(rw, nil)
26    }
27}
28
29func main() {
30    mux := http.NewServeMux()
31
32    mux.HandleFunc("/", Index)
33
34    server := http.ListenAndServe("localhost:8000", mux)
35
36    fmt.Println("Servidor corriendo en puerto 8000")
37    log.Fatal(server)
38}
  • Y por último implementamos en el index.html:

1{{ template "head" }}
2<!-- se carga tal cual el nombre de la key del mapa: -->
3<h1>{{ saludar "Guillermo" }}</h1>
4{{ template "footer"}}

Manejo de errores#

Lo ideal cuando falla algo en web es manejar el error con mensajes y códigos de error web:

 1package main
 2
 3import (
 4    "fmt"
 5    "html/template"
 6    "log"
 7    "net/http"
 8)
 9
10func Index(rw http.ResponseWriter, r *http.Request) {
11    // vamos a buscar un template que no funciona:
12    template, err := template.ParseFiles("fake.html")
13
14    if err != nil {
15        // para evitar que rompa la funcionalidad controlamos el error con http.error:
16        http.Error(rw, "No es posible cargar la página", http.StatusInternalServerError)
17    } else {
18        template.Execute(rw, nil)
19    }
20}
21
22func main() {
23    mux := http.NewServeMux()
24
25    mux.HandleFunc("/", Index)
26
27    server := http.ListenAndServe("localhost:8000", mux)
28
29    fmt.Println("Servidor corriendo en puerto 8000")
30    log.Fatal(server)
31}

Rutas web#

Los enlaces funcionan con rutas relativas que apuntan al handler de Mux:

 1<!DOCTYPE html>
 2<html lang="en">
 3<head>
 4    <meta charset="UTF-8">
 5    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7    <title>Document</title>
 8</head>
 9<body>
10    <h1>Hola</h1>
11    <ul>
12        <li><a href="/">Inicio</a></li>
13        <li><a href="/otra-web">Inicio</a></li>
14    </ul>
15</body>
16</html>

Cargar archivos estáticos#

  • En la raiz del proyecto se crea una carpeta llamada static

  • En main.go preparamos la ruta a la carpeta static:

 1package main
 2
 3import (
 4    "fmt"
 5    "html/template"
 6    "log"
 7    "net/http"
 8)
 9
10func Index(rw http.ResponseWriter, r *http.Request) {
11
12    template, err := template.ParseFiles("index.html")
13
14    if err != nil {
15        http.Error(rw, "No es posible cargar la página", http.StatusInternalServerError)
16    } else {
17        template.Execute(rw, nil)
18    }
19}
20
21func main() {
22
23    // Creamos la ruta a la carpeta de estáticos:
24    staticFile := http.FileServer(http.Dir("static"))
25
26    mux := http.NewServeMux()
27
28    // creamos un handler para la ruta:
29    mux.Handle("/static/", http.StripPrefix("/static/", staticFile))
30
31    mux.HandleFunc("/", Index)
32
33    server := http.ListenAndServe("localhost:8000", mux)
34
35    fmt.Println("Servidor corriendo en puerto 8000")
36    log.Fatal(server)
37}
  • Ahora se crea dentro de la carpeta static la carpeta css y dentro un archivo main.css:

1body{
2    width:100%;
3    background-color: red;
4}
  • Por último vamos a index.html y en la cabezera se carga la hoja:

 1<!DOCTYPE html>
 2<html lang="en">
 3<head>
 4    <meta charset="UTF-8">
 5    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 6    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 7    <!-- se utiliza la ruta relativa para recuperar el css: -->
 8    <link rel="stylesheet" href="/static/css/main.css">
 9    <title>Document</title>
10</head>
11<body>
12    <h1>Hola</h1>
13    <ul>
14        <li><a href="/">Inicio</a></li>
15        <li><a href="/otra-web">Inicio</a></li>
16    </ul>
17</body>
18</html>

Modelos y CRUD#

Para comenzar a trabajar con modelos hacemos lo siguiente:

Paso 1. En la raiz crear la carpeta models Paso 2. Crear un fichero consola.go que tendrá el modelo de consolas:

 1// asignamos el nombre del paquete (directorio)
 2package models
 3
 4// se define un struct para el modelo:
 5type Consola struct { // con la sentencia json va a imprimir el nombre que asignemos evitando mayúsculas:
 6    Id          int    `json:"id"`
 7    Modelo      string `json:"modelo"`
 8    Marca       string `json:"marca"`
 9    Lanzamiento int    `json:"lanzamiento"`
10}
11
12// se define también la colección de Consolas:
13type Consolas []Consola

Crear controlador#

Procedemos a crear un directorio en raiz llamado controllers y creamos el primer controlador consola.go:

Listar Elementos#

Paso 1. Editar controllers/consola.go:

 1package controllers
 2
 3// se importan las librerías necesarias:
 4import (
 5    "encoding/json"
 6    "go_api/models"
 7    "net/http"
 8)
 9
10// ahora se crean las funciones que controlarán la respuesta:
11func ConsolaList(w http.ResponseWriter, r *http.Request) {
12    // se crea el listado de juegos:
13    consolas := models.Consolas{
14        // se crea cada uno de los juegos con su modelo:
15        models.Consola{1, "Mega Drive", "Sega", 1989},
16        models.Consola{2, "Playstation", "Sony", 1995},
17        models.Consola{3, "GameBoy", "Nintendo", 1988},
18    }
19
20    // retornamos en JSON la lista:
21    json.NewEncoder(w).Encode(consolas)
22}

Paso 2. Crear ruta en main.go:

 1package main
 2
 3import (
 4    "fmt"
 5    "go_api/controllers" // se importan los controladores
 6    "log"
 7    "net/http"
 8
 9    "github.com/gorilla/mux"
10)
11
12func main() {
13    router := mux.NewRouter().StrictSlash(true)
14
15    router.HandleFunc("/", Index)
16    router.HandleFunc("/saludar/{nombre}", Saludar)
17    // creamos una ruta pora listar peliculas y otra para pelicula por id:
18    router.HandleFunc("/consolas", controllers.ConsolaList)
19
20    fmt.Println("Servidor ejecutandose en http://localhost:8000")
21    server := http.ListenAndServe(":8000", router)
22
23    log.Fatal(server)
24
25}
26
27func Index(w http.ResponseWriter, r *http.Request) {
28    fmt.Fprintf(w, "Ejecutando ruta raiz desde Mux")
29}
30
31func Saludar(w http.ResponseWriter, r *http.Request) {
32    params := mux.Vars(r)
33    nombre := params["nombre"]
34
35    fmt.Fprintf(w, "Hola %s", nombre)
36}