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 🙏

© 2025 – Pkg Stats / Ryan Hefner

routerworkers

v1.1.0

Published

Um roteador moderno e minimalista para Cloudflare Workers com TypeScript, response helpers, route groups, CORS e validação built-in.

Readme

RouterWorkers

npm version License: MIT TypeScript Cloudflare Workers

Um roteador moderno, minimalista e poderoso para Cloudflare Workers

InstalaçãoInício RápidoDocumentaçãoExemplos


🌟 Features

  • Zero Dependências - Bundle minimalista (~29KB)
  • TypeScript First-class - Tipos completos e inferência automática
  • Response Helpers - 14 métodos semânticos (ok, created, notFound, etc)
  • Route Groups - Organize rotas com prefixos e middlewares compartilhados
  • CORS Built-in - Middleware CORS completo e configurável
  • Validação Built-in - Validador de schemas sem dependências
  • Rotas Aninhadas - Suporte completo (/users/:id/posts/:postId)
  • Error Handlers - Tratamento customizado de erros e 404
  • Cache API - Integração nativa com Cloudflare Cache
  • Middlewares - Globais e por rota

📦 Instalação

npm install routerworkers

🚀 Início Rápido

Hello World

import { RouterWorkers } from 'routerworkers';
import type { Req, Res } from 'routerworkers';

export default {

    async fetch(request: Request): Promise<Response> {

        const app = new RouterWorkers(request);

        await app.get('/', (req: Req, res: Res) => {
            res.ok({ message: 'Hello World!' });
        });

        return app.resolve();
    }

};

API RESTful Completa

import { RouterWorkers, group, cors, validate, schemas } from 'routerworkers';

export default {

    async fetch(request: Request): Promise<Response> {

        const app = new RouterWorkers(request);

        // CORS
        await app.use(cors({ origin: 'https://app.example.com' }));

        // Error handlers
        app.onError((error, req, res) => {
            console.error(error);
            res.serverError(error.message);
        });

        app.notFound((req, res) => {
            res.notFound('Route not found');
        });

        // API v1
        await group(app, { prefix: '/api/v1' }, async (api) => {
            
            // GET /api/v1/users
            await api.get('/users',
                validate({ queries: schemas.pagination }),
                (req, res) => {
                    res.ok({ users: [] });
                }
            );

            // GET /api/v1/users/:id
            await api.get('/users/:id',
                validate({ params: { id: schemas.uuid } }),
                (req, res) => {
                    res.ok({ user: { id: req.params!.id } });
                }
            );

            // POST /api/v1/users
            await api.post('/users',
                validate({
                    body: {
                        name: { type: 'string', required: true },
                        email: { type: 'email', required: true }
                    }
                }),
                (req, res) => {
                    res.created(req.bodyJson, `/api/v1/users/${req.bodyJson.id}`);
                }
            );

            // DELETE /api/v1/users/:id
            await api.delete('/users/:id', (req, res) => {
                res.noContent();
            });
        });

        return app.resolve();
    }

};

📖 Documentação

Response Helpers

RouterWorkers oferece 14 métodos semânticos para respostas HTTP:

Success (2xx)

// 200 OK
res.ok({ users: [] });

// 201 Created
res.created({ id: '123' }, '/users/123');

// 202 Accepted
res.accepted({ jobId: '456', status: 'processing' });

// 204 No Content
res.noContent();

Client Errors (4xx)

// 400 Bad Request
res.badRequest('Email is required');

// 401 Unauthorized
res.unauthorized('Token required');

// 403 Forbidden
res.forbidden('Admin access required');

// 404 Not Found
res.notFound('User not found');

// 409 Conflict
res.conflict('Email already exists');

// 422 Unprocessable Entity
res.unprocessable([{ field: 'email', message: 'Invalid' }]);

Server Errors (5xx)

// 500 Internal Server Error
res.serverError('Something went wrong');

Custom

// JSON customizado
res.json({ custom: true }, 418);

// HTML
res.html('<h1>Hello</h1>');

// Text
res.text('Plain text');

Route Groups

Organize rotas com prefixos e middlewares compartilhados:

import { group } from 'routerworkers';

await group(app, { prefix: '/api' }, async (api) => {
    
    // GET /api/users
    await api.get('/users', handler);
    
    // POST /api/users
    await api.post('/users', handler);
});

Com Middlewares

const authMiddleware = async (req, res) => {
    if (!req.headers.get('Authorization')) {
        res.unauthorized();
    }
};

await group(app, { 
    prefix: '/api',
    middlewares: [authMiddleware]
}, async (api) => {
    // Todas as rotas aqui exigem autenticação
});

Grupos Aninhados

await group(app, { prefix: '/api' }, async (api) => {
    await api.group({ prefix: '/v1' }, async (v1) => {
        await v1.group({ prefix: '/users' }, async (users) => {
            // GET /api/v1/users
            await users.get('/', handler);
            // GET /api/v1/users/:id
            await users.get('/:id', handler);
        });
    });
});

CORS

Desenvolvimento (permite tudo)

import { corsDevMode } from 'routerworkers';

await app.use(corsDevMode());

Produção

import { corsProduction } from 'routerworkers';

await app.use(corsProduction([
    'https://example.com',
    'https://app.example.com'
]));

