Tutorial Mar 25, 2026

NestJS Screenshot API: Add Web Capture to Your TypeScript Backend

Integrate screenshot and PDF capture into your NestJS application. Injectable service, dependency injection, no browser binaries — works on Lambda, Cloud Run, and containers.

NestJS has become the dominant framework for enterprise Node.js applications. Teams building AI backends, automated dashboards, and real-time services 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.

One HTTP call fixes this. No browser binaries. Works everywhere — Lambda, Cloud Run, containers, and traditional VPS.

Why NestJS Developers Need This

Your NestJS app might need screenshots for:

Without a hosted solution, your options all have serious trade-offs:

Building a PageBolt Service for NestJS

The pattern: create an injectable PageboltService that wraps the API, then inject it wherever you need captures. This follows NestJS conventions — testable, swappable, and properly scoped.

Step 1: Install Dependencies

npm install @nestjs/axios axios

Step 2: Create the Injectable 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://pagebolt.dev/api/v1';
  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';
      fullPage?: boolean;
    },
  ): 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',
          fullPage: options?.fullPage ?? false,
          blockBanners: true,
          blockAds: true,
        },
        {
          headers: { 'x-api-key': this.apiKey },
          responseType: 'arraybuffer',
        },
      ),
    );
    return Buffer.from(response.data);
  }

  async generatePdf(
    url: string,
    options?: { landscape?: boolean },
  ): Promise<Buffer> {
    const response = await firstValueFrom(
      this.httpService.post(
        `${this.baseUrl}/pdf`,
        {
          url,
          landscape: options?.landscape ?? false,
          printBackground: true,
        },
        {
          headers: { 'x-api-key': this.apiKey },
          responseType: 'arraybuffer',
        },
      ),
    );
    return Buffer.from(response.data);
  }
}

Step 3: Create the Controller

Create src/controllers/capture.controller.ts:

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

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

  @Post('screenshot')
  async screenshot(
    @Body() body: { url: string },
    @Res() res: Response,
  ) {
    if (!body.url) {
      throw new HttpException('url is required', HttpStatus.BAD_REQUEST);
    }
    const image = await this.pagebolt.takeScreenshot(body.url);
    res.setHeader('Content-Type', 'image/png');
    res.setHeader('Cache-Control', 'public, max-age=3600');
    res.send(image);
  }

  @Post('pdf')
  async pdf(
    @Body() body: { url: string; landscape?: boolean },
    @Res() res: Response,
  ) {
    if (!body.url) {
      throw new HttpException('url is required', HttpStatus.BAD_REQUEST);
    }
    const pdf = await this.pagebolt.generatePdf(body.url, {
      landscape: body.landscape,
    });
    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', 'attachment; filename="document.pdf"');
    res.send(pdf);
  }
}

Step 4: Register in Your 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],
  exports: [PageboltService], // export if other modules need it
})
export class CaptureModule {}

Step 5: Set the Environment Variable

# .env
PAGEBOLT_API_KEY=your_key_here

Use @nestjs/config for production-grade config management, or load the key via your existing secrets manager.

Real-World Example: Compliance Audit Service

Inject PageboltService into any other service that needs captures:

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

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

  async captureFormSubmission(formUrl: string, userId: string): Promise<void> {
    const screenshot = await this.pagebolt.takeScreenshot(formUrl);
    await this.storeAuditProof(userId, screenshot);
  }

  async generateComplianceReport(dashboardUrl: string): Promise<Buffer> {
    return this.pagebolt.generatePdf(dashboardUrl, { landscape: true });
  }

  private async storeAuditProof(userId: string, data: Buffer): Promise<void> {
    // Store in S3, database, or your audit log
  }
}

Testing the Service

Because PageboltService depends only on HttpService, it's straightforward to mock in unit tests:

import { Test } from '@nestjs/testing';
import { HttpService } from '@nestjs/axios';
import { PageboltService } from './pagebolt.service';
import { of } from 'rxjs';

describe('PageboltService', () => {
  let service: PageboltService;
  let httpService: jest.Mocked<HttpService>;

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      providers: [
        PageboltService,
        {
          provide: HttpService,
          useValue: { post: jest.fn() },
        },
      ],
    }).compile();

    service = module.get(PageboltService);
    httpService = module.get(HttpService);
  });

  it('calls the screenshot endpoint', async () => {
    const fakeBuffer = Buffer.from('fake-png');
    httpService.post.mockReturnValue(
      of({ data: fakeBuffer.buffer } as any)
    );

    const result = await service.takeScreenshot('https://example.com');
    expect(result).toBeInstanceOf(Buffer);
    expect(httpService.post).toHaveBeenCalledWith(
      expect.stringContaining('/screenshot'),
      expect.objectContaining({ url: 'https://example.com' }),
      expect.objectContaining({ headers: { 'x-api-key': undefined } }),
    );
  });
});

Comparison: PageBolt vs Self-Hosted Puppeteer

FactorPageBolt APIPuppeteerwkhtmltopdf
Setup time~2 minutes30+ minutes20+ minutes
Docker image impactNone+500MB+200MB
Serverless supportYesNoNo
Maintenance burdenZeroHigh (crashes, memory leaks)Legacy, unmaintained
CSS/JS renderingFull ChromiumFull ChromiumLimited
Unit testabilityMock HTTP callRequires browser processSubprocess dependency

Getting Started

  1. Get your API keypagebolt.dev/dashboard (100 free requests/month, no credit card)
  2. Install depsnpm install @nestjs/axios axios
  3. Copy the service — Drop pagebolt.service.ts into your project
  4. Add PAGEBOLT_API_KEY to your environment
  5. Inject and usePOST /capture/screenshot with a URL

Add web capture to your NestJS app today

Injectable service, TypeScript types, no browser dependencies. Works on Lambda, Cloud Run, and every container platform.

Get your free API key