npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@jamx-framework/server

v1.0.0

Published

JAMX Framework — HTTP Server

Readme

@jamx-framework/server

Descripción

Servidor HTTP de JAMX Framework. Proporciona una base ligera y modular para construir aplicaciones server-side, con pipeline de middlewares, soporte para HMR (Hot Module Replacement), validación de requests, rate limiting, seguridad (CSRF, headers), y manejo de archivos estáticos. Está construido sobre node:http nativo sin dependencias externas pesadas.

Cómo funciona

El servidor implementa un modelo de pipeline de middlewares:

  1. Request → Middleware Pipeline: Cada request pasa por una cadena de middlewares
  2. Request Dispatcher: El dispatcher (ej: FileRouter) enruta al handler correcto
  3. Response: El handler envía la respuesta a través de JamxResponse
  4. Eventos: El servidor emite eventos para logging, monitoreo, HMR

Componentes principales

JamxServer (src/http/server.ts)

Servidor HTTP principal:

  • create(options): Factory method para crear instancia
  • start(): Inicia el servidor
  • stop(): Detiene gracefulmente
  • use(middleware): Añade middleware dinámicamente
  • on(event, handler): Suscribe a eventos
  • httpServer: Acceso al http.Server nativo

JamxRequest (src/http/request.ts)

Wrapper de http.IncomingMessage con utilidades:

  • method, url, path, query, params, headers
  • body: Parseado automáticamente por bodyParser
  • locals: Objeto para datos entre middlewares

JamxResponse (src/http/response.ts)

Wrapper de http.ServerResponse con API fluida:

  • status(code), header(name, value), json(body), text(body)
  • send(body), redirect(url), notFound(), noContent()
  • raw: Acceso al ServerResponse nativo

MiddlewarePipeline (src/middleware/pipeline.ts)

Gestiona la cadena de ejecución de middlewares:

  • use(middleware): Añade middleware
  • clone(): Crea copia para aislamiento por request
  • execute(req, res): Ejecuta pipeline completo

Middlewares built-in

  • bodyParser: Parse JSON, URL-encoded, text bodies
  • cors: Configura CORS headers
  • requestId: Añade X-Request-ID único
  • staticFiles: Sirve archivos estáticos
  • globalErrorHandler: Captura errores no manejados
  • rateLimit: Limita requests por IP/cliente

HMR (Hot Module Replacement)

  • HmrServer: Coordina HMR entre servidor y cliente
  • FileWatcher: Observa cambios en archivos
  • HmrWebSocketServer: WebSocket para push de cambios
  • generateClientScript: Genera script de cliente para HMR

Request Dispatcher (src/router/dispatcher.ts)

Interface para enrutadores:

  • dispatch(req, res): Método que deben implementar routers como FileRouter

Validación (src/validation/parse-body.ts)

  • parseBody(req): Parsea body según Content-Type
  • parseQuery(url): Parsea query string
  • parseParams(route, path): Extrae params de ruta

Webhooks (src/webhooks/)

  • WebhookSender: Envía webhooks con retry
  • webhookVerifier: Verifica signatures de webhooks

Uso básico

Servidor simple

import { JamxServer } from '@jamx-framework/server';

const server = await JamxServer.create();

server.use(async (req, res) => {
  res.json({ message: 'Hello World' });
});

await server.start();
console.log(`Server running on http://${server.address.host}:${server.address.port}`);

await server.stop();

Con middleware de body parsing

import { JamxServer, bodyParser } from '@jamx-framework/server';

const server = await JamxServer.create();

server.use(bodyParser()); // parsea req.body automáticamente

server.use(async (req, res) => {
  if (req.method === 'POST') {
    const data = req.body as { name: string };
    res.json({ received: data.name });
  }
});

await server.start();

Con CORS

import { JamxServer, cors } from '@jamx-framework/server';

const server = await JamxServer.create();

server.use(cors({
  origin: ['https://example.com', 'http://localhost:3000'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  headers: ['Content-Type', 'Authorization'],
}));

await server.start();

Con archivos estáticos

import { JamxServer, staticFiles } from '@jamx-framework/server';

const server = await JamxServer.create();

// Servir archivos estáticos desde /public
server.use(staticFiles({
  root: './public',
  cacheControl: 'public, max-age=3600',
}));

// API después de estáticos
server.use(async (req, res) => {
  res.json({ api: true });
});

await server.start();

Con rate limiting

import { JamxServer, rateLimit } from '@jamx-framework/server';

const server = await JamxServer.create();

server.use(rateLimit({
  max: 100, // 100 requests por ventana
  window: '1m', // 1 minuto
  keyBy: 'ip', // o 'user-id', 'session-id', etc.
}));

await server.start();

Con FileRouter

import { JamxServer, FileRouter } from '@jamx-framework/server';

const server = await JamxServer.create();

const router = new FileRouter({ projectRoot: './' });
server.use(router.dispatch.bind(router));

await server.start();

Con middleware personalizado

import { JamxServer } from '@jamx-framework/server';

const server = await JamxServer.create();

// Logging middleware
server.use(async (req, res, next) => {
  console.log(`${req.method} ${req.path}`);
  const start = Date.now();

  // Capturar respuesta
  const originalSend = res.send.bind(res);
  res.send = (body) => {
    const duration = Date.now() - start;
    console.log(`${req.method} ${req.path} ${res.statusCode} ${duration}ms`);
    return originalSend(body);
  };

  next();
});

// Auth middleware
server.use(async (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (token) {
    try {
      const user = await verifyToken(token);
      req.locals.user = user;
    } catch (err) {
      // token inválido, continuar sin usuario
    }
  }
  next();
});

// Handler principal
server.use(async (req, res) => {
  const user = req.locals.user;
  res.json({ user: user?.id ?? null });
});

await server.start();

Con error handling

import { JamxServer, globalErrorHandler } from '@jamx-framework/server';

const server = await JamxServer.create();

// Middleware que puede lanzar errores
server.use(async (req, res) => {
  if (Math.random() > 0.5) {
    throw new Error('Random error');
  }
  res.json({ ok: true });
});

// Error handler (debe ser el último)
server.use(globalErrorHandler({
  // En desarrollo, retorna stack trace
  // En producción, retorna mensaje genérico
  showStackTrace: process.env.NODE_ENV === 'development',
}));

await server.start();

Con HMR (desarrollo)

import { JamxServer, HmrServer } from '@jamx-framework/server';

const server = await JamxServer.create({
  isDev: true,
});

const hmr = new HmrServer({
  server,
  projectRoot: './',
  port: 3000,
});

await hmr.start();
await server.start();

Eventos del servidor

import { JamxServer } from '@jamx-framework/server';

const server = await JamxServer.create();

server.on('listening', (event) => {
  console.log(`Server listening on ${event.host}:${event.port}`);
});

server.on('request', (event) => {
  console.log(`${event.method} ${event.path} → ${event.status} (${event.durationMs}ms)`);
});

server.on('error', (event) => {
  console.error('Server error:', event.error);
});

server.on('stopped', () => {
  console.log('Server stopped');
});

await server.start();

Graceful shutdown

import { JamxServer } from '@jamx-framework/server';

const server = await JamxServer.create({
  shutdownTimeout: 10000, // 10 segundos
});

await server.start();

// Manejar señales de terminación
process.on('SIGTERM', async () => {
  console.log('SIGTERM received, shutting down gracefully...');
  await server.stop();
  process.exit(0);
});

process.on('SIGINT', async () => {
  console.log('SIGINT received, shutting down gracefully...');
  await server.stop();
  process.exit(0);
});

API Reference

JamxServer

Factory method

static async create(options?: ServerOptions): Promise<JamxServer>

Crea una nueva instancia del servidor.

ServerOptions:

interface ServerOptions {
  port?: number;              // Puerto (default: 3000)
  host?: string;              // Host (default: 'localhost')
  dispatcher?: RequestDispatcher; // Enrutador (default: StubDispatcher)
  middlewares?: Middleware[]; // Middlewares adicionales
  isDev?: boolean;            // Modo desarrollo (default: false)
  shutdownTimeout?: number;   // Timeout de shutdown en ms (default: 5000)
}

Métodos de ciclo de vida

start(): Promise<void>

Inicia el servidor. Retorna una promesa que se resuelve cuando el servidor está escuchando.

stop(): Promise<void>

Detiene el servidor gracefulmente. Espera a que las requests en curso terminen antes de cerrar.

Middleware management

use(middleware: Middleware): this

Añade un middleware al pipeline. Puede llamarse antes o después de start().

on(handler: ServerEventHandler): this

Suscribe un handler a eventos del servidor.

Getters

readonly isListening: boolean

true si el servidor está escuchando conexiones.

readonly address: { port: number; host: string }

Dirección en la que el servidor está escuchando.

readonly httpServer: http.Server | null

Instancia subyacente de http.Server de Node.js.

JamxRequest

Propiedades

method: string;           // Método HTTP (GET, POST, etc.)
url: string;             // URL completa con query string
path: string;            // Solo el path (sin query)
query: Record<string, string>; // Query parameters parseados
params: Record<string, string>; // Parámetros de ruta (por dispatcher)
headers: Record<string, string>; // Headers (normalizados a minúsculas)
body: unknown;           // Cuerpo parseado (por bodyParser)
locals: Record<string, unknown>; // Datos para middlewares

Métodos

getHeader(name: string): string | undefined

Obtiene un header específico (case-insensitive).

JamxResponse

Métodos de respuesta

status(code: number): this

Establece el código de estado HTTP.

header(name: string, value: string): this

Añade un header a la respuesta.

json(body: unknown, status?: number): void

Envía respuesta JSON. Establece Content-Type: application/json.

text(body: string, status?: number): void

Envía respuesta de texto plano.

send(body: string | Buffer, status?: number): void

Envía cuerpo crudo (string o Buffer).

redirect(url: string, status?: number): void

Redirige a otra URL (por defecto 302).

notFound(message?: string): void

Responde con 404 Not Found.

noContent(): void

Responde con 204 No Content.

raw: http.ServerResponse

Acceso directo al ServerResponse nativo de Node.js.

Getters

readonly sent: boolean

true si ya se envió una respuesta.

Middleware

Tipo

type Middleware = (
  req: JamxRequest,
  res: JamxResponse,
  next: NextFn
) => Promise<void> | void;

NextFn

type NextFn = () => Promise<void> | void;

Función que debe llamarse para continuar al siguiente middleware.

Ejemplo de middleware

function loggingMiddleware(req: JamxRequest, res: JamxResponse, next: NextFn) {
  console.log(`${req.method} ${req.path}`);
  next();
}

function authMiddleware(req: JamxRequest, res: JamxResponse, next: NextFn) {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) {
    res.json({ error: 'Unauthorized' }, 401);
    return;
  }
  // verificar token...
  next();
}

