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:
- Install Puppeteer — adds 200MB to your project, complexity to your code
- Use a headless browser library — adds dependencies, deployment friction
- 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
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
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;
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');
});
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>"}'
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);
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;
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' });
}
});
Configuration
In your .env:
PAGEBOLT_API_KEY=your_api_key_here
In your package.json:
{
"dependencies": {
"express": "^4.18.0",
"axios": "^1.4.0"
}
}
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
2. Install axios (you probably already have it)
npm install axios
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 });
});
4. Use it
curl -X POST http://localhost:3000/api/screenshot \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}'
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)