Tu Primer Servidor HTTP en Go
Tu Primer Servidor HTTP en Go
net/http: todo incluido
Go incluye un paquete HTTP completo en su biblioteca estandar. No necesitas Express, Gin ni ningun framework externo para empezar.
Handler basico
Un handler es una funcion que recibe una peticion HTTP y escribe una respuesta:
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", handleHome)
fmt.Println("Servidor en http://localhost:3000")
http.ListenAndServe(":3000", nil)
}
func handleHome(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
fmt.Fprint(w, "<h1>Pagina principal</h1>")
}
Los dos parametros clave:
w http.ResponseWriter: donde escribes la respuesta (headers, body, status code)r *http.Request: la peticion entrante (URL, metodo, body, headers)
Multiples rutas
func main() {
http.HandleFunc("/", handleHome)
http.HandleFunc("/about", handleAbout)
http.HandleFunc("/contacto", handleContacto)
fmt.Println("Servidor en http://localhost:3000")
http.ListenAndServe(":3000", nil)
}
func handleHome(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "<h1>Inicio</h1><a href='/about'>Sobre mi</a>")
}
func handleAbout(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "<h1>Sobre mi</h1><a href='/'>Volver</a>")
}
func handleContacto(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "<h1>Contacto</h1>")
}
http.ServeMux: el router
http.NewServeMux() te da un router dedicado en lugar de usar el default global. Desde Go 1.22, soporta patrones con metodos y parametros:
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /", handleHome)
mux.HandleFunc("GET /about", handleAbout)
mux.HandleFunc("GET /users/{id}", handleUser)
fmt.Println("Servidor en http://localhost:3000")
http.ListenAndServe(":3000", mux)
}
func handleUser(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
fmt.Fprintf(w, "<h1>Usuario: %s</h1>", id)
}
Con Go 1.22+:
"GET /"restringe al metodo GET"/users/{id}"captura parametros de la URLr.PathValue("id")extrae el valor del parametro
Servir archivos estaticos
Para CSS, imagenes y otros archivos estaticos:
func main() {
mux := http.NewServeMux()
// Servir archivos de la carpeta "static/"
fs := http.FileServer(http.Dir("static"))
mux.Handle("/static/", http.StripPrefix("/static/", fs))
mux.HandleFunc("GET /", handleHome)
http.ListenAndServe(":3000", mux)
}
Ahora puedes referenciar CSS en tu HTML:
<link rel="stylesheet" href="/static/styles.css">
Y el archivo static/styles.css se servira automaticamente.
Leer parametros de la URL
Query params
// URL: /buscar?q=golang&page=2
func handleBuscar(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("q")
page := r.URL.Query().Get("page")
fmt.Fprintf(w, "Buscando: %s (pagina %s)", query, page)
}
Path params (Go 1.22+)
// Ruta: "GET /todos/{id}"
func handleTodo(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
fmt.Fprintf(w, "Todo ID: %s", id)
}
Metodos HTTP
En una aplicacion CRUD necesitas diferenciar entre GET, POST, PUT y DELETE. Con Go 1.22+ se definen directo en la ruta:
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /todos", handleListarTodos)
mux.HandleFunc("POST /todos", handleCrearTodo)
mux.HandleFunc("PUT /todos/{id}", handleActualizarTodo)
mux.HandleFunc("DELETE /todos/{id}", handleEliminarTodo)
http.ListenAndServe(":3000", mux)
}
Leer el body de un POST
func handleCrearTodo(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
http.Error(w, "Error parseando form", http.StatusBadRequest)
return
}
titulo := r.FormValue("title")
if titulo == "" {
http.Error(w, "Titulo requerido", http.StatusBadRequest)
return
}
fmt.Fprintf(w, "<li>%s</li>", titulo)
}
r.FormValue() lee datos enviados desde un formulario HTML (application/x-www-form-urlencoded), que es exactamente lo que HTMX envia por defecto.
Status codes y headers
func handleNoEncontrado(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound) // 404
fmt.Fprint(w, "<h1>Pagina no encontrada</h1>")
}
func handleRedirect(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/", http.StatusSeeOther) // 303
}
func handleJSON(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, `{"status": "ok"}`)
}
Ejemplo completo: mini servidor
package main
import (
"fmt"
"net/http"
)
func main() {
mux := http.NewServeMux()
// Archivos estaticos
fs := http.FileServer(http.Dir("static"))
mux.Handle("/static/", http.StripPrefix("/static/", fs))
// Rutas
mux.HandleFunc("GET /", handleHome)
mux.HandleFunc("GET /todos/{id}", handleTodo)
mux.HandleFunc("POST /todos", handleCrearTodo)
fmt.Println("Servidor en http://localhost:3000")
http.ListenAndServe(":3000", mux)
}
func handleHome(w http.ResponseWriter, r *http.Request) {
html := `<!DOCTYPE html>
<html>
<head><title>Todo App</title></head>
<body>
<h1>Mis Tareas</h1>
<form method="POST" action="/todos">
<input name="title" placeholder="Nueva tarea">
<button type="submit">Agregar</button>
</form>
</body>
</html>`
fmt.Fprint(w, html)
}
func handleTodo(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
fmt.Fprintf(w, "<h1>Todo #%s</h1>", id)
}
func handleCrearTodo(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
titulo := r.FormValue("title")
fmt.Fprintf(w, "<p>Creado: %s</p>", titulo)
}
Ejecuta:
go run main.go
Resumen
net/httpde la stdlib es suficiente para construir aplicaciones web completashttp.ServeMux(Go 1.22+) soporta metodos HTTP y parametros en la URLhttp.FileServersirve archivos estaticos (CSS, imagenes)r.FormValue()lee datos de formularios HTMLr.PathValue()extrae parametros de la URLw.WriteHeader()establece el status code
En el siguiente capitulo reemplazaremos los strings HTML por templates Templ tipados y composables.