Customizado

import { cors } from 'routerworkers';

await app.use(cors({
    origin: 'https://example.com', // ou array ou function
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    credentials: true,
    maxAge: 86400
}));

Validação

RouterWorkers inclui um validador built-in completo:

import { validate, schemas } from 'routerworkers';

// Validação de body
await app.post('/users',
    validate({
        body: {
            name: { type: 'string', required: true, minLength: 3 },
            email: { type: 'email', required: true },
            age: { type: 'number', min: 18, max: 120 }
        }
    }),
    (req, res) => {
        // req.bodyJson já está validado
        res.created(req.bodyJson);
    }
);

// Validação de params
await app.get('/users/:id',
    validate({ params: { id: schemas.uuid } }),
    (req, res) => {
        res.ok({ user: { id: req.params!.id } });
    }
);

// Validação de queries
await app.get('/users',
    validate({ queries: schemas.pagination }),
    (req, res) => {
        const { page = 1, limit = 10 } = req.queries || {};
        res.ok({ users: [], page, limit });
    }
);

Schemas Pré-definidos

schemas.uuid         // UUID válido
schemas.email        // Email válido
schemas.url          // URL válida
schemas.pagination   // { page?: number, limit?: number }
schemas.date         // Date válida
schemas.objectId     // MongoDB ObjectId

Rotas Aninhadas

// Suporte completo a rotas aninhadas
await app.get('/users/:userId/posts/:postId', (req, res) => {
    const { userId, postId } = req.params!;
    res.ok({ userId, postId });
});

Middlewares

Global

await app.use(async (req, res) => {
    console.log(`${req.method} ${req.url}`);
});

Por Rota

const authMiddleware = async (req, res) => {
    if (!req.headers.get('Authorization')) {
        res.unauthorized();
    }
};

await app.get('/protected', authMiddleware, (req, res) => {
    res.ok({ protected: true });
});

Error Handlers

// Handler de erros customizado
app.onError((error, req, res) => {
    console.error('[ERROR]', error);
    res.serverError(error.message);
});

// Handler 404 customizado
app.notFound((req, res) => {
    res.notFound(`Route ${req.url} not found`);
});

Cache com Invalidação Automática

O RouterWorkers oferece integração nativa com a Cache API da Cloudflare, com uma solução robusta para o problema de invalidação de cache entre deploys.

O Problema: Cache Persistente

Por padrão, o cache da Cloudflare é persistente. Se você fizer um novo deploy com alterações no código, as respostas para rotas cacheadas podem continuar vindo da versão antiga (em cache), pois a chave do cache (a URL da rota) não mudou. A solução comum, mas tediosa, é adicionar manualmente um número de versão.

A Solução: Versionamento Automático

A solução ideal é usar um identificador único para cada deploy como parte da chave do cache. O RouterWorkers faz isso de forma transparente quando configurado corretamente com o ambiente da Cloudflare.

Passo 1: Configure seu wrangler.toml

Adicione a seguinte configuração ao seu wrangler.toml para que a Cloudflare injete os metadados da versão do seu Worker na variável de ambiente CF_VERSION_METADATA.

# wrangler.toml
name = "my-worker"
main = "src/index.ts"
compatibility_date = "2023-10-26"

# Habilita a injeção dos metadados da implantação
[version_metadata]
binding = "CF_VERSION_METADATA"

Passo 2: Use o ID da Versão na Configuração

No seu código, passe o ID da versão (env.CF_VERSION_METADATA.id) para a configuração do RouterWorkers. Ele será usado para criar uma chave de cache única para o deploy atual.

// src/index.ts
import { RouterWorkers } from 'routerworkers';
import type { Req, Res, ConfigWorker } from 'routerworkers';

// Defina a interface para o seu ambiente
interface Env {
    CF_VERSION_METADATA: {
        id: string;
        timestamp: string;
        tag: string;
    };
}

export default {
    async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {

        // Obtenha o ID único do deploy
        const deploymentId = env.CF_VERSION_METADATA.id;

        // Configure o cache com o ID de versionamento
        const config: ConfigWorker = {
            cache: {
                pathname: ['/data'],      // A rota que queremos cachear
                maxage: '3600',           // Tempo de vida do cache (1 hora)
                version: deploymentId     // Chave de versionamento automático!
            }
        };

        const app = new RouterWorkers(request, config);

        // Esta rota será cacheada automaticamente por deploy
        await app.get('/data', (req: Req, res: Res) => {
            console.log('Executando a lógica da rota (não veio do cache)');
            res.ok({
                message: 'Estes são dados frescos, servidos diretamente pela função.',
                deploymentId: deploymentId
            });
        });

        return app.resolve();
    }
};

Com essa configuração, a cada novo wrangler deploy, o deploymentId muda, o cache antigo é automaticamente ignorado e seu Worker servirá a nova versão, que será então cacheada.


📝 Exemplos

Veja a pasta examples/ para exemplos completos:


🛠️ Tecnologias

  • TypeScript 5.9+
  • Cloudflare Workers
  • Rollup (build)
  • Jest (testes)

📄 Licença

MIT © Heleno Salgado


🔗 Links


Feito com ❤️ para Cloudflare Workers

Se este projeto foi útil, considere dar uma ⭐ no GitHub!