tug v0.45.0

Go + Inertia.js v3

Páginas de React directamente desde handlers de Go, sin una API de por medio.

tug es un framework web para aplicaciones en Go cuyo frontend es Inertia.js. Los handlers renderizan páginas de React, Vue o Svelte con props tipadas, y toda la aplicación se distribuye como un único binario.

De cero a un binario
$ go install github.com/cuonggt/tug/cmd/tug@latest
$ tug new blog          # -auth for accounts
$ cd blog
$ tug dev
tug  │ serving http://localhost:8080, built in 268ms
^C
$ tug build
tug build: blog, 9.6 MB, frontend included

Cómo encaja todo

Las props de una página, y sus tipos, son un struct de Go.

Un handler renderiza una página con un struct, que el componente de la página recibe como props. tug gen escribe el TypeScript del struct, así que el componente se comprueba frente al código Go, y tug dev lo vuelve a escribir cuando ese código cambia.

main.go
type PostsIndexProps struct {
	Posts []Post                   `json:"posts"`
	Stats inertia.DeferProp[Stats] `json:"stats"`
}

var PostsIndex = tug.Page[PostsIndexProps]("Posts/Index")

func index(c *tug.Ctx) error {
	return PostsIndex.Render(c, PostsIndexProps{
		Posts: store.List(),
		Stats: inertia.Defer(store.Count), // after the page shows
	})
}
resources/js/tug/pages.ts
// Code generated by tug gen from the Go types. DO NOT EDIT.

export interface PostsIndexProps {
  posts: Post[]
  stats?: Stats
}
resources/js/pages/Posts/Index.tsx
import { Deferred, Link } from '@inertiajs/react'
import type { PageProps } from '../../tug/pages'
import { route } from '../../tug/routes'

type Props = PageProps<'Posts/Index'>

export default function Index({ posts, stats }: Props) {
  return (
    <>
      <Deferred data="stats" fallback={<p>Counting…</p>}>
        {stats && <p>{stats.posts} posts</p>}
      </Deferred>
      {posts.map((post) => (
        <Link
          key={post.id}
          href={route('posts.show', { id: post.id })}
        >
          {post.title}
        </Link>
      ))}
    </>
  )
}

Qué incluye

Las piezas que necesita una aplicación, que ya funcionan con Inertia.

Las sesiones, los mensajes flash, los errores de validación y la protección CSRF funcionan con Inertia sin configurar nada, con la biblioteca estándar por debajo.

  • Inertia v3, completo

    Props diferidas, fusionadas, opcionales y que se envían una sola vez, recargas parciales de props anidadas, scroll infinito, y el título y la descripción de una página desde Go.

  • Tipos desde Go

    tug gen escribe el TypeScript de las props de cada página, las rutas con nombre, lo que envía cada formulario y las claves de los datos flash.

  • Formularios que se validan solos

    c.BindValid llena un struct con una petición y la comprueba según las etiquetas del struct. Un formulario que no es válido vuelve con sus errores, y cada campo se comprueba al salir de él.

  • Cuentas, con una sola opción

    tug new -auth: registro, correo verificado, autenticación de dos factores, llaves de acceso, tokens de API y notificaciones, en SQLite, Postgres o MySQL.

  • Tareas en segundo plano

    Tareas que se vuelven a ejecutar si fallan, programadas en cualquier zona horaria, una sola vez por muchas veces que se encolen, y en la propia transacción de la aplicación.

  • Un solo binario

    tug build crea un único binario estático con el frontend dentro, que la propia aplicación envía comprimido con gzip, y la aplicación viene con un Dockerfile.

  • Renderizado en el servidor, si quieres

    tug new -ssr renderiza la página de una primera visita con el propio SSR de Inertia, en Node junto a la aplicación. Sin Node, las páginas se renderizan en el navegador.

  • Pruebas sin navegador

    El paquete tugtest es el cliente de Inertia para las pruebas en Go: visitas, formularios, subidas de archivos e inicios de sesión, con las props de una página leídas en su struct de Go.

  • Eventos en todas las instancias

    El paquete broadcast lleva eventos por canales, a través de la base de datos de la aplicación, a las páginas abiertas en todas las instancias, que recargan lo que cambió.

Por qué Go

Un binario pequeño que usa todos los núcleos y arranca en milisegundos.

Go convierte una aplicación web en un único binario estático, que usa todos los núcleos en un solo proceso, arranca en milisegundos y funciona con decenas de MB de memoria. tug conserva todo eso para una aplicación de Inertia: un único binario estático, con la compilación del frontend dentro, servido por el propio servidor HTTP de Go.

  • Rendimiento

    Decenas de miles de peticiones por segundo en un equipo portátil, en un proceso que usa todos los núcleos, con decenas de MB de RAM.

  • Arranque

    En milisegundos, lo que mantiene bajo el gasto en una plataforma que escala a cero.

  • Despliegue

    Un archivo que copiar, sin entorno de ejecución ni dependencias que instalar, compilado para Linux o ARM desde macOS.

  • Desarrollo

    Un lenguaje pequeño que compila rápido, con un servidor HTTP en su biblioteca estándar lo bastante bueno para producción.