MiddlewarePipeline

Métodos

use(middleware: Middleware): void

Añade un middleware al final del pipeline.

clone(): MiddlewarePipeline

Crea una copia del pipeline con los mismos middlewares. Usado para aislar cada request.

execute(req: JamxRequest, res: JamxResponse): Promise<void>

Ejecuta todos los middlewares en secuencia.

RequestDispatcher

Interface

interface RequestDispatcher {
  dispatch(req: JamxRequest, res: JamxResponse): Promise<void>;
}

Cualquier router (FileRouter, StubDispatcher) implementa esta interface.

StubDispatcher

Dispatcher por defecto que responde 404 a todo. Útil para tests o como fallback.

Built-in Middlewares

bodyParser

function bodyParser(options?: {
  limit?: string | number;  // límite de tamaño (default: '1mb')
  json?: boolean;           // parsear JSON (default: true)
  urlencoded?: boolean;     // parsear URL-encoded (default: true)
  text?: boolean;           // parsear texto (default: false)
}): Middleware

Parsea el cuerpo de la request según Content-Type.

Content-Type soportados:

  • application/jsonreq.body = objeto parseado
  • application/x-www-form-urlencodedreq.body = objeto
  • text/plainreq.body = string

cors

function cors(options?: {
  origin: string | string[];  // orígenes permitidos
  methods?: string[];         // métodos permitidos
  headers?: string[];         // headers permitidos
  credentials?: boolean;      // Access-Control-Allow-Credentials
}): Middleware

Configura CORS headers.

requestId

function requestId(): Middleware

Añade header X-Request-ID con UUID único a cada request.

staticFiles

