{"id":50731446,"url":"https://github.com/btwld/nest-puppeteer","last_synced_at":"2026-06-10T09:01:38.690Z","repository":{"id":362655956,"uuid":"1204096539","full_name":"btwld/nest-puppeteer","owner":"btwld","description":"Puppeteer provider for NestJS with Cloudflare Browser Rendering-compatible REST API. PDF, screenshot, scrape, crawl, and more","archived":false,"fork":false,"pushed_at":"2026-05-11T21:08:10.000Z","size":209,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-05T11:04:13.062Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@bitwild/nest-puppeteer","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/btwld.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-07T17:28:45.000Z","updated_at":"2026-05-11T21:08:06.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/btwld/nest-puppeteer","commit_stats":null,"previous_names":["btwld/nest-puppeteer"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/btwld/nest-puppeteer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/btwld%2Fnest-puppeteer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/btwld%2Fnest-puppeteer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/btwld%2Fnest-puppeteer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/btwld%2Fnest-puppeteer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/btwld","download_url":"https://codeload.github.com/btwld/nest-puppeteer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/btwld%2Fnest-puppeteer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34144680,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-10T02:00:07.152Z","response_time":89,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-10T09:01:37.826Z","updated_at":"2026-06-10T09:01:38.679Z","avatar_url":"https://github.com/btwld.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @bitwild/nest-puppeteer\n\nPuppeteer provider for NestJS with a high-level API inspired by the [Cloudflare Browser Rendering](https://developers.cloudflare.com/browser-rendering/) REST API.\n\n## Features\n\n- **Service layer** -- `PuppeteerService` with 7 Cloudflare-aligned methods\n- **REST layer** -- Auto-generated endpoints with configurable prefix, guards, and feature selection\n- **Feature modules** -- Standalone modules for individual features (`PdfBrowserModule`, `ScreenshotBrowserModule`, etc.)\n- **DI decorators** -- `@InjectBrowser()`, `@InjectContext()`, `@InjectPage()` for direct Puppeteer access\n- **Validation** -- DTOs with class-validator, registered via `APP_PIPE`\n- **Swagger** -- OpenAPI decorators on all endpoints and DTOs\n- **Error handling** -- Cloudflare-format error responses via `APP_FILTER`\n- **Testing** -- `createMockPuppeteerProviders()` for unit tests\n\n## Installation\n\n```bash\nnpm install @bitwild/nest-puppeteer puppeteer\n```\n\nFor REST endpoints (optional):\n\n```bash\nnpm install class-validator class-transformer @nestjs/swagger\n```\n\n## Quick Start\n\n### Service-only (no REST endpoints)\n\n```ts\nimport { Module } from '@nestjs/common';\nimport { PuppeteerModule } from '@bitwild/nest-puppeteer';\n\n@Module({\n  imports: [PuppeteerModule.forRoot()],\n})\nexport class AppModule {}\n```\n\nThen inject the service:\n\n```ts\nimport { Injectable } from '@nestjs/common';\nimport { PuppeteerService } from '@bitwild/nest-puppeteer';\n\n@Injectable()\nexport class ReportService {\n  constructor(private readonly puppeteer: PuppeteerService) {}\n\n  async generateInvoice(url: string): Promise\u003cBuffer\u003e {\n    return this.puppeteer.pdf({\n      url,\n      format: 'a4',\n      printBackground: true,\n      margin: { top: '1cm', bottom: '1cm', left: '1cm', right: '1cm' },\n    });\n  }\n\n  async getPageContent(url: string): Promise\u003cstring\u003e {\n    return this.puppeteer.content({\n      url,\n      waitForSelector: { selector: '#main-content', visible: true },\n    });\n  }\n}\n```\n\n### REST API endpoints\n\nExpose Cloudflare-compatible HTTP endpoints:\n\n```ts\nimport { Module } from '@nestjs/common';\nimport { PuppeteerModule } from '@bitwild/nest-puppeteer';\nimport { AuthGuard } from './auth.guard';\n\n@Module({\n  imports: [\n    PuppeteerModule.forRoot({\n      headless: true,\n      rest: {\n        prefix: 'browser-rendering',\n        features: ['content', 'screenshot', 'pdf', 'markdown', 'scrape', 'links'],\n        guards: [AuthGuard],\n      },\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\nThis registers:\n\n| Method | Path | Description |\n|--------|------|-------------|\n| POST | `/browser-rendering/content` | Fetch rendered HTML |\n| POST | `/browser-rendering/screenshot` | Capture screenshot (binary) |\n| POST | `/browser-rendering/pdf` | Generate PDF (binary) |\n| POST | `/browser-rendering/markdown` | Extract Markdown |\n| POST | `/browser-rendering/snapshot` | HTML + screenshot in one call |\n| POST | `/browser-rendering/scrape` | Scrape elements by CSS selectors |\n| POST | `/browser-rendering/links` | Extract all links |\n\n**Request example:**\n\n```bash\ncurl -X POST http://localhost:3000/browser-rendering/pdf \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"url\": \"https://example.com\", \"format\": \"a4\", \"printBackground\": true}' \\\n  --output document.pdf\n```\n\n**JSON response format** (content, markdown, scrape, links):\n\n```json\n{\n  \"success\": true,\n  \"result\": { \"html\": \"...\" }\n}\n```\n\n**Error response format:**\n\n```json\n{\n  \"success\": false,\n  \"errors\": [{ \"code\": 400, \"message\": \"Either \\\"url\\\" or \\\"html\\\" must be provided\" }]\n}\n```\n\n### Async configuration\n\n```ts\nimport { Module } from '@nestjs/common';\nimport { ConfigModule, ConfigService } from '@nestjs/config';\nimport { PuppeteerModule } from '@bitwild/nest-puppeteer';\n\n@Module({\n  imports: [\n    PuppeteerModule.forRootAsync({\n      imports: [ConfigModule],\n      useFactory: (config: ConfigService) =\u003e ({\n        launchOptions: {\n          headless: config.get('PUPPETEER_HEADLESS', true),\n          args: config.get('PUPPETEER_ARGS', '').split(',').filter(Boolean),\n        },\n      }),\n      inject: [ConfigService],\n      rest: {\n        prefix: 'api/browser',\n        features: ['pdf', 'screenshot'],\n        guards: [AuthGuard],\n      },\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n## Feature Modules\n\nPre-configured modules for individual features with their own defaults, REST endpoint, and guards.\n\n### Standalone (single-feature app)\n\n```ts\nimport { Module } from '@nestjs/common';\nimport { PdfBrowserModule } from '@bitwild/nest-puppeteer';\n\n@Module({\n  imports: [\n    PdfBrowserModule.forRoot({\n      launchOptions: { headless: true },\n      defaults: {\n        format: 'a4',\n        printBackground: true,\n        margin: { top: '1cm', bottom: '1cm', left: '1cm', right: '1cm' },\n      },\n      prefix: 'api/pdf',  // POST /api/pdf\n      guards: [AuthGuard],\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n### Multi-feature (shared browser)\n\n```ts\nimport { Module } from '@nestjs/common';\nimport {\n  PuppeteerModule,\n  PdfBrowserModule,\n  ScreenshotBrowserModule,\n  ScrapeBrowserModule,\n} from '@bitwild/nest-puppeteer';\n\n@Module({\n  imports: [\n    // Shared browser\n    PuppeteerModule.forRoot({ headless: true }),\n\n    // Feature modules with individual config\n    PdfBrowserModule.register({\n      defaults: { format: 'a4', printBackground: true },\n      prefix: 'api/pdf',\n      guards: [AuthGuard],\n    }),\n    ScreenshotBrowserModule.register({\n      defaults: { fullPage: true, type: 'png' },\n      prefix: 'api/screenshot',\n      guards: [AuthGuard],\n    }),\n    ScrapeBrowserModule.register({\n      prefix: 'api/scrape',\n      guards: [AuthGuard],\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n### Feature services\n\nEach feature module provides a dedicated service:\n\n| Module | Service | Method |\n|--------|---------|--------|\n| `PdfBrowserModule` | `PdfBrowserService` | `.generate(options)` |\n| `ScreenshotBrowserModule` | `ScreenshotBrowserService` | `.capture(options)` |\n| `ContentBrowserModule` | `ContentBrowserService` | `.fetch(options)` |\n| `MarkdownBrowserModule` | `MarkdownBrowserService` | `.extract(options)` |\n| `SnapshotBrowserModule` | `SnapshotBrowserService` | `.take(options)` |\n| `ScrapeBrowserModule` | `ScrapeBrowserService` | `.scrape(options)` |\n| `LinksBrowserModule` | `LinksBrowserService` | `.extract(options)` |\n\nFeature services merge module defaults with per-call options:\n\n```ts\n@Injectable()\nexport class InvoiceService {\n  constructor(private readonly pdf: PdfBrowserService) {}\n\n  async generate(url: string): Promise\u003cBuffer\u003e {\n    // Module defaults (format: a4, margins, etc.) applied automatically\n    return this.pdf.generate({ url });\n  }\n\n  async generateLandscape(url: string): Promise\u003cBuffer\u003e {\n    // Override specific defaults per call\n    return this.pdf.generate({ url, landscape: true });\n  }\n}\n```\n\n## Low-level Access\n\nInject Puppeteer primitives directly:\n\n```ts\nimport { Injectable } from '@nestjs/common';\nimport { InjectBrowser, InjectPage } from '@bitwild/nest-puppeteer';\nimport { Browser, Page } from 'puppeteer';\n\n@Injectable()\nexport class CustomService {\n  constructor(\n    @InjectBrowser() private readonly browser: Browser,\n    @InjectPage() private readonly page: Page,\n  ) {}\n\n  async doCustomWork() {\n    const page = await this.browser.newPage();\n    try {\n      await page.goto('https://example.com');\n      // ... custom puppeteer logic\n    } finally {\n      await page.close();\n    }\n  }\n}\n```\n\n### Named pages with forFeature\n\n```ts\n@Module({\n  imports: [PuppeteerModule.forFeature(['crawler', 'renderer'])],\n})\nexport class CrawlerModule {}\n\n// Then inject:\n@Injectable()\nexport class CrawlerService {\n  constructor(\n    @InjectPage('crawler') private readonly crawlerPage: Page,\n    @InjectPage('renderer') private readonly rendererPage: Page,\n  ) {}\n}\n```\n\n### Multiple browser instances\n\n```ts\n@Module({\n  imports: [\n    PuppeteerModule.forRoot({ headless: true }, 'chrome'),\n    PuppeteerModule.forRoot({ headless: true }, 'stealth'),\n  ],\n})\nexport class AppModule {}\n\n// Inject specific instances:\n@Injectable()\nexport class MyService {\n  constructor(\n    @InjectBrowser('chrome') private readonly chrome: Browser,\n    @InjectBrowser('stealth') private readonly stealth: Browser,\n  ) {}\n}\n```\n\n## PuppeteerService API\n\nAll methods accept a common set of options plus method-specific fields. Options are flat (not nested) to match the Cloudflare API.\n\n### Common options\n\n```ts\ninterface CommonBrowserOptions {\n  url?: string;                              // URL to navigate to\n  html?: string;                             // HTML to render directly\n  authenticate?: { username, password };     // HTTP Basic Auth\n  cookies?: CookieParam[];                   // Cookies to set\n  gotoOptions?: { waitUntil, timeout };      // Navigation behavior\n  setExtraHTTPHeaders?: Record\u003cstring, string\u003e;\n  rejectResourceTypes?: ResourceType[];      // Block resource types\n  rejectRequestPattern?: string[];           // Block URL patterns (regex)\n  allowResourceTypes?: ResourceType[];       // Allow only these types\n  allowRequestPattern?: string[];            // Allow only these patterns\n  userAgent?: string;\n  waitForSelector?: { selector, timeout?, visible? };\n  waitForTimeout?: number;                   // Static delay (ms)\n  viewport?: { width, height, deviceScaleFactor };\n  addScriptTag?: { url?, content? }[];\n  addStyleTag?: { url?, content? }[];\n  setJavaScriptEnabled?: boolean;\n  emulateMediaType?: string;                 // 'screen' | 'print'\n}\n```\n\n### content(options)\n\n```ts\nconst html = await puppeteerService.content({\n  url: 'https://example.com',\n  waitForSelector: { selector: '#app', visible: true },\n});\n```\n\n### screenshot(options)\n\n```ts\nconst buffer = await puppeteerService.screenshot({\n  url: 'https://example.com',\n  fullPage: true,\n  type: 'png',\n  quality: 90,            // jpeg/webp only\n  omitBackground: true,\n  selector: '#chart',     // screenshot a specific element\n});\n```\n\n### pdf(options)\n\n```ts\nconst buffer = await puppeteerService.pdf({\n  url: 'https://example.com',\n  format: 'a4',\n  landscape: true,\n  printBackground: true,\n  scale: 0.8,\n  margin: { top: '2cm', bottom: '2cm', left: '1cm', right: '1cm' },\n  displayHeaderFooter: true,\n  headerTemplate: '\u003cdiv style=\"font-size:10px\"\u003eHeader\u003c/div\u003e',\n  footerTemplate: '\u003cdiv style=\"font-size:10px\"\u003ePage \u003cspan class=\"pageNumber\"\u003e\u003c/span\u003e\u003c/div\u003e',\n});\n```\n\n### markdown(options)\n\n```ts\nconst md = await puppeteerService.markdown({\n  url: 'https://example.com/article',\n  rejectResourceTypes: ['image', 'stylesheet'],\n});\n```\n\n### snapshot(options)\n\n```ts\nconst { html, screenshot } = await puppeteerService.snapshot({\n  url: 'https://example.com',\n  fullPage: true,\n  type: 'jpeg',\n  quality: 80,\n});\n```\n\n### scrape(options)\n\n```ts\nconst results = await puppeteerService.scrape({\n  url: 'https://example.com',\n  selectors: ['h1', 'p.intro', 'a[href]'],\n});\n// results: [{ selector: 'h1', elements: [{ text, html, attributes, width, height, top, left }] }, ...]\n```\n\n### links(options)\n\n```ts\nconst urls = await puppeteerService.links({\n  url: 'https://example.com',\n  visibleLinksOnly: true,\n});\n// urls: ['https://example.com/about', 'https://example.com/contact', ...]\n```\n\n## Swagger\n\nIf `@nestjs/swagger` is installed, all endpoints and DTOs are auto-documented:\n\n```ts\nimport { NestFactory } from '@nestjs/core';\nimport { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';\n\nconst app = await NestFactory.create(AppModule);\n\nconst config = new DocumentBuilder()\n  .setTitle('Browser Rendering API')\n  .setVersion('1.0')\n  .build();\n\nSwaggerModule.setup('docs', app, SwaggerModule.createDocument(app, config));\nawait app.listen(3000);\n```\n\n## Testing\n\nUse `createMockPuppeteerProviders()` to avoid launching a real browser:\n\n```ts\nimport { Test } from '@nestjs/testing';\nimport { createMockPuppeteerProviders, PuppeteerService } from '@bitwild/nest-puppeteer';\n\ndescribe('ReportService', () =\u003e {\n  let service: ReportService;\n\n  beforeEach(async () =\u003e {\n    const module = await Test.createTestingModule({\n      providers: [\n        ReportService,\n        PuppeteerService,\n        ...createMockPuppeteerProviders({\n          browser: {\n            newPage: jest.fn().mockResolvedValue({\n              goto: jest.fn(),\n              pdf: jest.fn().mockResolvedValue(Buffer.from('pdf')),\n              content: jest.fn().mockResolvedValue('\u003chtml\u003e\u003c/html\u003e'),\n              close: jest.fn(),\n              setViewport: jest.fn(),\n            }),\n          },\n        }),\n      ],\n    }).compile();\n\n    service = module.get(ReportService);\n  });\n});\n```\n\n## Docker\n\nRecommended launch options for containerized environments:\n\n```ts\nPuppeteerModule.forRoot({\n  headless: true,\n  args: [\n    '--no-sandbox',\n    '--disable-setuid-sandbox',\n    '--disable-dev-shm-usage',\n    '--disable-gpu',\n  ],\n})\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbtwld%2Fnest-puppeteer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbtwld%2Fnest-puppeteer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbtwld%2Fnest-puppeteer/lists"}