Cuando se trata de servidores HTTP en Gleam, el stack habitual es Wisp más Mist. Wisp es el framework de alto nivel que gestiona rutas, requests y responses; Mist es el servidor HTTP subyacente escrito en Gleam que maneja las conexiones TCP. Los dos corren sobre la BEAM, así que heredan toda la concurrencia y tolerancia a fallos del ecosistema Erlang.
En este artículo construimos una API REST sencilla con Gleam, Wisp y Mist, viendo cómo se estructura un proyecto web real.
Crear el proyecto
gleam new mi_api cd mi_api gleam add wisp mist gleam_http gleam_json
El fichero gleam.toml resultante:
name = "mi_api" version = "1.0.0" [dependencies] gleam_stdlib = ">= 0.34.0" wisp = ">= 0.16.0" mist = ">= 1.2.0" gleam_http = ">= 3.6.0" gleam_json = ">= 1.0.0"
El punto de entrada: arrancar Mist
La función main arranca el servidor Mist y le pasa el handler de Wisp:
import gleam/erlang/process
import mist
import wisp
import mi_api/router
pub fn main() {
wisp.configure_logger()
let secret_key_base = wisp.random_string(64)
let assert Ok(_) =
wisp.mist_handler(router.handle_request, secret_key_base)
|> mist.new
|> mist.port(8000)
|> mist.start_http
process.sleep_forever()
}
El router: gestionar rutas
El router recibe cada request y devuelve un response. Wisp no usa macros de enrutamiento: el routing es pattern matching sobre el método HTTP y el path segmentado:
import wisp.{type Request, type Response}
import gleam/http.{Get, Post, Delete}
import mi_api/handlers/users
pub fn handle_request(req: Request) -> Response {
use req <- wisp.handle_head(req)
case wisp.path_segments(req) {
[] ->
wisp.html_response(wisp.Text("Bienvenido a mi API"), 200)
["users"] if req.method == Get ->
users.list_users(req)
["users"] if req.method == Post ->
users.create_user(req)
["users", id] if req.method == Get ->
users.get_user(req, id)
["users", id] if req.method == Delete ->
users.delete_user(req, id)
_ ->
wisp.not_found()
}
}
El pattern matching hace el routing explícito y legible. No hay magic strings, no hay DSL especial: es el mismo case que usarías en cualquier otra parte del código Gleam.
Handlers: leer el body y responder JSON
import wisp.{type Request, type Response}
import gleam/json
import gleam/dynamic
pub fn get_user(req: Request, id: String) -> Response {
case find_user(id) {
Ok(user) -> {
let body = json.object([
#("id", json.string(user.id)),
#("name", json.string(user.name)),
#("email", json.string(user.email)),
])
wisp.json_response(json.to_string(body), 200)
}
Error(_) ->
wisp.not_found()
}
}
pub fn create_user(req: Request) -> Response {
use body <- wisp.require_json(req)
let decoder = dynamic.decode2(
fn(name, email) { #(name, email) },
dynamic.field("name", dynamic.string),
dynamic.field("email", dynamic.string),
)
case decoder(body) {
Ok(#(name, email)) -> {
wisp.json_response("{"status":"created"}", 201)
}
Error(_) ->
wisp.unprocessable_entity()
}
}
Middleware: use y la función de continuación
Wisp usa un patrón de middleware con use y funciones de continuación. Esto permite encadenar transformaciones del request antes de llegar al handler principal:
pub fn handle_request(req: Request) -> Response {
// Middleware de logging
use req <- wisp.handle_head(req)
// Middleware de autenticación
use user <- require_auth(req)
// A partir de aquí tenemos el user autenticado
case wisp.path_segments(req) {
["profile"] -> get_profile(req, user)
_ -> wisp.not_found()
}
}
fn require_auth(req: Request, next: fn(User) -> Response) -> Response {
case get_auth_header(req) {
Ok(user) -> next(user)
Error(_) -> wisp.response(401)
}
}
Respuestas: tipos de response disponibles
Wisp ofrece funciones de conveniencia para los tipos de respuesta más habituales:
wisp.json_response(body, status) respuesta conContent-Type: application/jsonwisp.html_response(body, status) respuesta HTMLwisp.not_found() 404wisp.unprocessable_entity() 422wisp.internal_server_error() 500wisp.redirect(url) 302 redirect
Rendimiento y concurrencia
Al correr sobre la BEAM, cada request HTTP se maneja en su propio proceso ligero. No hay thread pool que configurar, no hay límite de conexiones concurrentes arbitrario: la máquina virtual gestiona miles de requests simultáneos de forma natural. Si un handler falla, el proceso muere pero el servidor sigue corriendo, y el supervisor puede reiniciarlo si es necesario.
Wisp no tiene la madurez de Phoenix todavía, pero para construir APIs REST es perfectamente funcional. Para proyectos donde la seguridad de tipos es importante, el enfoque de Gleam elimina toda una categoría de bugs que en frameworks más dinámicos solo se detectan en tests o en producción.
En el siguiente artículo de la serie vemos la gestión de errores en Gleam con Result y Option en profundidad, que es lo que usa Wisp internamente para manejar requests fallidos.
Imagen: Pexels / Digital Buggu
