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

@varbyte/nest-worker

v0.1.0

Published

NestJS-inspired micro framework for Cloudflare Workers with D1 support

Readme

nest-worker 🪺

Mini framework estilo NestJS para Cloudflare Workers con soporte nativo para D1.


Características

  • Decoradores@Controller, @Get, @Post, @Body, @Param, @D1, etc.
  • Módulos — organiza tu app en módulos con @Module
  • Inyección de dependencias@Injectable + constructor injection
  • D1 integrado@D1() inyecta el binding, D1Repository y QueryBuilder listos para usar
  • Middlewares — CORS, logger, rate limiting, bearer auth incluidos
  • Excepciones HTTPNotFoundException, BadRequestException, etc.
  • Cero dependencias en runtime — solo reflect-metadata

Inicio rápido

1. Instalar dependencias

npm install reflect-metadata
npm install -D typescript wrangler @cloudflare/workers-types

2. Configurar tsconfig.json

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "types": ["@cloudflare/workers-types"]
  }
}

3. Crear tu Worker

// worker.ts
import 'reflect-metadata';
import { Module, createApplication, cors } from './src/index';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';

@Module({
  controllers: [UsersController],
  providers: [UsersService],
})
class AppModule {}

const app = createApplication(AppModule);
app.use(cors());

export default app.handler;

4. Configurar wrangler.toml

name = "my-nest-worker"
main = "worker.ts"
compatibility_date = "2024-01-01"
compatibility_flags = ["nodejs_compat"]

[[d1_databases]]
binding = "DB"
database_name = "my-app-db"
database_id = "YOUR_D1_DATABASE_ID"

Decoradores

Módulos

@Module({
  imports: [OtherModule],      // importar otros módulos
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],     // opcional
})
class AppModule {}

Controladores y rutas

@Controller('users')           // prefijo de ruta → /users
export class UsersController {
  constructor(private svc: UsersService) {}

  @Get()                       // GET /users
  getAll() { ... }

  @Get(':id')                  // GET /users/:id
  getOne(@Param('id') id: string) { ... }

  @Post()                      // POST /users
  create(@Body() body: CreateUserDto) { ... }

  @Put(':id')                  // PUT /users/:id
  update(@Param('id') id: string, @Body() body: UpdateUserDto) { ... }

  @Delete(':id')               // DELETE /users/:id
  remove(@Param('id') id: string) { ... }
}

Parámetros de handler

| Decorador | Descripción | |-----------|-------------| | @Body() | Body completo del request (JSON) | | @Body('campo') | Un campo específico del body | | @Param('id') | Path parameter | | @Query('page') | Query string parameter | | @Headers('authorization') | Header específico | | @Req() | Request completo | | @D1() | Binding D1 (env.DB por defecto) | | @D1('MY_DB') | Binding D1 con clave personalizada | | @Env() | Objeto env completo | | @Env('MY_SECRET') | Variable de entorno específica |

Servicios

@Injectable()
export class UsersService {
  async findAll(db: D1Database) {
    const repo = new D1Repository<User>(db, 'users');
    return repo.findAll();
  }
}

D1 — Base de datos

D1Repository

Clase base con operaciones CRUD listas para usar:

const repo = new D1Repository<User>(db, 'users');

// CRUD básico
await repo.findAll();
await repo.findById(1);
await repo.findWhere({ role: 'admin' });
await repo.findOneWhere({ email: '[email protected]' });
await repo.create({ name: 'Alice', email: '[email protected]', role: 'user' });
await repo.update(1, { name: 'Alice Updated' });
await repo.delete(1);
await repo.count({ role: 'admin' });

// Queries personalizadas
await repo.raw('SELECT * FROM users WHERE created_at > ?', '2024-01-01');
await repo.rawFirst('SELECT * FROM users WHERE email = ?', '[email protected]');

QueryBuilder

Para queries más complejas con interfaz fluida:

import { QueryBuilder } from './src/index';

