DEV Community

Custodia-Admin
Custodia-Admin

Posted on • Originally published at pagebolt.dev

NestJS Screenshot API: Add Web Capture to Your TypeScript Backend

NestJS Screenshot API: Add Web Capture to Your TypeScript Backend

NestJS has become the dominant framework for enterprise Node.js applications. Teams building AI backends, automated dashboards, and real-time applications choose NestJS for its modular architecture, strong typing, and dependency injection.

But when you need to capture screenshots, generate PDFs, or create dynamic OG images, you hit a wall: Puppeteer requires 200MB+ of dependencies, doesn't work in serverless, and complicates your Docker builds.

PageBolt changes that. One HTTP call. No browser binaries. Works everywhere — Lambda, Cloud Run, containers, and traditional VPS.

Why NestJS Developers Need This

Your NestJS app might need screenshots for:

  • Automated reports — PDF exports of dashboards or data visualizations
  • Dynamic OG images — Social cards generated from user content
  • Compliance audits — Visual proof of data states, form submissions, or transactions
  • AI agent workflows — Claude agents need to see web pages to make decisions

Without a solution, you're stuck:

  • Puppeteer: 200MB+ dependency bloat, Docker complexity, serverless incompatibility
  • wkhtmltopdf: Legacy tool, CSS limitations, system-level dependencies
  • Self-hosted Chrome: Infrastructure overhead, scaling headaches, memory management

PageBolt solves this with one REST API call from your NestJS service.


Solution: PageBolt Screenshot Service

Create a reusable NestJS service that wraps the PageBolt API. This follows NestJS best practices: injectable providers, dependency injection, and testability.

Step 1: Install Dependencies

npm install axios
npm install --save-dev @nestjs/common @nestjs/core
Enter fullscreen mode Exit fullscreen mode

Step 2: Create PageBolt Service

Create src/services/pagebolt.service.ts:

import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom } from 'rxjs';

@Injectable()
export class PageboltService {
  private readonly baseUrl = 'https://api.pagebolt.dev';
  private readonly apiKey = process.env.PAGEBOLT_API_KEY;

  constructor(private readonly httpService: HttpService) {}

  async takeScreenshot(
    url: string,
    options?: {
      width?: number;
      height?: number;
      format?: 'png' | 'jpeg' | 'webp';
    },
  ): Promise<Buffer> {
    const response = await firstValueFrom(
      this.httpService.post(
        `${this.baseUrl}/screenshot`,
        {
          url,
          width: options?.width || 1280,
          height: options?.height || 720,
          format: options?.format || 'png',
        },
        {
          headers: {
            Authorization: `Bearer ${this.apiKey}`,
          },
          responseType: 'arraybuffer',
        },
      ),
    );
    return response.data;
  }

  async generatePdf(
    url: string,
    options?: {
      landscape?: boolean;
      margin?: string;
    },
  ): Promise<Buffer> {
    const response = await firstValueFrom(
      this.httpService.post(
        `${this.baseUrl}/pdf`,
        {
          url,
          landscape: options?.landscape || false,
          margin: options?.margin || '1cm',
        },
        {
          headers: {
            Authorization: `Bearer ${this.apiKey}`,
          },
          responseType: 'arraybuffer',
        },
      ),
    );
    return response.data;
  }