function staticFiles(options?: {
  root: string;                // directorio raíz
  cacheControl?: string;       // Cache-Control header
  index?: boolean | string;    // servir index.html por defecto
  extensions?: string[];       // extensiones a probar
}): Middleware

Sirve archivos estáticos desde disco.

globalErrorHandler

function globalErrorHandler(options?: {
  showStackTrace?: boolean;    // mostrar stack en respuesta
  logger?: (err: unknown) => void; // logger personalizado
}): Middleware

Captura errores no manejados y responde con 500.

rateLimit

function rateLimit(options?: {
  max: number;                 // max requests por ventana
  window: string | number;     // ventana de tiempo ('1m', '1h', etc.)
  keyBy: 'ip' | 'user-id' | 'session-id' | ((req) => string); // clave
  storage?: 'memory' | 'redis'; // backend de almacenamiento
}): Middleware

Limita el número de requests por cliente.

HMR

HmrServer

class HmrServer {
  constructor(options: HmrServerOptions);
  start(): Promise<void>;
  stop(): Promise<void>;
}

Coordina Hot Module Replacement.

HmrServerOptions:

interface HmrServerOptions {
  server: JamxServer;       // instancia del servidor
  projectRoot: string;      // raíz del proyecto
  port: number;             // puerto del servidor
  wsPort?: number;          // puerto WebSocket (default: port+1)
}

FileWatcher

class FileWatcher {
  constructor(projectRoot: string);
  onChange(handler: (changes: FileChange[]) => void): void;
  start(): void;
  stop(): void;
}

Observa cambios en archivos.

FileChange:

interface FileChange {
  path: string;
  kind: ChangeKind; // 'create' | 'modify' | 'delete' | 'rename'
}

HmrWebSocketServer

class HmrWebSocketServer {
  constructor(server: http.Server, port: number);
  broadcast(message: HmrMessage): void;
  start(): void;
  stop(): void;
}

WebSocket server para push de cambios a clientes.

HmrMessage:

interface HmrMessage {
  type: 'reload' | 'update' | 'error';
  path?: string;
  data?: unknown;
}

generateClientScript

function generateClientScript(options: {
  wsUrl: string;
  projectRoot: string;
  isDev: boolean;
}): string

Genera el script JavaScript que el cliente ejecuta para HMR.

Webhooks

WebhookSender

class WebhookSender {
  constructor(options: {
    url: string;
    secret?: string;          // para verificar signature
    retries?: number;         // reintentos (default: 3)
    timeout?: number;         // timeout en ms (default: 5000)
  });

  async send(payload: unknown, options?: SendWebhookOptions): Promise<WebhookDelivery>;
}

Envía webhooks con retry automático.

WebhookDelivery:

interface WebhookDelivery {
  id: string;
  status: 'pending' | 'sent' | 'failed';
  attempts: number;
  response?: http.IncomingMessage;
  error?: Error;
}

webhookVerifier

function webhookVerifier(secret: string): (req: JamxRequest) => boolean

Retorna un middleware que verifica la signature de un webhook.

Flujo interno

Ciclo de vida de una request

// 1. Servidor recibe conexión HTTP
server = http.createServer(async (rawReq, rawRes) => handleRequest(rawReq, rawRes));

// 2. handleRequest crea wrappers JAMX
const req = new JamxRequest(rawReq);
const res = new JamxResponse(rawRes);

// 3. Clonar pipeline (aislamiento por request)
const pipeline = this.pipeline.clone();

// 4. Añadir dispatcher como último middleware
pipeline.use(async (req, res) => {
  await this.dispatcher.dispatch(req, res);
});

// 5. Ejecutar pipeline
await pipeline.execute(req, res);

// 6. Si no se respondió → 404
if (!res.sent && !rawRes.writableEnded) {
  res.notFound();
}

// 7. Emitir evento de request
this.emit({
  type: 'request',
  method: req.method,
  path: req.path,
  status: rawRes.statusCode,
  durationMs: Date.now() - startTime,
});

Pipeline de middlewares

class MiddlewarePipeline {
  private middlewares: Middleware[] = [];

  use(mw: Middleware) {
    this.middlewares.push(mw);
  }

  clone(): MiddlewarePipeline {
    const clone = new MiddlewarePipeline();
    clone.middlewares = [...this.middlewares];
    return clone;
  }

