veb: el framework web de V que sustituye a vweb y es 4 veces más rápido

Si buscas ejemplos de servidores web en V y te encuentras código con vweb, ten cuidado con la fecha: ese módulo quedó oficialmente deprecado en la versión 0.4.10 (marzo de 2025) en favor de veb, que según el propio anuncio del proyecto es unas cuatro veces más rápido en peticiones por segundo, más estable y más simple de usar. Ambos módulos van a convivir un tiempo para no romper proyectos antiguos, pero para código nuevo la recomendación oficial es clara: usa veb.

Una app mínima

module main

import veb

pub struct Context {
	veb.Context
pub mut:
	user string
}

pub struct App {
pub:
	secret_key string
}

pub fn (app &App) index(mut ctx Context) veb.Result {
	return ctx.html('<h1>Hola desde V</h1>')
}

fn main() {
	mut app := &App{
		secret_key: 'secret'
	}
	veb.run[App, Context](mut app, 8080)
}

La firma veb.run[App, Context] usa genéricos: el primer parámetro es tu struct de aplicación (donde guardas configuración compartida entre peticiones), el segundo es el contexto por petición, que extiende veb.Context con lo que necesites (aquí, un campo user).

Rutas: por convención o explícitas

// Ruta automática: el nombre del método se convierte en /hello
pub fn (app &App) hello(mut ctx Context) veb.Result {
	return ctx.text('Hola')
}

// Ruta explícita con atributo
@['/foo']
pub fn (app &App) world(mut ctx Context) veb.Result {
	return ctx.text('Mundo')
}

// Restringir verbos HTTP
@['/login'; get; post]
pub fn (app &App) login(mut ctx Context) veb.Result {
	if ctx.req.method == .get {
		return ctx.html('<h1>Login</h1>')
	}
	return ctx.redirect('/profile')
}

// Parámetros de ruta
@['/hello/:user']
pub fn (app &App) hello_user(mut ctx Context, user string) veb.Result {
	return ctx.text('Hola ${user}')
}

El patrón de mapear el nombre del método a la ruta por defecto ahorra escribir atributos en el caso común, algo que frameworks como Axum en Rust o net/http en Go no hacen (ahí la ruta siempre es explícita).

El objeto Context: la petición y la respuesta en un sitio

// Respuestas
ctx.html('<h1>contenido</h1>')
ctx.text('texto plano')
ctx.json(data)
ctx.file('informe.pdf')

// Query string y formularios
name := ctx.query['name'] or { 'invitado' }
password := ctx.form['password']

// Cookies
token := ctx.get_cookie('token') or { '' }
ctx.set_cookie(http.Cookie{ name: 'session', value: 'abc123' })

// Errores HTTP
ctx.request_error('falta un campo obligatorio') // 400
ctx.server_error('error de base de datos')       // 500

Middleware

pub fn check_auth(mut ctx Context) bool {
	token := ctx.get_cookie('token') or { '' }
	if token.is_empty() {
		ctx.text('No autorizado')
		return false
	}
	return true
}

fn main() {
	mut app := &App{}
	app.use(handler: check_auth)
	veb.run[App, Context](mut app, 8080)
}

El middleware devuelve un booleano: si es false, la petición se corta ahí y no llega al handler. Es un modelo más simple que la cadena de next() de Express o los tower::Layer de Axum, a cambio de menos flexibilidad para componer middlewares complejos.

Servir ficheros estáticos

pub struct App {
	veb.StaticHandler
}

fn main() {
	mut app := &App{}
	app.handle_static('static', false)!
	veb.run[App, Context](mut app, 8080)
}

Para comparar con opciones más asentadas en otros lenguajes, tenemos este artículo sobre Axum en Rust, con un modelo de extractors y capas bastante más maduro. veb es joven y todavía está definiendo su superficie de API, pero para una API REST sencilla o un panel interno, ya cubre lo esencial sin dependencias externas. En el próximo artículo de la serie vemos cómo V se comunica con C: #flag, C.función() y el comando v translate.

Imagen: Pexels / cottonbro studio

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP