|
4 | 4 |
|
5 | 5 | --- |
6 | 6 |
|
| 7 | +# Fast HTML to PDF, Screenshots, and Video API |
7 | 8 |
|
| 9 | +A lightweight, high-performance rendering API built on Puppeteer. This is the open-source version of the software powering [html2pdfapi.com](https://html2pdfapi.com). |
8 | 10 |
|
| 11 | +## Features |
9 | 12 |
|
10 | | -# Fast HTML to PDF, Screenshots, and Video API |
| 13 | +- 📸 PNG, JPEG, WebP, GIF, AVIF screenshots from any URL or HTML |
| 14 | +- 📄 PDF generation with full page layout control |
| 15 | +- 🎬 MP4 video recording with smooth scroll animation |
| 16 | +- 🌐 Full HTML capture with all resources embedded inline |
| 17 | +- ⚡ Browser pool with configurable recycling and warmup |
| 18 | +- 🔧 Structured JSON logging (Pino) |
| 19 | +- 🐳 Docker-ready with CI pipeline |
11 | 20 |
|
12 | | -Welcome to the **Fast HTML to PDF, Screenshots, and Video API**! 🚀 |
| 21 | +## Getting Started |
13 | 22 |
|
14 | | -This is a lightweight version of the software used at [html2pdfapi.com](https://html2pdfapi.com). |
| 23 | +### Requirements |
15 | 24 |
|
16 | | -It provides a basic yet performant wrapper along with additional features to enhance the standard Puppeteer experience. |
| 25 | +- Node.js 24+ |
| 26 | +- FFmpeg (for video recording) |
17 | 27 |
|
18 | | -## Features |
| 28 | +### Local Development |
19 | 29 |
|
20 | | -✅ Generate PNG images from any URL or HTML content<br> |
21 | | -✅ Generate PDFs from any URL or HTML content<br> |
22 | | -✅ Generate Videos from any URL with smooth animation<br> |
23 | | -✅ **Direct HTML rendering** - Pass HTML content directly without needing a URL<br> |
24 | | -✅ Support for custom headers (like Authorization)<br> |
25 | | -✅ Support to render Lazy animations<br> |
26 | | -✅ Additional support for blocking: Cookies, Ads, Trackers, Banner<br> |
27 | | -✅ High-Performance webserver<br> |
28 | | -✅ Extended and simplified API wrapper to Puppeteer |
| 30 | +```bash |
| 31 | +npm install |
| 32 | +npm run dev |
| 33 | +``` |
29 | 34 |
|
| 35 | +The server starts on `http://localhost:3000`. API docs are available at `/docs`. |
30 | 36 |
|
31 | | -## License |
| 37 | +For video support on macOS (Homebrew): |
32 | 38 |
|
33 | | -For usage in commercial services, please refer to the `license.txt` file in this repository. |
| 39 | +```bash |
| 40 | +FFMPEG_PATH=/opt/homebrew/bin/ffmpeg npm run dev |
| 41 | +``` |
34 | 42 |
|
35 | | -Note: License is not enforced, but we are a small team, and any support to further develop this product would be greatly appreciated! 🙏 |
| 43 | +### Docker |
| 44 | + |
| 45 | +Pull from the GitHub Container Registry: |
| 46 | + |
| 47 | +```bash |
| 48 | +docker pull ghcr.io/carbogninalberto/fast-html-to-pdf-api:latest |
| 49 | +docker run -p 3000:3000 ghcr.io/carbogninalberto/fast-html-to-pdf-api:latest |
| 50 | +``` |
| 51 | + |
| 52 | +Or build locally: |
| 53 | + |
| 54 | +```bash |
| 55 | +docker build . -t bakney/fastrender |
| 56 | +docker run -p 3000:3000 bakney/fastrender |
| 57 | +``` |
| 58 | + |
| 59 | +For ARM-based machines (Apple Silicon): |
| 60 | + |
| 61 | +```bash |
| 62 | +docker build --platform linux/amd64 . -t render |
| 63 | +docker run --platform linux/amd64 -p 3000:3000 render |
| 64 | +``` |
36 | 65 |
|
37 | 66 | ## API Usage |
38 | 67 |
|
39 | | -### Render from URL |
| 68 | +### Endpoints |
| 69 | + |
| 70 | +| Endpoint | Method | Description | |
| 71 | +|----------|--------|-------------| |
| 72 | +| `/render` | POST | Render URL or HTML to PDF/Image/Video/HTML | |
| 73 | +| `/render?config=...` | GET | Same as above, with JSON config as query param | |
| 74 | +| `/health` | GET | Health check with browser pool stats | |
| 75 | +| `/ping` | GET | Simple connectivity check (returns `"pong"`) | |
| 76 | +| `/docs` | GET | Swagger API documentation | |
| 77 | + |
| 78 | +### Request Parameters |
| 79 | + |
| 80 | +| Parameter | Type | Required | Description | |
| 81 | +|-----------|------|----------|-------------| |
| 82 | +| `url` | string | One of `url` or `html` | URL to render | |
| 83 | +| `html` | string | One of `url` or `html` | Raw HTML content to render | |
| 84 | +| `type` | string | Yes | `image`, `pdf`, `video`, or `html` | |
| 85 | +| `device` | object | No | Viewport settings (width, height, userAgent) | |
| 86 | +| `render` | object | No | Render options (waitTime, fullPage, scroll) | |
| 87 | +| `image` | object | No | Image settings (type, quality, compression, crop, resize, rotation) | |
| 88 | +| `pdf` | object | No | PDF settings (format, margins, orientation, header/footer) | |
| 89 | +| `video` | object | No | Video settings (fps, duration, codec, bitrate, crf, preset) | |
40 | 90 |
|
41 | | -Use the Playground at [html2pdfapi](https://html2pdfapi.com/playground) (a free account is required), to create the API request in your favorite language. |
42 | | -You can omit the `apiKey` parameter. |
| 91 | +### Examples |
| 92 | + |
| 93 | +**Screenshot from URL:** |
43 | 94 |
|
44 | | -**Example - Generate PDF from URL:** |
45 | 95 | ```bash |
46 | 96 | curl -X POST http://localhost:3000/render \ |
47 | 97 | -H "Content-Type: application/json" \ |
48 | | - -d '{ |
49 | | - "url": "https://example.com", |
50 | | - "type": "pdf" |
51 | | - }' \ |
52 | | - --output output.pdf |
| 98 | + -d '{"url": "https://example.com", "type": "image"}' \ |
| 99 | + --output screenshot.png |
53 | 100 | ``` |
54 | 101 |
|
55 | | -### Render from HTML Content |
| 102 | +**PDF from HTML:** |
56 | 103 |
|
57 | | -You can now pass HTML content directly to the API without needing a URL! This is perfect for: |
58 | | -- Generating PDFs from dynamically created HTML |
59 | | -- Creating images from HTML templates |
60 | | -- Converting HTML email templates to images |
61 | | -- Any scenario where you have HTML as a string |
62 | | - |
63 | | -**Example - Generate PDF from HTML:** |
64 | 104 | ```bash |
65 | 105 | curl -X POST http://localhost:3000/render \ |
66 | 106 | -H "Content-Type: application/json" \ |
67 | 107 | -d '{ |
68 | | - "html": "<!DOCTYPE html><html><head><title>My Document</title><style>body { font-family: Arial; padding: 40px; } h1 { color: #333; }</style></head><body><h1>Hello World!</h1><p>This PDF was generated from HTML content.</p></body></html>", |
69 | | - "type": "pdf" |
| 108 | + "html": "<!DOCTYPE html><html><body><h1>Hello</h1></body></html>", |
| 109 | + "type": "pdf", |
| 110 | + "pdf": {"format": "A4", "margin": {"top": "20px", "bottom": "20px"}} |
70 | 111 | }' \ |
71 | 112 | --output output.pdf |
72 | 113 | ``` |
73 | 114 |
|
74 | | -**Example - Generate PNG from HTML:** |
| 115 | +**WebP image with custom quality:** |
| 116 | + |
75 | 117 | ```bash |
76 | 118 | curl -X POST http://localhost:3000/render \ |
77 | 119 | -H "Content-Type: application/json" \ |
78 | | - -d '{ |
79 | | - "html": "<!DOCTYPE html><html><head><style>body { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; display: flex; justify-content: center; align-items: center; height: 100vh; font-family: sans-serif; } .card { background: rgba(255,255,255,0.1); padding: 40px; border-radius: 20px; }</style></head><body><div class=\"card\"><h1>Beautiful Card</h1><p>Rendered from HTML!</p></div></body></html>", |
80 | | - "type": "image", |
81 | | - "image": { "type": "png" } |
82 | | - }' \ |
83 | | - --output output.png |
| 120 | + -d '{"url": "https://example.com", "type": "image", "image": {"type": "webp", "quality": 80}}' \ |
| 121 | + --output output.webp |
84 | 122 | ``` |
85 | 123 |
|
86 | | -**Example - With Custom Options:** |
| 124 | +**Video with scroll animation:** |
| 125 | + |
87 | 126 | ```bash |
88 | 127 | curl -X POST http://localhost:3000/render \ |
89 | 128 | -H "Content-Type: application/json" \ |
90 | 129 | -d '{ |
91 | | - "html": "<html><body><h1>Custom Settings</h1></body></html>", |
92 | | - "type": "pdf", |
93 | | - "device": { |
94 | | - "width": 800, |
95 | | - "height": 600 |
96 | | - }, |
97 | | - "pdf": { |
98 | | - "format": "Letter", |
99 | | - "printBackground": true, |
100 | | - "margin": { |
101 | | - "top": "20px", |
102 | | - "right": "20px", |
103 | | - "bottom": "20px", |
104 | | - "left": "20px" |
105 | | - } |
106 | | - } |
| 130 | + "url": "https://example.com", |
| 131 | + "type": "video", |
| 132 | + "video": {"fps": 24}, |
| 133 | + "render": {"scroll": {"animate": true, "duration": 3000}} |
107 | 134 | }' \ |
108 | | - --output custom.pdf |
| 135 | + --output output.mp4 |
109 | 136 | ``` |
110 | 137 |
|
111 | | -**Important Notes:** |
112 | | -- Either `url` OR `html` must be provided (not both) |
113 | | -- All standard options (device settings, PDF options, image options, etc.) work with both URL and HTML modes |
114 | | -- When using HTML content, network idle wait conditions (`networkidle0`, `networkidle2`) are automatically adjusted to prevent timeouts |
| 138 | +**Full HTML capture (resources embedded as data URIs):** |
115 | 139 |
|
116 | | -The Saas solution of our service provides out-of-the-box async support so that you don't have to implement your own. |
| 140 | +```bash |
| 141 | +curl -X POST http://localhost:3000/render \ |
| 142 | + -H "Content-Type: application/json" \ |
| 143 | + -d '{"url": "https://example.com", "type": "html"}' \ |
| 144 | + --output page.html |
| 145 | +``` |
117 | 146 |
|
118 | | -There are many libraries you can use to achieve it, it depends on the language you are using, this is a very lightweight and versatile solution if you are looking |
119 | | -for a simple, yet performant solution. |
| 147 | +**GET request with config parameter:** |
120 | 148 |
|
121 | | -## API Reference |
| 149 | +```bash |
| 150 | +curl "http://localhost:3000/render?config=%7B%22url%22%3A%22https%3A%2F%2Fexample.com%22%2C%22type%22%3A%22image%22%7D" \ |
| 151 | + --output screenshot.png |
| 152 | +``` |
122 | 153 |
|
123 | | -### Endpoints |
| 154 | +## Configuration |
124 | 155 |
|
125 | | -| Endpoint | Method | Description | |
126 | | -|----------|--------|-------------| |
127 | | -| `/render` | POST | Render URL or HTML to PDF/Image/Video | |
128 | | -| `/render?config=...` | GET | Render URL using GET with JSON config parameter | |
129 | | -| `/health` | GET | Health check with browser pool stats | |
130 | | -| `/docs` | GET | Swagger API documentation | |
| 156 | +All settings are driven by environment variables with sensible defaults. |
131 | 157 |
|
132 | | -### Request Parameters |
| 158 | +### Server |
133 | 159 |
|
134 | | -| Parameter | Type | Required | Description | |
135 | | -|-----------|------|----------|-------------| |
136 | | -| `url` | string | Either url or html | URL to render | |
137 | | -| `html` | string | Either url or html | HTML content to render | |
138 | | -| `type` | string | Yes | Output type: `image`, `pdf`, `video`, `html` | |
139 | | -| `device` | object | No | Device settings (width, height, userAgent, etc.) | |
140 | | -| `render` | object | No | Render options (waitTime, fullPage, scroll, etc.) | |
141 | | -| `image` | object | No | Image settings (type, quality, compression, etc.) | |
142 | | -| `pdf` | object | No | PDF settings (format, margins, orientation, etc.) | |
143 | | -| `video` | object | No | Video settings (fps, duration, codec, etc.) | |
| 160 | +| Variable | Default | Description | |
| 161 | +|----------|---------|-------------| |
| 162 | +| `SERVER_PORT` | `3000` | HTTP server port | |
| 163 | +| `BODY_LIMIT_BYTES` | `52428800` | Max request body size (50 MB) | |
| 164 | +| `SHUTDOWN_TIMEOUT_MS` | `30000` | Graceful shutdown timeout | |
| 165 | +| `LOG_LEVEL` | `info` | Log level (trace, debug, info, warn, error, fatal) | |
144 | 166 |
|
145 | | -For complete API documentation, visit `/docs` endpoint after starting the server. |
| 167 | +### Browser Pool |
146 | 168 |
|
147 | | -## Getting Started with Development |
| 169 | +| Variable | Default | Description | |
| 170 | +|----------|---------|-------------| |
| 171 | +| `POOL_MIN` | `2` | Minimum browser instances | |
| 172 | +| `POOL_MAX` | `10` | Maximum browser instances | |
| 173 | +| `POOL_ACQUIRE_TIMEOUT_MS` | `30000` | Max wait to acquire a browser | |
| 174 | +| `POOL_IDLE_TIMEOUT_MS` | `30000` | Idle time before eviction | |
| 175 | +| `BROWSER_MAX_AGE_MS` | `600000` | Max browser lifetime (10 min) | |
| 176 | +| `BROWSER_MAX_REQUESTS` | `100` | Max requests per browser before recycling | |
| 177 | +| `BROWSER_MAX_MEMORY_MB` | `500` | Memory limit per browser | |
148 | 178 |
|
149 | | -To get started, run the following commands: |
| 179 | +### Video |
150 | 180 |
|
151 | | -``` |
152 | | -npm i |
153 | | -npm run dev |
154 | | -``` |
| 181 | +| Variable | Default | Description | |
| 182 | +|----------|---------|-------------| |
| 183 | +| `FFMPEG_PATH` | `/usr/bin/ffmpeg` | Path to FFmpeg binary | |
| 184 | +| `VIDEO_MAX_SCROLL_DURATION_MS` | `20000` | Max scroll animation duration | |
155 | 185 |
|
156 | | -## Build and Run in Docker |
| 186 | +### Viewport |
157 | 187 |
|
158 | | -### Quick usage |
| 188 | +| Variable | Default | Description | |
| 189 | +|----------|---------|-------------| |
| 190 | +| `DEFAULT_VIEWPORT_WIDTH` | `1920` | Default viewport width | |
| 191 | +| `DEFAULT_VIEWPORT_HEIGHT` | `1080` | Default viewport height | |
159 | 192 |
|
160 | | -Install the docker image from the Github registry of this repository |
| 193 | +## Testing |
161 | 194 |
|
162 | | -``` |
163 | | -docker pull ghcr.io/carbogninalberto/fast-html-to-pdf-api:latest |
164 | | -docker run -p 3000:3000 ghcr.io/carbogninalberto/fast-html-to-pdf-api:latest |
| 195 | +### Unit Tests |
| 196 | + |
| 197 | +```bash |
| 198 | +npm test |
165 | 199 | ``` |
166 | 200 |
|
167 | | -### Prerequisites |
| 201 | +With coverage report (97% line coverage required): |
168 | 202 |
|
169 | | -- Docker installed on your system |
| 203 | +```bash |
| 204 | +npm run coverage |
| 205 | +``` |
170 | 206 |
|
171 | | -### Build the Docker image |
| 207 | +### End-to-End Tests |
172 | 208 |
|
173 | | -To build the Docker image, run the following command in the project root directory: |
| 209 | +Requires a running server on port 3000: |
174 | 210 |
|
| 211 | +```bash |
| 212 | +# Terminal 1: start the server |
| 213 | +FFMPEG_PATH=/opt/homebrew/bin/ffmpeg node app/server.js |
| 214 | + |
| 215 | +# Terminal 2: run E2E tests |
| 216 | +npm run test:e2e |
175 | 217 | ``` |
176 | | -docker build --platform linux/amd64 . -t render |
| 218 | + |
| 219 | +E2E tests validate output format correctness using magic bytes (PNG, WebP, PDF headers, MP4 ftyp box, HTML structure). |
| 220 | + |
| 221 | +### CI |
| 222 | + |
| 223 | +The GitHub Actions workflow runs on every push: |
| 224 | + |
| 225 | +1. Unit tests with 97% coverage gate |
| 226 | +2. Docker build |
| 227 | +3. E2E tests against the running container |
| 228 | + |
| 229 | +## Health Check |
| 230 | + |
| 231 | +`GET /health` returns pool status and memory usage: |
| 232 | + |
| 233 | +```json |
| 234 | +{ |
| 235 | + "status": "healthy", |
| 236 | + "timestamp": "2026-01-22T12:00:00.000Z", |
| 237 | + "browserPool": { |
| 238 | + "available": 2, |
| 239 | + "borrowed": 1, |
| 240 | + "pending": 0, |
| 241 | + "min": 2, |
| 242 | + "max": 10 |
| 243 | + }, |
| 244 | + "uptime": 3600, |
| 245 | + "memory": { |
| 246 | + "used": "256 MB", |
| 247 | + "total": "512 MB" |
| 248 | + } |
| 249 | +} |
177 | 250 | ``` |
178 | 251 |
|
179 | | -### Run the Docker container |
| 252 | +Returns `200` when healthy, `503` when degraded (no browsers available). |
180 | 253 |
|
181 | | -To run the Docker container, use the following command: |
| 254 | +## License |
182 | 255 |
|
183 | | -``` |
184 | | -docker run --platform linux/amd64 -p 3000:3000 render |
185 | | -``` |
| 256 | +For usage in commercial services, please refer to the `license.txt` file in this repository. |
| 257 | + |
| 258 | +Note: License is not enforced, but we are a small team, and any support to further develop this product would be greatly appreciated! 🙏 |
0 commit comments