  async execute(req: JamxRequest, res: JamxResponse) {
    let index = 0;

    const next = (): Promise<void> => {
      if (index >= this.middlewares.length) return Promise.resolve();

      const mw = this.middlewares[index++];
      return Promise.resolve(mw(req, res, next)).then(next);
    };

    await next();
  }
}

Construcción del pipeline

private buildPipeline(extraMiddlewares: Middleware[]): MiddlewarePipeline {
  const pipeline = new MiddlewarePipeline();

  // 1. Middlewares built-in
  pipeline.use(requestId());      // X-Request-ID
  pipeline.use(bodyParser());     // parse body

  // 2. Middlewares del usuario
  for (const mw of extraMiddlewares) {
    pipeline.use(mw);
  }

  return pipeline;
}

Request lifecycle completo

// Ejemplo con todos los componentes

const server = await JamxServer.create({
  port: 3000,
  middlewares: [
    cors({ origin: '*' }),
    rateLimit({ max: 100, window: '1m' }),
  ],
});

// 1. Request entra
// 2. requestId → añade X-Request-ID
// 3. bodyParser → parsea body
// 4. cors → configura CORS headers
// 5. rateLimit → verifica límite
// 6. user middlewares (logging, auth, etc.)
// 7. dispatcher.dispatch → enruta a handler
// 8. handler envía respuesta
// 9. Si no se respondió → 404
// 10. Emitir evento 'request'

Configuración

Opciones del servidor

const server = await JamxServer.create({
  port: 8080,
  host: '0.0.0.0',
  isDev: true,
  shutdownTimeout: 10000,
  middlewares: [customMiddleware],
  dispatcher: fileRouter,
});

Variables de entorno

  • PORT: Puerto del servidor (sobreescribe options.port)
  • HOST: Host del servidor (sobreescribe options.host)
  • NODE_ENV: Modo desarrollo/producción (afecta logs, errores)

Testing

Tests unitarios

import { JamxServer, bodyParser } from '@jamx-framework/server';
import { describe, it, expect, beforeEach, afterEach } from 'vitest';

describe('Server', () => {
  let server: JamxServer;

  beforeEach(async () => {
    server = await JamxServer.create({
      port: 0, // puerto aleatorio
    });
  });

  afterEach(async () => {
    await server.stop();
  });

  it('should start and stop', async () => {
    await server.start();
    expect(server.isListening).toBe(true);

    await server.stop();
    expect(server.isListening).toBe(false);
  });

  it('should handle requests', async () => {
    server.use(async (req, res) => {
      res.json({ method: req.method, path: req.path });
    });

    await server.start();
    const port = server.address.port;

    const response = await fetch(`http://localhost:${port}/test`);
    const data = await response.json();

    expect(data.method).toBe('GET');
    expect(data.path).toBe('/test');
  });

  it('should emit events', async () => {
    const events: any[] = [];

    server.on('listening', (e) => events.push(e));
    server.on('request', (e) => events.push(e));
    server.on('stopped', (e) => events.push(e));

    await server.start();
    await fetch(`http://localhost:${server.address.port}/ping`);
    await server.stop();

    expect(events.some(e => e.type === 'listening')).toBe(true);
    expect(events.some(e => e.type === 'request')).toBe(true);
    expect(events.some(e => e.type === 'stopped')).toBe(true);
  });
});

Tests con TestServer

import { createTestServer } from '@jamx-framework/testing';

const testServer = createTestServer();

testServer.get('/api/test', (req, res) => {
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({ ok: true }));
});

await testServer.start();
const client = new TestClient(testServer.url);

const response = await client.get('/api/test');
expect(response.status).toBe(200);
expect(response.json()).toEqual({ ok: true });

await testServer.stop();

Consideraciones de rendimiento

Pipeline optimizado

  • Los middlewares se ejecutan en secuencia, no en paralelo
  • pipeline.clone() crea una copia ligera por request (shallow copy de array)
  • No hay overhead significativo por request

Sin dependencias externas

  • Solo usa node:http nativo
  • Sin Express/Fastify → menos memoria, menos CPU
  • Ideal para serverless (Lambda, Cloudflare Workers via adapters)

HMR overhead

  • En desarrollo, HMR agrega WebSocket server y file watcher
  • En producción, HMR está deshabilitado
  • El overhead es mínimo (~1-2MB de memoria)

Body parsing

  • bodyParser carga el body completo en memoria
  • Para uploads grandes, usar staticFiles o streaming
  • Límite por defecto: 1MB (configurable)

Rate limiting

  • Por defecto usa memoria (Map)
  • Para múltiples instancias, usar Redis storage
  • Limpia automáticamente entradas expiradas

Seguridad

Headers de seguridad

El servidor no agrega security headers por defecto. Usar middleware:

import { helmet } from '@jamx-framework/security'; // (hipotético)

server.use(helmet());

O manualmente:

server.use((req, res, next) => {
  res.header('X-Content-Type-Options', 'nosniff');
  res.header('X-Frame-Options', 'DENY');
  res.header('X-XSS-Protection', '1; mode=block');
  next();
});

CSRF

No incluido en core. Usar @jamx-framework/auth o middleware externo.

Validación de input

El servidor no valida automáticamente. Usar @jamx-framework/validator:

import { validate } from '@jamx-framework/validator';

server.use(async (req, res, next) => {
  if (req.method === 'POST') {
    const result = validate(req.body, userSchema);
    if (!result.valid) {
      res.json({ errors: result.errors }, 400);
      return;
    }
    req.body = result.value; // datos validados y casteados
  }
  next();
});

Limitaciones

Sin soporte nativo para:

  • WebSockets (usar @jamx-framework/realtime con adapter)
  • HTTP/2 (solo HTTP/1.1)
  • TLS/SSL (usir reverse proxy como Nginx)
  • Sessions (usar @jamx-framework/auth con JWT o DB)

Single-threaded

  • Node.js es single-threaded
  • Para CPU-intensive tasks, usar Worker Threads o external services
  • Para I/O, Node.js es no-blocking por defecto

No incluye:

  • ORM (usar @jamx-framework/db)
  • Template engine (usar @jamx-framework/renderer)
  • Authentication (usar @jamx-framework/auth)

Integración con otros paquetes

Con @jamx-framework/router

import { FileRouter } from '@jamx-framework/router';

const router = new FileRouter({ projectRoot: './' });
const server = await JamxServer.create();
server.use(router.dispatch.bind(router));

Con @jamx-framework/config

import { ConfigManager } from '@jamx-framework/config';

const configManager = new ConfigManager();
await configManager.load('./');

const server = await JamxServer.create({
  port: configManager.section('server').port,
  host: configManager.section('server').host,
});

Con @jamx-framework/auth

import { authMiddleware } from '@jamx-framework/auth';

server.use(authMiddleware());

Con @jamx-framework/metrics

import { Metrics } from '@jamx-framework/metrics';

server.on('request', (event) => {
  Metrics.histogram('http.request.duration', event.durationMs, {
    method: event.method,
    path: event.path,
    status: event.status,
  });
});

Ejemplo completo

// server/index.ts
import { JamxServer, bodyParser, cors, rateLimit, staticFiles, requestId } from '@jamx-framework/server';
import { FileRouter } from '@jamx-framework/router';
import { ConfigManager } from '@jamx-framework/config';
import { Metrics } from '@jamx-framework/metrics';

async function main() {
  // Cargar configuración
  const configManager = new ConfigManager();
  await configManager.load('./');

  const config = configManager.get();

  // Crear servidor
  const server = await JamxServer.create({
    port: config.server?.port ?? 3000,
    host: config.server?.host ?? 'localhost',
    isDev: configManager.isDev,
    middlewares: [
      // CORS
      cors({
        origin: config.server?.cors?.origins ?? ['http://localhost:3000'],
        credentials: true,
      }),

      // Rate limiting
      rateLimit({
        max: config.security?.rateLimit?.max ?? 100,
        window: config.security?.rateLimit?.window ?? '1m',
        keyBy: 'ip',
      }),

      // Logging con métricas
      async (req, res, next) => {
        const start = Date.now();
        res.on('finish', () => {
          const duration = Date.now() - start;
          Metrics.histogram('http.request.duration', duration, {
            method: req.method,
            path: req.path,
            status: res.statusCode,
          });
        });
        next();
      },

      // Autenticación (si está configurada)
      ...(config.auth ? [authMiddleware(config.auth)] : []),
    ],
  });

  // Eventos
  server.on('listening', () => {
    console.log(`Server listening on http://${server.address.host}:${server.address.port}`);
  });

  server.on('error', (err) => {
    console.error('Server error:', err);
  });

  // Archivos estáticos
  server.use(staticFiles({
    root: './public',
    cacheControl: 'public, max-age=3600',
  }));

  // API routes con FileRouter
  const router = new FileRouter({ projectRoot: './' });
  server.use(router.dispatch.bind(router));

  // Graceful shutdown
  process.on('SIGTERM', async () => {
    console.log('SIGTERM received, shutting down...');
    await server.stop();
    process.exit(0);
  });

  process.on('SIGINT', async () => {
    console.log('SIGINT received, shutting down...');
    await server.stop();
    process.exit(0);
  });

  // Iniciar
  await server.start();
}

