ranuts/node — mini framework HTTP
Un pequeño juego de herramientas HTTP para Node.js, sin dependencias: un servidor HTTP, un enrutador, un servidor WebSocket, middleware de cuerpo y de archivos estáticos, y un puñado de ayudas para la terminal y el sistema de archivos.
⚠️ Solo Node. Este punto de entrada carga
node:http,node:fs,node:child_processy compañía. Impórtalo desderanuts/node, nunca desde código de navegador.
Importar
import { Server, Router, staticMiddleware, body } from 'ranuts/node';El middleware que interpreta el cuerpo se exporta como
body(su nombre interno esbodyMiddleware). No existe ninguna exportación llamadabodyMiddleware.
Primeros pasos
import { Server, Router, staticMiddleware, body } from 'ranuts/node';
const app = new Server();
const router = new Router();
// Las rutas se emparejan por ruta exacta. El manejador recibe el Context de la petición.
router.get('/hello', (ctx) => {
ctx.res.setHeader('Content-Type', 'application/json');
ctx.res.end(JSON.stringify({ message: 'hello world' }));
});
// POST con cuerpo JSON: body() lo interpreta y lo deja en ctx.request.body
router.post('/echo', (ctx) => {
ctx.res.end(JSON.stringify({ youSent: ctx.request.body }));
});
// body() interpreta el cuerpo de la petición Y rellena ctx.request (method / path / url / query),
// que es lo que lee el enrutador, así que regístralo ANTES de router.routes().
app.use(body());
app.use(router.routes());
app.use(router.allowedMethods());
// Sirve archivos estáticos (para `/` recurre a ./public/index.html)
app.use(staticMiddleware({ pathname: './public' }));
const server = app.listen(3000, () => {
console.log('Server running at http://localhost:3000');
});
// `server` es la instancia de Server de node:http que hay debajo.El orden del middleware importa: el Router lee ctx.request.path y ctx.request.method, y quien los rellena es body(). Registra body() primero. El body() que viene incluido interpreta hoy por hoy cuerpos application/json y multipart/form-data.
API
Server
Exportación por defecto. Un servidor mínimo al estilo de Koa, montado sobre node:http.
| Miembro | Descripción | Tipo |
|---|---|---|
new Server() |
Crea un servidor. No recibe argumentos. | () => Server |
use(middleware) |
Añade un middleware al final de la cadena. Devuelve void, o sea que no se encadena. |
(fn: MiddlewareFunction) => void |
listen(...args) |
Se pone a escuchar. Los argumentos se pasan tal cual a http.Server.listen. Devuelve el http.Server de debajo. |
(...args) => http.Server |
middleware |
El array de middleware registrados. | MiddlewareFunction[] |
ctx |
El Context de petición compartido (su req y su res se cambian en cada petición). |
Context |
Firma del middleware
type Next = () => Promise<void> | Promise<never>;
type MiddlewareFunction = (ctx: Context, next: Next) => void | Promise<void>;Llama a next() para pasar el control al siguiente middleware. Los middleware se despachan en el orden en que se registraron; llamar a next() dos veces lanza un error.
Forma del Context
| Campo | Descripción | Tipo |
|---|---|---|
req |
La petición entrante. | http.IncomingMessage |
res |
La respuesta del servidor. Se escribe con res.setHeader, res.writeHead y res.end. |
http.ServerResponse |
ipv4() |
Devuelve la primera dirección IPv4 no interna de la máquina (o undefined). |
() => string | undefined |
request |
Lo añade body(): { method, path, url, query, body }. query es un URLSearchParams. |
object (dynamic) |
[key] |
Context es un saco abierto: cualquier middleware puede colgarle campos. |
any |
Router
Exportación por defecto. Registra manejadores por método HTTP y ruta exacta, y luego los ofrece como middleware mediante routes().
| Método | Descripción | Tipo |
|---|---|---|
new Router() |
Crea un enrutador. | () => Router |
get(url, handler) |
Registra una ruta GET. |
(url: string, h: Handler) => void |
post(url, handler) |
Registra una ruta POST. |
(url: string, h: Handler) => void |
put(url, handler) |
Registra una ruta PUT. |
(url: string, h: Handler) => void |
patch(url, handler) |
Registra una ruta PATCH. |
(url: string, h: Handler) => void |
del(url, handler) |
Registra una ruta DELETE. |
(url: string, h: Handler) => void |
head(url, handler) |
Registra una ruta HEAD. |
(url: string, h: Handler) => void |
options(url, handler) |
Registra una ruta OPTIONS. |
(url: string, h: Handler) => void |
routes() |
Devuelve un middleware que deriva al manejador que haya encajado. | () => MiddlewareFunction |
allowedMethods() |
Devuelve un middleware que responde 404, 405 o 501 cuando la ruta o el método no encajan. |
() => MiddlewareFunction |
Firma del manejador
type Handler = (ctx: Context, next: Next) => void;Lee los datos de la petición en ctx.request (method, path, url, query, body) y responde a través de ctx.res. Las rutas se emparejan de forma exacta: no hay segmentos :param; para los parámetros de consulta usa ctx.request.query.
Middleware
| Símbolo | Descripción | Tipo |
|---|---|---|
body(options?) |
Middleware que interpreta el cuerpo. Rellena ctx.request y procesa application/json y multipart/form-data. Devuelve un middleware. |
(o?: Partial<ServerBody>) => MiddlewareFunction |
staticMiddleware(opt?) |
Sirve archivos estáticos desde opt.pathname (por defecto process.cwd()); para / sirve index.html. |
(o?: Partial<Option>) => MiddlewareFunction |
connect(fn) |
Adapta un middleware al estilo de Connect o Express, (req, res, next), al middleware de este framework. |
(fn) => MiddlewareFunction |
Opciones de body(options)
| Opción | Descripción | Tipo | Por defecto |
|---|---|---|---|
uploadDir |
Directorio donde van los archivos subidos con multipart/form-data. |
string |
'.' |
encoding |
Codificación del flujo de la petición entrante. | BufferEncoding |
'utf-8'/'binary' |
json |
Interpreta los cuerpos JSON (con false se queda la cadena en crudo). |
boolean |
true |
urlencoded |
Reservado para cuerpos urlencoded. | boolean |
true |
Opciones de staticMiddleware(option)
| Opción | Descripción | Tipo | Por defecto |
|---|---|---|---|
pathname |
Directorio raíz desde el que se sirven los archivos. | string |
process.cwd() |
fileTypes |
Correspondencias extensión → tipo MIME adicionales que registrar. |
Record<string, string> |
{} |
WebSocket
| Símbolo | Descripción | Tipo |
|---|---|---|
new WSS(httpServer) |
Engancha un servidor WebSocket a un servidor node:http (se encarga del apretón de manos upgrade y del troceado en tramas). |
(server: http.Server) => WSS |
import { Server, WSS } from 'ranuts/node';
const app = new Server();
const server = app.listen(3000);
const wss = new WSS(server);
wss.on('connect', (client) => {
client.on('message', (data) => client.send('echo: ' + data));
});
// wss.broadcast(data) envía a todos los clientes conectados; wss.clients es la lista.Cada client ofrece send(data, options?), ping(), pong(), close() y socket, además de los eventos message, close y error.
Utilidades
| Símbolo | Descripción | Firma |
|---|---|---|
connect(fn) |
Adapta un middleware (req, res, next) de Connect o Express al middleware del framework. |
(fn) => MiddlewareFunction |
get({ url }) |
Hace un GET por HTTPS a un punto JSON; resuelve con { success, data, message }. |
({ url: string }) => Promise<Response> |
getIPAdress() |
La primera dirección IPv4 no interna de la máquina, o undefined. |
() => string | undefined |
paresUrl(req) |
Descompone req.url en { search, query, pathname, path, href } (ojo a cómo se escribe). |
(req: IncomingMessage) => ParseUrl | undefined |
prompt({ message }) |
Hace una pregunta de sí o no en la terminal; resuelve true ante y o yes. |
({ message, stream?, defaultResponse? }) => Promise<boolean> |
runCommand(cmd, args) |
Lanza un proceso hijo (hereda stdio); resuelve con el código de salida 0. |
(cmd: string, args: string[]) => Promise<void> |
readStream({ path }) |
Crea un fs.ReadStream para path. |
(o: { path: string, ... }) => ReadStream |
writeStream({ path }) |
Crea un fs.WriteStream para path. |
(o: { path: string, ... }) => WriteStream |
startTask() |
Arranca un cronómetro de alta resolución; devuelve un symbol opaco. |
() => symbol |
taskEnd(symbol) |
Tiempo transcurrido desde el startTask() correspondiente (en Node, nanosegundos como bigint). |
(s: symbol) => number | bigint |
traverse(dir, cb, pre?) |
Recorre dir recursivamente y llama a cb(relPath, absPath, stats) por cada archivo (asíncrono). |
(dir, cb, pre?) => Promise<any> |
traverseSync(dir, cb, pre?) |
Versión síncrona de traverse. |
(dir, cb, pre?) => void |
isColorSupported |
Booleano: si la terminal actual admite colores ANSI. | boolean |
colors |
Ayudas de color ANSI, por ejemplo colors.red('text'), más reset, bold y dim. |
Record<string, (s: string) => string> |
Véase también
Ese mismo punto de entrada ranuts/node trae además ayudas para el sistema de archivos, documentadas aparte: