DEV Community

Custodia-Admin
Custodia-Admin

Posted on • Originally published at pagebolt.dev

Express.js Screenshot API: Add Web Capture to Your Node.js App in Minutes

Express.js Screenshot API: Add Web Capture to Your Node.js App in Minutes

You're building an Express.js app. Your users need to export pages as PDFs, share preview images, or capture content for later reference.

You could:

  1. Install Puppeteer — adds 200MB to your project, complexity to your code
  2. Use a headless browser library — adds dependencies, deployment friction
  3. Build it yourself — infrastructure nightmare

Or you could use an API.

Here's the thing: adding screenshot/PDF capabilities to Express should be simple. And it can be.

The Express Problem: Screenshots Without Bloat

Express doesn't come with built-in screenshot or PDF generation. Your options have traditionally been:

Option 1: Puppeteer (Heavy)

const puppeteer = require('puppeteer');
const express = require('express');

const app = express();
let browser;

// Initialize browser on startup
(async () => {
  browser = await puppeteer.launch();
})();

app.get('/screenshot', async (req, res) => {
  const { url } = req.query;

  try {
    const page = await browser.newPage();
    await page.goto(url);
    const screenshot = await page.screenshot({ type: 'png' });
    await page.close();

    res.type('image/png').send(screenshot);
  } catch (error) {
    res.status(500).json({ error: 'Screenshot failed' });
  }
});

app.listen(3000);

// Problems:
// - 200MB+ dependency
// - Browser instance management
// - Memory overhead
// - Doesn't scale well
// - Complex error handling
Enter fullscreen mode Exit fullscreen mode

Option 2: PageBolt API (Simple)

const express = require('express');
const axios = require('axios');

const app = express();
const PAGEBOLT_KEY = process.env.PAGEBOLT_API_KEY;

app.get('/screenshot', async (req, res) => {
  const { url } = req.query;

  try {
    const response = await axios.post('https://api.pagebolt.dev/take_screenshot', 
      { url },
      { headers: { 'Authorization': `Bearer ${PAGEBOLT_KEY}` } }
    );

    const imageUrl = response.data.imageUrl;
    res.json({ imageUrl });
  } catch (error) {
    res.status(500).json({ error: 'Screenshot failed' });
  }
});

app.listen(3000);

// Benefits:
// - No dependencies to manage
// - Simple error handling
// - Scales instantly
// - Works in any environment
Enter fullscreen mode Exit fullscreen mode

Full Express Router Example

Create a complete, production-ready screenshot router:

// routes/screenshot.js
const express = require('express');
const axios = require('axios');
const router = express.Router();

const PAGEBOLT_KEY = process.env.PAGEBOLT_API_KEY;
const PAGEBOLT_BASE_URL = 'https://api.pagebolt.dev';

if (!PAGEBOLT_KEY) {
  throw new Error('PAGEBOLT_API_KEY environment variable is required');
}

/**
 * POST /screenshot/url
 * Take a screenshot of a URL
 */
router.post('/url', async (req, res) => {
  try {
    const { url, width = 1280, height = 720 } = req.body;

    // Validate input
    if (!url) {
      return res.status(400).json({ error: 'URL is required' });
    }

    // Call PageBolt
    const response = await axios.post(
      `${PAGEBOLT_BASE_URL}/take_screenshot`,
      { url, width, height },
      { headers: { 'Authorization': `Bearer ${PAGEBOLT_KEY}` } }
    );

    return res.json({ 
      success: true, 
      imageUrl: response.data.imageUrl 
    });
  } catch (error) {
    console.error('Screenshot error:', error.message);
    return res.status(500).json({ 
      error: 'Screenshot generation failed',
      message: error.message 
    });
  }
});

/**
 * POST /screenshot/html
 * Take a screenshot of raw HTML
 */
router.post('/html', async (req, res) => {
  try {
    const { html, width = 1280, height = 720 } = req.body;

    if (!html) {
      return res.status(400).json({ error: 'HTML is required' });
    }

    const response = await axios.post(
      `${PAGEBOLT_BASE_URL}/take_screenshot`,
      { html, width, height },
      { headers: { 'Authorization': `Bearer ${PAGEBOLT_KEY}` } }
    );

    return res.json({ 
      success: true, 
      imageUrl: response.data.imageUrl 
    });
  } catch (error) {
    console.error('Screenshot error:', error.message);
    return res.status(500).json({ error: 'Screenshot generation failed' });
  }
});

/**
 * POST /screenshot/pdf
 * Generate a PDF from a URL or HTML
 */
router.post('/pdf', async (req, res) => {
  try {
    const { url, html, format = 'A4', margin = '10mm' } = req.body;

    if (!url && !html) {
      return res.status(400).json({ error: 'URL or HTML is required' });
    }

    const payload = { format, margin };
    if (url) payload.url = url;
    if (html) payload.html = html;

    const response = await axios.post(
      `${PAGEBOLT_BASE_URL}/generate_pdf`,
      payload,
      { headers: { 'Authorization': `Bearer ${PAGEBOLT_KEY}` } }
    );

    return res.json({ 
      success: true, 
      pdfUrl: response.data.pdfUrl 
    });
  } catch (error) {
    console.error('PDF error:', error.message);
    return res.status(500).json({ error: 'PDF generation failed' });
  }
});

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

In your main app:

// server.js
const express = require('express');
const screenshotRouter = require('./routes/screenshot');

const app = express();
app.use(express.json());

// Mount the screenshot router
app.use('/api/screenshot', screenshotRouter);

app.listen(3000, () => {
  console.log('Server running on port 3000');
});
Enter fullscreen mode Exit fullscreen mode

Usage:

# Take a screenshot
curl -X POST http://localhost:3000/api/screenshot/url \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com"}'

# Generate a PDF
curl -X POST http://localhost:3000/api/screenshot/pdf \
  -H "Content-Type: application/json" \
  -d '{"html": "<h1>Hello</h1>"}'
Enter fullscreen mode Exit fullscreen mode

Middleware Pattern for Automatic Captures

Create middleware that automatically captures pages:

// middleware/autoCapture.js
const axios = require('axios');

const PAGEBOLT_KEY = process.env.PAGEBOLT_API_KEY;

/**
 * Middleware that captures every GET request (for monitoring/logging)
 */
const autoCaptureMiddleware = async (req, res, next) => {
  // Only capture GET requests to pages
  if (req.method !== 'GET' || !req.path.startsWith('/pages')) {
    return next();
  }

  // Capture the page after response is sent
  res.on('finish', async () => {
    try {
      const fullUrl = `${req.protocol}://${req.get('host')}${req.originalUrl}`;

      await axios.post(
        'https://api.pagebolt.dev/take_screenshot',
        { url: fullUrl },
        { headers: { 'Authorization': `Bearer ${PAGEBOLT_KEY}` } }
      );

      // Log or store the capture
      console.log(`Captured: ${fullUrl}`);
    } catch (error) {
      console.error('Auto-capture failed:', error.message);
      // Don't fail the request if capture fails
    }
  });

  next();
};

module.exports = autoCaptureMiddleware;

// In your app:
// app.use(autoCaptureMiddleware);
Enter fullscreen mode Exit fullscreen mode

Real-World Use Cases

1. Dynamic OG Image Generation

// routes/og-image.js
const express = require('express');
const axios = require('axios');
const router = express.Router();

router.get('/:slug', async (req, res) => {
  const { slug } = req.params;

  // Fetch article from database
  const article = await Article.findBySlug(slug);

  if (!article) return res.status(404).send('Not found');

  const html = `
    <html>
      <style>
        body {
          width: 1200px;
          height: 630px;
          background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
          display: flex;
          align-items: center;
          justify-content: center;
          color: white;
          font-family: Arial;
          padding: 0;
          margin: 0;
        }
        h1 { font-size: 48px; margin: 0; }
      </style>
      <body>
        <h1>${article.title}</h1>
      </body>
    </html>
  `;

  try {
    const response = await axios.post(
      'https://api.pagebolt.dev/take_screenshot',
      { html, width: 1200, height: 630 },
      { headers: { 'Authorization': `Bearer ${process.env.PAGEBOLT_API_KEY}` } }
    );

    return res.redirect(response.data.imageUrl);
  } catch (error) {
    return res.status(500).send('OG image generation failed');
  }
});

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

2. Export to PDF

// routes/export.js
router.post('/:documentId/pdf', async (req, res) => {
  const document = await Document.findById(req.params.documentId);

  const html = renderTemplate('document.ejs', { document });

  try {
    const response = await axios.post(
      'https://api.pagebolt.dev/generate_pdf',
      { html, format: 'A4' },
      { headers: { 'Authorization': `Bearer ${process.env.PAGEBOLT_API_KEY}` } }
    );

    return res.json({ downloadUrl: response.data.pdfUrl });
  } catch (error) {
    return res.status(500).json({ error: 'PDF generation failed' });
  }
});
Enter fullscreen mode Exit fullscreen mode

Configuration

In your .env:

PAGEBOLT_API_KEY=your_api_key_here
Enter fullscreen mode Exit fullscreen mode

In your package.json:

{
  "dependencies": {
    "express": "^4.18.0",
    "axios": "^1.4.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

That's it. No puppeteer, no wkhtmltopdf, no heavy dependencies.

Why PageBolt for Express

Aspect Puppeteer PageBolt API
Setup npm install (200MB+) API key
Dependencies Heavy (Chrome binary) Zero (just axios)
Lines of code 30+ lines per route 10 lines per route
Error handling Complex (browser crashes) Simple (HTTP errors)
Scaling Difficult (more servers = more browsers) Automatic
Performance Slow cold starts Fast (~1-2s)
Maintenance High (browser updates, memory leaks) Zero

Getting Started

1. Get API key (free tier: 100 requests/month)

# Visit pagebolt.dev, create account, copy key
export PAGEBOLT_API_KEY=your_key_here
Enter fullscreen mode Exit fullscreen mode

2. Install axios (you probably already have it)

npm install axios
Enter fullscreen mode Exit fullscreen mode

3. Create a screenshot route

router.post('/screenshot', async (req, res) => {
  const response = await axios.post(
    'https://api.pagebolt.dev/take_screenshot',
    { url: req.body.url },
    { headers: { 'Authorization': `Bearer ${process.env.PAGEBOLT_API_KEY}` } }
  );
  res.json({ imageUrl: response.data.imageUrl });
});
Enter fullscreen mode Exit fullscreen mode

4. Use it

curl -X POST http://localhost:3000/api/screenshot \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com"}'
Enter fullscreen mode Exit fullscreen mode

Next Steps

  • Try PageBolt free — 100 requests/month, no credit card.
  • Copy the router example above — drop it into your Express app.
  • Start with one use case — PDF export, OG images, or screenshots. Pick one.
  • Scale without infrastructure — when you need more requests, just upgrade. No code changes.

Stop managing browser processes. Start adding web capture to Express in minutes.


PageBolt: Screenshots and PDFs for Express, zero dependencies. Get started free →

Top comments (0)