main().catch(console.error);

Referencia rápida

Crear servidor

const server = await JamxServer.create(options);

Middlewares comunes

server.use(bodyParser());
server.use(cors({ origin: '*' }));
server.use(rateLimit({ max: 100, window: '1m' }));
server.use(staticFiles({ root: './public' }));
server.use(requestId());
server.use(globalErrorHandler());

Añadir middleware personalizado

server.use(async (req, res, next) => {
  // lógica
  next();
});

Eventos

server.on('listening', (e) => console.log(`Listening on ${e.port}`));
server.on('request', (e) => console.log(`${e.method} ${e.path}`));
server.on('error', (e) => console.error(e.error));
server.on('stopped', () => console.log('Stopped'));

Control

await server.start();
await server.stop();

Acceso a http.Server nativo

const httpServer = server.httpServer;
// puedes añadir listeners personalizados, etc.

Archivos importantes

  • src/http/server.ts - JamxServer principal
  • src/http/request.ts - JamxRequest
  • src/http/response.ts - JamxResponse
  • src/middleware/pipeline.ts - MiddlewarePipeline
  • src/middleware/built-in/ - Middlewares incluidos
  • src/router/dispatcher.ts - RequestDispatcher interface
  • src/hmr/ - Hot Module Replacement
  • tests/unit/http/server.test.ts - Tests del servidor
  • tests/unit/middleware/ - Tests de middlewares

Dependencias

  • @jamx-framework/core - Para NotFoundException y tipos
  • @jamx-framework/cache - Para rate limit con Redis
  • @jamx-framework/config - Para configuración
  • @jamx-framework/storage - Para static files con S3
  • @jamx-framework/validator - Para validación
  • @types/node - Tipos de Node.js
  • vitest - Testing
  • rimraf - Limpieza

Scripts del paquete

  • pnpm build - Compila TypeScript a JavaScript
  • pnpm dev - Compilación en watch mode
  • pnpm test - Ejecuta tests unitarios
  • pnpm test:watch - Tests en watch mode
  • pnpm type-check - Verifica tipos sin compilar
  • pnpm clean - Limpia archivos compilados

Comparación con Express/Fastify

| Característica | JAMX Server | Express | Fastify | |----------------|-------------|---------|---------| | Peso | ~50KB | ~200KB | ~150KB | | Dependencias | Solo core | muchas | moderadas | | Pipeline | Sí (explicito) | Sí (implícito) | Sí | | TypeScript | Nativo | opcional | opcional | | HMR | Sí (integrado) | No | No | | Validación | Externa | Externa | Externa | | ORM | Externa | Externa | Externa | | Rendimiento | Muy alto | Medio | Alto | | Aprendizaje | Moderado | Fácil | Moderado |

Buenas prácticas

1. Orden de middlewares

// Correcto: bodyParser antes de handlers que usen req.body
server.use(bodyParser());
server.use(async (req, res) => {
  // req.body disponible aquí
});

// Incorrecto: bodyParser después
server.use(async (req, res) => {
  // req.body undefined aquí
});
server.use(bodyParser());

2. Error handling al final

// Middlewares primero
server.use(bodyParser());
server.use(cors());
server.use(auth);

// Error handler al final
server.use(globalErrorHandler());

3. Usar async/await

// ✅ Bien
server.use(async (req, res, next) => {
  try {
    const data = await fetchData();
    res.json(data);
  } catch (err) {
    next(err); // pasar al error handler
  }
});

