Introduccion a HTMX

Por: Artiko
htmxgowebinteractividad

Capitulo 7: Introduccion a HTMX

HTMX es una libreria que permite hacer peticiones AJAX, transiciones CSS y mas directamente desde atributos HTML. La idea central: el servidor devuelve HTML, no JSON.

Que es HTMX

HTMX extiende HTML con atributos que permiten:

El modelo mental es diferente al de las SPAs: en vez de consumir APIs JSON y construir el DOM en el cliente, el servidor genera HTML parcial y HTMX lo inyecta donde indiques.

Ventajas sobre el modelo SPA

Incluir HTMX

Via CDN

<script src="https://unpkg.com/[email protected]"></script>

Archivo local (recomendado para produccion)

# Descargar y colocar en tu directorio de archivos estaticos
curl -o static/htmx.min.js https://unpkg.com/[email protected]/dist/htmx.min.js
package layouts

templ Base(title string) {
	<!DOCTYPE html>
	<html lang="es">
		<head>
			<title>{ title }</title>
		</head>
		<body>
			{ children... }
			<script src="/static/htmx.min.js"></script>
		</body>
	</html>
}

Atributos fundamentales

hx-get: hacer GET y reemplazar contenido

hx-get hace una peticion GET cuando se interactua con el elemento:

<button hx-get="/api/saludo">Cargar saludo</button>

Al hacer clic, HTMX hace GET a /api/saludo y reemplaza el contenido interno del boton con la respuesta.

Handler en Go:

func handleSaludo(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "<span>Hola desde el servidor!</span>")
}

hx-post: enviar datos sin reload

hx-post envia un formulario via POST sin recargar la pagina:

<form hx-post="/api/contacto">
    <input type="text" name="nombre" placeholder="Tu nombre"/>
    <input type="email" name="email" placeholder="[email protected]"/>
    <button type="submit">Enviar</button>
</form>

El servidor recibe los datos como un formulario normal (r.FormValue).

hx-target: donde poner la respuesta

Por defecto, HTMX reemplaza el contenido del elemento que disparo la peticion. Con hx-target controlas donde va la respuesta:

<button hx-get="/api/usuarios" hx-target="#lista-usuarios">
    Cargar usuarios
</button>

<div id="lista-usuarios">
    <!-- Aqui aparecera la respuesta -->
</div>

hx-swap: como reemplazar el contenido

hx-swap controla la estrategia de reemplazo:

ValorComportamiento
innerHTMLReemplaza el contenido interno (por defecto)
outerHTMLReemplaza el elemento completo
beforeendAgrega al final del elemento
afterbeginAgrega al inicio del elemento
beforebeginInserta antes del elemento
afterendInserta despues del elemento
deleteElimina el elemento target
noneNo hace swap (util para side effects)
<button hx-get="/api/nuevo-item" hx-target="#lista" hx-swap="beforeend">
    Agregar item
</button>

<ul id="lista">
    <li>Item existente</li>
    <!-- Los nuevos items se agregan al final -->
</ul>

Primer ejemplo completo

Un boton que carga contenido dinamico:

package pages

import "myapp/templates/layouts"

templ Home() {
	@layouts.Base("Mi App") {
		<h1>HTMX Demo</h1>
		<button hx-get="/clicked" hx-swap="outerHTML">
			Hazme clic
		</button>
	}
}
package components

templ ClickedMessage() {
	<p class="success">El boton fue clickeado! Este HTML vino del servidor.</p>
}

Handler en Go:

func handleClicked(w http.ResponseWriter, r *http.Request) {
	components.ClickedMessage().Render(r.Context(), w)
}

Al hacer clic, el boton se reemplaza por el mensaje. Sin JavaScript, sin JSON, sin DOM manipulation.

Formulario sin recargar la pagina

package pages

import "myapp/templates/layouts"

templ ContactPage() {
	@layouts.Base("Contacto") {
		<h1>Contacto</h1>
		<form hx-post="/contacto" hx-target="#resultado" hx-swap="innerHTML">
			<input type="text" name="nombre" placeholder="Nombre" required/>
			<input type="email" name="email" placeholder="Email" required/>
			<textarea name="mensaje" placeholder="Mensaje"></textarea>
			<button type="submit">Enviar</button>
		</form>
		<div id="resultado"></div>
	}
}
package components

templ ContactSuccess(nombre string) {
	<div class="alert-success">
		<p>Gracias { nombre }, tu mensaje fue enviado.</p>
	</div>
}

templ ContactError(msg string) {
	<div class="alert-error">
		<p>Error: { msg }</p>
	</div>
}

Handler:

func handleContact(w http.ResponseWriter, r *http.Request) {
	nombre := r.FormValue("nombre")
	email := r.FormValue("email")

	if nombre == "" || email == "" {
		components.ContactError("Todos los campos son requeridos").
			Render(r.Context(), w)
		return
	}

	// Procesar el contacto...
	components.ContactSuccess(nombre).Render(r.Context(), w)
}

El patron: HTML parcial, no JSON

Este es el concepto mas importante. En una API REST tradicional:

Cliente: GET /api/usuarios
Servidor: {"users": [{"name": "Ana"}, {"name": "Luis"}]}
Cliente: Parsear JSON → Crear elementos DOM → Insertar en el DOM

Con HTMX:

Cliente: GET /usuarios (via hx-get)
Servidor: <li>Ana</li><li>Luis</li>
HTMX: Inserta el HTML directamente

El servidor es responsable de generar el HTML. El cliente solo lo muestra.

Como funciona internamente

Cuando HTMX procesa una peticion:

  1. Intercepta el evento (clic, submit, etc.)
  2. Hace una peticion AJAX al URL especificado
  3. Agrega el header HX-Request: true automaticamente
  4. Recibe la respuesta HTML del servidor
  5. Inserta el HTML segun la estrategia de hx-swap

Puedes detectar si una peticion viene de HTMX en tu handler:

func handler(w http.ResponseWriter, r *http.Request) {
	isHTMX := r.Header.Get("HX-Request") == "true"

	if isHTMX {
		// Devolver solo el fragmento HTML
		components.UserList(users).Render(r.Context(), w)
		return
	}

	// Devolver la pagina completa con layout
	pages.UsersPage(users).Render(r.Context(), w)
}

Este patron permite que la misma ruta funcione tanto para navegacion normal como para peticiones HTMX.

Comparacion con fetch/JSON

Sin HTMX (JavaScript vanilla)

<button id="load-btn">Cargar datos</button>
<div id="resultado"></div>

<script>
document.getElementById('load-btn').addEventListener('click', async () => {
    const response = await fetch('/api/datos');
    const data = await response.json();
    const html = data.items.map(item =>
        `<div class="item"><h3>${item.title}</h3></div>`
    ).join('');
    document.getElementById('resultado').innerHTML = html;
});
</script>

Con HTMX

<button hx-get="/datos" hx-target="#resultado">Cargar datos</button>
<div id="resultado"></div>

Menos codigo, mas declarativo, la logica de presentacion vive en el servidor.


Anterior: Templ Avanzado | Siguiente: HTMX Avanzado