Desarrollo Web
Contents
Desarrollo Web#

Trabajar con Go para desarrollo de aplicaciones web.
Índice
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:
Instalar Gorilla mux:
go get -u github.com/gorilla/mux
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}