const users = await new QueryBuilder<User>(db, 'users')
  .select('id', 'name', 'email')
  .where('role', 'admin')
  .where('name', 'A%', 'LIKE')
  .orderBy('created_at', 'DESC')
  .limit(10)
  .offset(20)
  .all();

const count = await new QueryBuilder(db, 'users')
  .where('role', 'admin')
  .count();

Inyectar D1 en controladores

@Get()
async getAll(@D1() db: D1Database) {
  // db = env.DB automáticamente
  const repo = new D1Repository(db, 'users');
  return repo.findAll();
}

// Con binding personalizado
@Get()
async getData(@D1('ANALYTICS_DB') db: D1Database) {
  // db = env.ANALYTICS_DB
}

Middlewares

Globales (en la app)

app
  .use(logger())
  .use(cors({ origin: 'https://mi-dominio.com' }))
  .use(rateLimit({ windowMs: 60_000, max: 100 }));

Por controlador o ruta

@Controller('admin')
@UseMiddleware(bearerAuth({ tokenEnvKey: 'ADMIN_TOKEN' }))  // aplica a todas las rutas
export class AdminController {

  @Delete(':id')
  @UseMiddleware(bearerAuth({ staticToken: 'super-secret' }))  // solo esta ruta
  remove(@Param('id') id: string) { ... }
}

Middlewares disponibles

// CORS
cors({ origin: '*', methods: ['GET', 'POST'], credentials: false })

// Logger (consola)
logger()

// Rate limiting por IP
rateLimit({ windowMs: 60_000, max: 60 })

// Bearer Token auth
bearerAuth({ tokenEnvKey: 'API_SECRET' })   // lee env.API_SECRET
bearerAuth({ staticToken: 'mi-token' })     // token fijo (dev only)

Middleware personalizado

import { MiddlewareFn } from './src/index';

const myMiddleware: MiddlewareFn = async (req, env) => {
  const token = req.headers.get('X-Api-Key');
  if (!token) {
    return new Response('No autorizado', { status: 401 });
  }
  // Si no retorna Response, continúa al siguiente middleware/handler
};

Excepciones HTTP

import {
  NotFoundException,
  BadRequestException,
  UnauthorizedException,
  ForbiddenException,
  ConflictException,
  InternalServerErrorException,
  HttpException,
} from './src/index';

// En cualquier handler o servicio
throw new NotFoundException('Usuario no encontrado');
throw new BadRequestException('Email inválido', { field: 'email' });
throw new HttpException('Error custom', 422);

// El framework las captura y responde automáticamente con el status correcto

Respuestas del handler

El framework convierte automáticamente lo que retornas:

| Retorno | Respuesta | |---------|-----------| | objeto / array | 200 JSON | | string | 200 text/plain | | undefined / null | 204 No Content | | Response | Se usa tal cual | | throw HttpException | Status correspondiente en JSON |


Comandos D1

# Crear base de datos
wrangler d1 create my-app-db

# Ejecutar migración
wrangler d1 execute my-app-db --file=./migrations/001_init.sql

# Seed de datos
wrangler d1 execute my-app-db --file=./migrations/002_seed.sql

# Query directa
wrangler d1 execute my-app-db --command="SELECT * FROM users"

# Dev local (D1 incluido)
wrangler dev

# Deploy
wrangler deploy

# Secretos
wrangler secret put API_SECRET

Estructura de proyecto recomendada

my-worker/
├── src/                    # Framework (esta librería)
├── migrations/
│   ├── 001_init.sql
│   └── 002_seed.sql
├── modules/
│   ├── users/
│   │   ├── users.controller.ts
│   │   ├── users.service.ts
│   │   └── users.module.ts
│   └── auth/
│       ├── auth.controller.ts
│       ├── auth.service.ts
│       └── auth.module.ts
├── worker.ts               # Entrypoint principal
├── wrangler.toml
├── tsconfig.json
└── package.json

Ejemplo con módulos separados

// modules/users/users.module.ts
@Module({
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}

// worker.ts
@Module({
  imports: [UsersModule, AuthModule],
})
class AppModule {}

Licencia

MIT