// ❌ No usar callbacks
server.use((req, res, next) => {
  fetchData((err, data) => {
    if (err) return next(err);
    res.json(data);
  });
});

4. Llamar next() o responder

// ✅ Una de las dos, no ambas
server.use(async (req, res, next) => {
  // O respondes:
  res.json({ ok: true });

  // O llamas next():
  // next();
});

// ❌ No hagas ambas
server.use(async (req, res, next) => {
  res.json({ ok: true });
  next(); // error: headers ya enviados
});

5. Usar locals para datos compartidos

// Middleware de autenticación
server.use(async (req, res, next) => {
  const user = await verifyToken(req.headers.authorization);
  req.locals.user = user; // guardar para siguientes middlewares
  next();
});

// Handler
server.use(async (req, res) => {
  const user = req.locals.user; // disponible aquí
  res.json({ user });
});

Preguntas frecuentes

¿Cómo manejar 404?

El servidor responde 404 automáticamente si ningún middleware envía respuesta.

// Para custom 404, añadir middleware al final
server.use(async (req, res) => {
  res.notFound('Page not found');
});

¿Cómo manejar errores 500?

Usar globalErrorHandler al final del pipeline:

server.use(globalErrorHandler({
  showStackTrace: process.env.NODE_ENV === 'development',
}));

¿Puedo usar Express middleware?

No directamente. Los middlewares de Express tienen una API diferente (3 args vs 4 args, next como callback). Necesitas un adapter.

¿Cómo servir HTTPS?

El servidor solo soporta HTTP. Usar reverse proxy (Nginx, Caddy) o spdy/http2 externamente.

¿Cómo escalar horizontalmente?

  • Usar balanceador de carga (Nginx, HAProxy)
  • Asegurar que el estado sea externo (Redis, DB)
  • No guardar estado en memoria del servidor

¿Cómo añadir WebSocket?

El servidor no incluye WebSocket. Usar @jamx-framework/realtime:

import { WebSocketServer } from '@jamx-framework/realtime';

const wsServer = new WebSocketServer({ server });
wsServer.on('connection', (socket) => {
  socket.on('message', (msg) => {
    // manejar mensaje
  });
});

¿Cómo subir archivos?

Usar busboy o multer externamente, o el middleware multipart de JAMX (si existe).

import { multipart } from '@jamx-framework/server';

server.use(multipart({ limits: { fileSize: 10 * 1024 * 1024 } }));

server.use(async (req, res) => {
  const file = req.files?.[0];
  if (file) {
    await saveFile(file);
  }
  res.json({ ok: true });
});

¿Cómo usar sesiones?

El servidor no incluye sesiones. Usar @jamx-framework/auth con JWT o sesiones en DB.

import { session } from '@jamx-framework/auth';

server.use(session({
  secret: process.env.SESSION_SECRET,
  store: new RedisStore(),
}));

Limitaciones conocidas

No incluye:

  • Template rendering (usar @jamx-framework/renderer)
  • ORM/database (usar @jamx-framework/db)
  • Authentication (usar @jamx-framework/auth)
  • Validation (usar @jamx-framework/validator)
  • Rate limiting con Redis (solo memoria por defecto)
  • WebSockets (usar @jamx-framework/realtime)

Diseñado para:

  • APIs REST/GraphQL
  • SSR con renderer
  • Microservicios
  • Serverless functions (via adapters)

No diseñado para:

  • Aplicaciones monolíticas pesadas (usar Next.js/Nuxt)
  • WebSockets puros (usar Socket.io)
  • FTP/SFTP (usar servidores especializados)

Futuras mejoras

  • Soporte para HTTP/2
  • WebSocket integrado
  • Sesiones nativas
  • Template engine integrado
  • Metrics integradas (Prometheus endpoint)
  • Graceful shutdown mejorado (drenar conexiones)
  • Request timeout configurable
  • Compression (gzip/brotli)
  • Caching (CDN, browser)

Conclusión

@jamx-framework/server es un servidor HTTP minimalista pero potente, ideal para aplicaciones JAMX que necesitan control total sobre el stack. Su diseño modular permite añadir solo las funcionalidades necesarias, manteniendo el footprint pequeño y el rendimiento alto.