A high-performance Fastify server that generates thumbnail images from PDF documents and uploads them to Cloudflare R2 storage.
- π Fast PDF rendering using
pdfjs-distandnode-canvas - πΈ Generates PNG thumbnails from the first page of PDFs
- βοΈ Automatic upload to Cloudflare R2 storage
- π Bearer token authentication for API security
- π Interactive Swagger/OpenAPI documentation
- π‘οΈ Built-in rate limiting to prevent abuse
- π Process management with PM2
- π Comprehensive logging and error handling
- π Production-ready TypeScript codebase
- Node.js (v16 or higher)
- npm or yarn
- PM2 (for production deployment)
- Cloudflare R2 credentials
- Clone the repository:
git clone <repository-url>
cd pdf-screenshot- Install dependencies:
npm install- Configure environment variables:
# Copy the example file
cp env.example .env
# Edit .env and add your actual credentials
nano .envRequired environment variables:
R2_ACCOUNT_ID- Your Cloudflare R2 account IDR2_ACCESS_KEY_ID- Your R2 access keyR2_SECRET_ACCESS_KEY- Your R2 secret keyR2_PUBLIC_BUCKET_URL- Your public bucket URLR2_MAIN_BUCKET_NAME- Your R2 bucket namePORT- Server port (default: 3000)SERVER_URL- Full server URL for API documentation (default: http://localhost:3000)API_KEYS- Comma-separated list of API keys for authentication (required)
- Build the TypeScript code:
npm run buildRun the server in development mode:
npm run devThe server will start on http://localhost:3000
- Install PM2 globally:
npm install -g pm2- Install PM2 log rotation:
pm2 install pm2-logrotate- Build and start the server:
npm run build
npm run pm2:start- Save PM2 configuration (optional, for auto-restart on system reboot):
pm2 save
pm2 startup| Command | Description |
|---|---|
npm run pm2:start |
Start the server with PM2 |
npm run pm2:stop |
Stop the server |
npm run pm2:restart |
Restart the server |
npm run pm2:delete |
Remove from PM2 |
npm run pm2:logs |
View real-time logs |
npm run pm2:status |
Check server status |
After making code changes:
npm run build
npm run pm2:restartThe API uses Bearer Token authentication. All endpoints require a valid API key.
API keys are managed via the API_KEYS environment variable in .env.
node -e "console.log('sk_live_' + require('crypto').randomBytes(32).toString('hex'))"Add the generated key to your .env file:
API_KEYS=sk_live_your_generated_key_hereFor multiple keys (comma-separated):
API_KEYS=sk_live_key1...,sk_live_key2...,sk_live_key3...Include the API key in the Authorization header:
curl -X POST http://localhost:3000/upload/pdf-screenshot \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/document.pdf"}'Requests without a valid API key will receive:
HTTP 401 Unauthorized
{
"statusCode": 401,
"error": "Unauthorized",
"message": "Invalid or missing API key. Include \"Authorization: Bearer YOUR_API_KEY\" header."
}Once the server is running, visit the interactive Swagger UI documentation:
http://localhost:3000/docs
Click the π Authorize button and enter your API key to test endpoints.
The documentation provides:
- π Complete API reference for all endpoints
- π§ͺ Interactive "Try it out" feature to test endpoints
- π Request/response schemas with examples
- π·οΈ Organized by tags (health, upload)
- π Built-in authentication testing
GET /Headers:
Authorization: Bearer YOUR_API_KEY
Response:
{
"message": "Hello from Hetzner Node server fastify"
}POST /upload/pdf-screenshotAuthentication: Required
Headers:
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
Rate Limit: 10 requests per hour per IP address
Request Body:
{
"url": "https://example.com/document.pdf"
}Success Response (200):
{
"success": true,
"path": "test/thumb-document.png",
"publicUrl": "https://your-r2-domain.com/test/thumb-document.png"
}Unauthorized Response (401):
{
"statusCode": 401,
"error": "Unauthorized",
"message": "Invalid or missing API key. Include \"Authorization: Bearer YOUR_API_KEY\" header."
}Error Response (400/500):
{
"success": false,
"error": "Error message",
"details": "Detailed error information"
}Rate Limit Response (429):
{
"statusCode": 429,
"error": "Too Many Requests",
"message": "PDF processing rate limit exceeded. Maximum 10 requests per hour. Try again in 45 minutes.",
"retryAfter": 2700000
}Rate Limit Headers: All responses include rate limit information:
x-ratelimit-limit: 10
x-ratelimit-remaining: 7
x-ratelimit-reset: 1640000000000
The API implements rate limiting to prevent abuse and ensure fair usage:
- 100 requests per minute per IP address across all endpoints
- Applies to all API calls
- Health Check (
GET /): 100 requests per minute - PDF Processing (
POST /upload/pdf-screenshot): 10 requests per hour
Every response includes these headers:
x-ratelimit-limit- Maximum requests allowed in time windowx-ratelimit-remaining- Requests remaining in current windowx-ratelimit-reset- Unix timestamp when the limit resets
When you exceed the limit, you'll receive:
HTTP 429 Too Many Requests
{
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded. Try again later.",
"retryAfter": 3600000
}- Localhost (127.0.0.1) is whitelisted for development
- instances: Number of instances to run (default: 1)
- max_memory_restart: Auto-restart if memory exceeds limit (default: 1G)
- autorestart: Automatically restart on crashes
- logs: Stored in
./logs/directory
PM2 automatically rotates logs with these settings:
- Max log size: 10MB
- Retained files: 30 old logs
- Compression: Enabled for old logs
- Check interval: Every 30 seconds
pdf-screenshot/
βββ index.ts # Main server file with Fastify initialization
βββ env.schema.ts # Environment variable schema and types
βββ swagger.config.ts # Swagger/OpenAPI configuration
βββ rate-limit.config.ts # Rate limiting configuration
βββ types.ts # TypeScript type definitions
βββ r2Client.ts # R2 storage client
βββ schemas/ # Reusable schemas
β βββ common-responses.ts # Common response schemas (400, 401, 429, 500)
βββ routes/ # Route handlers
β βββ root.ts # GET / endpoint
β βββ upload/
β βββ pdf-screenshot.ts # POST /upload/pdf-screenshot endpoint
βββ ecosystem.config.js # PM2 configuration
βββ tsconfig.json # TypeScript configuration
βββ package.json # Dependencies and scripts
βββ dist/ # Compiled JavaScript (generated)
βββ logs/ # PM2 logs (generated)
- Uses
pdfjs-dist(version 3.4.120) for PDF parsing - Renders to canvas with
node-canvas(version 3.2.0) - Default scale: 1.5x for better quality
- Output format: PNG
- Uploads to Cloudflare R2 using AWS S3 SDK
- Automatic filename generation from PDF URL
- Returns public URL for immediate access
npm run pm2:statusnpm run pm2:logsnpm run pm2:restartnpm run pm2:delete
npm run build
npm run pm2:start