Velocidad

60.061 visitas por segundo, con 19,9 MB de memoria.

Una página de Inertia, una entrada y sus comentarios, servida por una aplicación como la que crea tug new, con su sesión, CSRF y Content-Security-Policy, en un proceso que usa todos los núcleos.

  • 60.061 visitas por segundo

    El cliente de Inertia pide la página y la recibe en JSON, 99 de cada 100 veces en 4,38 ms o menos.

  • 56.146 primeras visitas por segundo

    Un navegador pide la página y recibe su HTML, 99 de cada 100 veces en 5,09 ms o menos.

  • 19,9 MB de memoria bajo carga

    6,4 MB tras arrancar, en reposo.

  • 15 ms para arrancar

    Desde que arranca el binario hasta que responde por primera vez con la página.

  • 9,6 MB para desplegar

    Un único binario estático, sin nada más que instalar.

  • 75 ns añadidos a una petición

    Lo que la App de tug añade a ServeMux, el router propio de Go, en un solo proceso.

Medido el en Apple M1 Max, 10 núcleos, macOS 27.0.1. Todas las cifras, y cómo se midieron

La guía

De una aplicación nueva a una desplegada.

18 partes, en el orden en que las necesita una aplicación. La documentación de cada paquete, en pkg.go.dev, tiene el resto de los detalles.

  1. Primeros pasos

    instalar tug, crear una aplicación, ejecutarla y añadirle una página y un formulario.

  2. Rutas y handlers

    la App, las rutas, los enlaces absolutos y firmados, los grupos, los middleware, entre ellos los de las cabeceras de seguridad, la ruta que respondió, la dirección del cliente detrás de un proxy, Ctx, archivos, descargas, streams y eventos, el binding de peticiones y los errores.

  3. Páginas

    las páginas de Inertia y sus props, la plantilla raíz y su nonce, las props que se calculan más tarde, las props compartidas, las redirecciones, las descargas y los eventos en una página, las páginas de error, Vite y las DevTools de Inertia.

  4. Renderizado en el servidor

    páginas renderizadas en el servidor para una primera visita, por Node junto a la aplicación, y el paquete ssr.

  5. Formularios y sesiones

    la validación, reglas propias de la aplicación, formularios que comprueban cada campo al salir de él, los mensajes flash, las sesiones y CSRF.

  6. Idiomas

    lo que dicen tug y la aplicación, en el idioma de la petición, a partir de un archivo por idioma, el paquete lang y tug lang.

  7. Archivos

    los archivos subidos, comprobados por su tamaño y por lo que son, guardados en el disco de la aplicación o en S3 con el paquete storage, y los enlaces a ellos, públicos o firmados.

  8. Cuentas

    el kit de inicio de autenticación, en SQLite, Postgres o MySQL, sus tokens de API y notificaciones, y los paquetes auth y mail, con copias, archivos adjuntos y un enlace para darse de baja con un clic.

  9. Migraciones

    las tablas de la base de datos, creadas y modificadas por archivos SQL que se ejecutan una vez cada uno, en orden, al arrancar la aplicación y con su comando migrate, con tug migrate new para crear el siguiente, y el paquete migrate.

  10. Autorización

    lo que un usuario puede hacer con algo, las habilidades del paquete auth, una negativa en forma de 403 que dice por qué, y lo que puede hacer el usuario de una página, en sus props.

  11. Cifrado

    la clave de la aplicación, lo que tug cifra y firma con ella, el paquete crypt, para los valores propios de la aplicación, y la rotación de la clave.

  12. Tareas en segundo plano

    el paquete queue, para el trabajo que dura más que la petición: se vuelve a ejecutar cuando falla, se ejecuta según un horario, también en una zona horaria, queda en cola una sola vez por muchas veces que se pida, o como mucho tantas a la vez o por segundo, en todas las instancias, y avisa cuando ha fallado definitivamente, y cuenta cómo ha ido cada ejecución.

  13. Caché

    el paquete cache, para lo que es lento de calcular, guardado donde lo encuentran todas las instancias de la aplicación, y bloqueos para lo que no debe ejecutarse dos veces a la vez.

  14. Difusión de eventos

    el paquete broadcast, eventos en canales llevados por la base de datos de la aplicación hasta las páginas abiertas en todas las instancias, que recargan lo que cambió.

  15. TypeScript

    los tipos que tug gen escribe a partir del código Go.

  16. Pruebas

    el paquete tugtest, el cliente de Inertia para las pruebas en Go de las páginas, los formularios, las subidas de archivos y los inicios de sesión de una aplicación, mailtest, para su correo, y sus eventos.

  17. Despliegue

    un único binario, el Dockerfile, el entorno, HTTPS servido por la propia aplicación, con certificados de Let's Encrypt, las cabeceras que dicen qué puede hacer un navegador con las páginas de la aplicación, entre ellas una Content-Security-Policy, los logs y las métricas.

  18. La CLI

    tug new, tug dev, tug gen, tug routes, tug lang, tug migrate, tug build y tug key, en detalle.

Un remolcador es pequeño y mueve barcos muchas veces más grandes que él.

Crear tu primera aplicación
tug