  async createOgImage(
    title: "string,"
    subtitle?: string,
    options?: {
      width?: number;
      height?: number;
    },
  ): Promise<Buffer> {
    const response = await firstValueFrom(
      this.httpService.post(
        `${this.baseUrl}/og-image`,
        {
          title,
          subtitle,
          width: options?.width || 1200,
          height: options?.height || 630,
        },
        {
          headers: {
            Authorization: `Bearer ${this.apiKey}`,
          },
          responseType: 'arraybuffer',
        },
      ),
    );
    return response.data;
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Create Controller Endpoint

Create src/controllers/capture.controller.ts:

import { Controller, Post, Body, Response } from '@nestjs/common';
import { PageboltService } from '../services/pagebolt.service';
import { Response as ExpressResponse } from 'express';

@Controller('capture')
export class CaptureController {
  constructor(private readonly pageboltService: PageboltService) {}

  @Post('screenshot')
  async screenshot(
    @Body() body: { url: string },
    @Response() res: ExpressResponse,
  ) {
    const screenshot = await this.pageboltService.takeScreenshot(body.url);
    res.type('image/png');
    res.send(screenshot);
  }

  @Post('pdf')
  async pdf(@Body() body: { url: string }, @Response() res: ExpressResponse) {
    const pdf = await this.pageboltService.generatePdf(body.url);
    res.type('application/pdf');
    res.contentDisposition('attachment; filename="document.pdf"');
    res.send(pdf);
  }

  @Post('og-image')
  async ogImage(
    @Body() body: { title: "string; subtitle?: string },"
    @Response() res: ExpressResponse,
  ) {
    const image = await this.pageboltService.createOgImage(
      body.title,
      body.subtitle,
    );
    res.type('image/png');
    res.send(image);
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Register in Module

Update src/app.module.ts:

import { Module } from '@nestjs/common';
import { HttpModule } from '@nestjs/axios';
import { PageboltService } from './services/pagebolt.service';
import { CaptureController } from './controllers/capture.controller';

@Module({
  imports: [HttpModule],
  providers: [PageboltService],
  controllers: [CaptureController],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

Step 5: Add Environment Variable

Create .env:

PAGEBOLT_API_KEY=YOUR_API_KEY
Enter fullscreen mode Exit fullscreen mode

Real-World Example: AI Agent Backend

Your NestJS app powers an AI agent that needs to verify form submissions:

import { Injectable } from '@nestjs/common';
import { PageboltService } from './pagebolt.service';

@Injectable()
export class FormAuditService {
  constructor(private readonly pagebolt: PageboltService) {}

  async captureFormSubmission(formUrl: string, userId: string): Promise<void> {
    // Capture proof of submission
    const screenshot = await this.pagebolt.takeScreenshot(formUrl);

    // Store in audit log
    await this.storeAuditProof(userId, screenshot);
  }

  async generateComplianceReport(dashboardUrl: string): Promise<Buffer> {
    // Generate PDF proof for auditors
    return this.pagebolt.generatePdf(dashboardUrl, { landscape: true });
  }
}
Enter fullscreen mode Exit fullscreen mode

Comparison: PageBolt vs Self-Hosted Puppeteer

Feature PageBolt Puppeteer wkhtmltopdf
Setup time 2 minutes 30+ minutes 20+ minutes
Docker size No bloat +500MB +200MB
Serverless support ✅ Yes ❌ No ❌ No
Maintenance 0 — managed High Legacy tool
Cost (baseline) $29/mo $100+/mo infrastructure Free but time cost
API reliability 99.9% SLA Your responsibility Unmaintained

Cost Analysis

PageBolt:

  • Free tier: 100 requests/month, no credit card
  • Starter: $29/month (5,000 requests)
  • Pro: $99/month (50,000 requests)

Self-hosted Puppeteer + infrastructure:

  • EC2 instance: $20-50/month
  • Database (logs): $10+/month
  • Monitoring & alerts: $15+/month
  • Developer time (setup + maintenance): 10+ hours
  • Total: $45-75/month + significant developer time

PageBolt pays for itself in the first month.


Getting Started

  1. Sign uppagebolt.dev (100 free requests/month, no credit card)
  2. Get API key — Copy from dashboard
  3. Copy the service above — Paste into your NestJS project
  4. Make your first callPOST /capture/screenshot with a URL
  5. Monitor usage — Dashboard shows all requests

Your enterprise NestJS backend now captures web pages, generates PDFs, and creates OG images without any browser dependencies.


Try it free — 100 requests/month, no credit card. Start now.

Top comments (0)