Tu Primer Servidor HTTP en Go

Por: Artiko
gohttpservidornet/httprutas

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:

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+:

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

En el siguiente capitulo reemplazaremos los strings HTML por templates Templ tipados y composables.