UNPKG

routerworkers

Version:

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

524 lines (372 loc) 12.5 kB
# RouterWorkers <div align="center"> [![npm version](https://img.shields.io/npm/v/routerworkers.svg)](https://www.npmjs.com/package/routerworkers) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue)](https://www.typescriptlang.org/) [![Cloudflare Workers](https://img.shields.io/badge/Cloudflare-Workers-orange)](https://workers.cloudflare.com/) **Um roteador moderno, minimalista e poderoso para Cloudflare Workers** [Instalação](#-instalação) [Início Rápido](#-início-rápido) [Documentação](#-documentação) [Exemplos](#-exemplos) </div> --- ## 🌟 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 ```bash npm install routerworkers ``` --- ## 🚀 Início Rápido ### Hello World ```typescript 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 ```typescript 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) ```typescript // 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) ```typescript // 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) ```typescript // 500 Internal Server Error res.serverError('Something went wrong'); ``` #### Custom ```typescript // 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: ```typescript 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 ```typescript 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 ```typescript 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) ```typescript import { corsDevMode } from 'routerworkers'; await app.use(corsDevMode()); ``` #### Produção ```typescript import { corsProduction } from 'routerworkers'; await app.use(corsProduction([ 'https://example.com', 'https://app.example.com' ])); ``` #### Customizado ```typescript 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: ```typescript 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 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 ```typescript 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 ```typescript // 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 ```typescript await app.use(async (req, res) => { console.log(`${req.method} ${req.url}`); }); ``` #### Por Rota ```typescript 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 ```typescript // 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](https://developers.cloudflare.com/workers/runtime-apis/cache/) 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`. ```toml # 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. ```typescript // 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/`](./examples) para exemplos completos: - [Fase 1 - Rotas Aninhadas](./examples/fase1-example.ts) - [Fase 2A - Validação](./examples/fase2a-example.ts) - [Fase 3 - Response Helpers + Groups + CORS](./examples/fase3-example.ts) --- ## 🛠️ Tecnologias - TypeScript 5.9+ - Cloudflare Workers - Rollup (build) - Jest (testes) --- ## 📄 Licença MIT © [Heleno Salgado](https://github.com/HelenoSalgado) --- ## 🔗 Links - [GitHub](https://github.com/HelenoSalgado/routerworkers) - [npm](https://www.npmjs.com/package/routerworkers) - [Issues](https://github.com/HelenoSalgado/routerworkers/issues) --- <div align="center"> **Feito com ❤️ para Cloudflare Workers** Se este projeto foi útil, considere dar uma no GitHub! </div>