April 2026 · 6 min read · Blog
Screenshot API for Express.js — Middleware, Routes, and Async Patterns
A practical guide to integrating SnapAPI into Express.js applications: route handlers, middleware patterns, streaming responses, and rate limiting.
Why Express Apps Need Screenshot Automation
Express.js powers a huge share of Node.js backends — REST APIs, BFF layers, monolithic web apps, and microservices. All of these can benefit from programmatic screenshot generation: authenticated dashboard exports, OG image endpoints, visual regression test utilities, and link preview generation. SnapAPI integrates with Express as a plain async HTTP call, with no SDK installation required beyond Node's native fetch (available from Node 18+).
Basic Route Handler
The simplest integration is a GET route that proxies a screenshot request to SnapAPI and streams the result back to the client:
const express = require('express');
const app = express();
app.get('/screenshot', async (req, res) => {
const { url } = req.query;
if (!url) return res.status(400).json({ error: 'url is required' });
try {
const params = new URLSearchParams({
url,
format: req.query.format || 'webp',
width: req.query.width || '1280',
access_key: process.env.SNAP_KEY
});
const snap = await fetch(`https://api.snapapi.pics/v1/screenshot?${params}`);
if (!snap.ok) return res.status(snap.status).json({ error: 'SnapAPI error' });
res.setHeader('Content-Type', snap.headers.get('content-type'));
res.setHeader('Cache-Control', 'public, max-age=3600');
snap.body.pipeTo(new WritableStream({
write(chunk) { res.write(chunk); },
close() { res.end(); }
}));
} catch (e) {
res.status(500).json({ error: e.message });
}
});
app.listen(3000);
Screenshot Middleware
For apps that need screenshot generation across multiple routes, extract the SnapAPI call into Express middleware. This middleware attaches a snap() helper to the response object, available in any downstream handler:
// middleware/snapapi.js
const snapMiddleware = (req, res, next) => {
res.snap = async (url, options = {}) => {
const params = new URLSearchParams({
url,
format: options.format || 'webp',
width: String(options.width || 1280),
height: String(options.height || 800),
access_key: process.env.SNAP_KEY,
...options
});
const response = await fetch(`https://api.snapapi.pics/v1/screenshot?${params}`);
if (!response.ok) throw new Error('SnapAPI: ' + response.status);
return response;
};
next();
};
module.exports = snapMiddleware;
// Usage in any route:
app.use(snapMiddleware);
app.get('/api/preview', async (req, res) => {
const snap = await res.snap(req.query.url, { format: 'webp', width: 640, height: 400 });
res.setHeader('Content-Type', 'image/webp');
snap.body.pipeTo(new WritableStream({
write(c) { res.write(c); }, close() { res.end(); }
}));
});
Authenticated Dashboard Export
For SaaS apps where dashboard pages require authentication, generate a short-lived token, pass it via URL, and expire it after the screenshot is captured:
const crypto = require('crypto');
// In-memory token store (use Redis in production)
const tokens = new Map();
app.post('/api/export/screenshot', requireAuth, async (req, res) => {
const token = crypto.randomBytes(24).toString('hex');
tokens.set(token, { userId: req.user.id, expires: Date.now() + 60_000 });
const renderUrl = process.env.APP_URL + '/render/dashboard?token=' + token;
const snap = await fetch(
'https://api.snapapi.pics/v1/screenshot?' +
new URLSearchParams({
url: renderUrl,
format: 'png',
width: '1440',
full_page: 'true',
wait_for: '.dashboard-ready',
access_key: process.env.SNAP_KEY
})
);
tokens.delete(token); // Invalidate immediately after capture
res.setHeader('Content-Type', 'image/png');
res.setHeader('Content-Disposition', 'attachment; filename="dashboard.png"');
const buf = Buffer.from(await snap.arrayBuffer());
res.send(buf);
});
// Render endpoint — validates token, renders dashboard for current user
app.get('/render/dashboard', (req, res) => {
const entry = tokens.get(req.query.token);
if (!entry || entry.expires < Date.now()) return res.status(401).send('Expired');
// Render dashboard HTML for entry.userId
res.render('dashboard', { userId: entry.userId });
});
Rate Limiting Screenshot Endpoints
Screenshot generation is a resource-intensive operation from SnapAPI's perspective. Protect your endpoint from abuse using express-rate-limit:
const rateLimit = require('express-rate-limit');
const screenshotLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 10, // 10 screenshots per minute per IP
message: { error: 'Too many screenshot requests, please wait.' },
standardHeaders: true,
legacyHeaders: false,
});
app.get('/screenshot', screenshotLimiter, async (req, res) => {
// ... handler
});
Caching with node-cache or Redis
Screenshots of the same URL within a short window rarely change. Cache responses for an hour to cut API usage and improve response times for repeated requests:
const NodeCache = require('node-cache');
const cache = new NodeCache({ stdTTL: 3600 });
app.get('/screenshot', screenshotLimiter, async (req, res) => {
const cacheKey = req.query.url + ':' + (req.query.format || 'webp');
const cached = cache.get(cacheKey);
if (cached) {
res.setHeader('Content-Type', 'image/webp');
res.setHeader('X-Cache', 'HIT');
return res.send(cached);
}
const snap = await fetch('https://api.snapapi.pics/v1/screenshot?' + new URLSearchParams({
url: req.query.url, format: 'webp', width: '1280', access_key: process.env.SNAP_KEY
}));
const buf = Buffer.from(await snap.arrayBuffer());
cache.set(cacheKey, buf);
res.setHeader('Content-Type', 'image/webp');
res.setHeader('X-Cache', 'MISS');
res.send(buf);
});
Getting Started
Sign up at snapapi.pics, get your API key (free tier is 200 screenshots/month with no credit card required), add it as SNAP_KEY in your .env file, and integrate with Express using native fetch. No npm packages required beyond your existing dependencies. The full REST API reference is at snapapi.pics/docs.