@@ -20,7 +20,8 @@ A high-performance image processing microservice built with Bun, ElysiaJS, and S
2020- ** OG Image Generation** - Dynamic social media images using Satori (no headless browser)
2121- ** Multiple Templates** - 8 built-in templates + JSON-based custom templates
2222- ** Background Images** - Support for background images with opacity and fit modes (cover, contain, fill, tile)
23- - ** Smart Caching** - Disk, memory, or no caching with CDN-friendly headers
23+ - ** Text Watermarks** - Add text watermarks with custom fonts, colors, and positioning
24+ - ** Smart Caching** - Disk, memory, Redis, or hybrid caching with CDN-friendly headers
2425- ** SSRF Protection** - Blocks private IPs, localhost, and dangerous protocols
2526- ** Type-Safe** - Full TypeScript with Elysia's TypeBox validation
2627
@@ -104,9 +105,16 @@ Transform remote images with URL parameters.
104105| ` tint ` | string | Tint color (hex, e.g., ` ff5500 ` ) |
105106| ` trim ` | boolean | Trim whitespace |
106107| ` crop ` | string | Crop region ` x,y,width,height ` |
107- | ` watermark ` | string | Watermark image URL |
108+ | ` size ` | number | Percentage resize (1-100) |
109+ | ` wm_image ` | string | Watermark image URL |
110+ | ` wm_text ` | string | Watermark text |
108111| ` wm_position ` | string | Watermark position (see table below) |
109112| ` wm_opacity ` | number | Watermark opacity (0-1) |
113+ | ` wm_scale ` | number | Watermark scale (1-100) |
114+ | ` wm_padding ` | number | Watermark padding (0-500) |
115+ | ` wm_font ` | string | Watermark text font (any Google Font) |
116+ | ` wm_fontsize ` | number | Watermark text font size (8-200) |
117+ | ` wm_color ` | string | Watermark text color (hex, e.g., ` ff5500 ` ) |
110118
111119** Position Values** (for ` position ` and ` wm_position ` ):
112120
@@ -258,7 +266,7 @@ Configure via environment variables:
258266PORT=3000
259267
260268# Caching
261- CACHE_MODE=disk # disk, memory, hybrid, or none
269+ CACHE_MODE=disk # disk, memory, hybrid, redis, or none
262270CACHE_DIR=./cache
263271CACHE_TTL=86400 # 24 hours
264272MAX_CACHE_SIZE=1073741824 # 1GB
@@ -274,6 +282,12 @@ REQUEST_TIMEOUT=30000 # 30 seconds
274282# Browser/CDN Cache
275283BROWSER_CACHE_TTL=31536000 # 1 year
276284
285+ # Redis (when CACHE_MODE=redis)
286+ REDIS_URL=redis://localhost:6379
287+ REDIS_KEY_PREFIX=ps:
288+ REDIS_CONNECTION_TIMEOUT=5000
289+ REDIS_MAX_RETRIES=10
290+
277291# Custom templates
278292TEMPLATES_DIR=./templates
279293
@@ -288,6 +302,7 @@ CLUSTER_WORKERS=0 # 0 = auto-detect CPU cores
288302| ` disk ` | Persist to disk only. Survives restarts but slower reads. |
289303| ` memory ` | In-memory LRU cache. Fastest but lost on restart. |
290304| ` hybrid ` | Memory (L1) + Disk (L2). Fast reads with persistence. Best for production. |
305+ | ` redis ` | Redis-backed cache. Best for distributed/multi-instance deployments. |
291306| ` none ` | No caching. Every request processes the image fresh. |
292307
293308### Security Settings
@@ -481,24 +496,33 @@ pixelserve/
481496│ ├── index.ts # Main server
482497│ ├── cluster.ts # Multi-process cluster manager
483498│ ├── config.ts # Environment configuration
499+ │ ├── constants.ts # Named constants (timeouts, limits)
500+ │ ├── middleware/
501+ │ │ ├── origin-validator.ts # Origin/Referer validation guard
502+ │ │ └── error-handler.ts # Shared error handler
484503│ ├── routes/
485504│ │ ├── image.ts # Image processing endpoint
486505│ │ ├── og.ts # OG image generation endpoint
487506│ │ └── health.ts # Health check
488507│ ├── services/
489- │ │ ├── image-processor.ts # Sharp transformations
490- │ │ ├── image-fetcher.ts # Remote image fetching
491- │ │ ├── cache.ts # Disk/memory caching
508+ │ │ ├── image-processor.ts # Sharp pipeline orchestrator
509+ │ │ ├── image-fetcher.ts # Remote image fetching with SSRF protection
510+ │ │ ├── cache.ts # Multi-backend caching (disk/memory/hybrid/redis)
492511│ │ ├── og-generator.ts # Satori + resvg OG generation
493512│ │ ├── custom-templates.ts # JSON template builder
494- │ │ └── fonts.ts # Dynamic font loading via Google Fonts
513+ │ │ ├── fonts.ts # Dynamic font loading via Google Fonts
514+ │ │ └── transforms/ # Individual image transform steps
515+ │ │ ├── crop.ts
516+ │ │ ├── resize.ts
517+ │ │ ├── adjustments.ts
518+ │ │ ├── watermark.ts
519+ │ │ └── output.ts
495520│ ├── utils/
496521│ │ ├── url-validator.ts # SSRF prevention
497522│ │ └── errors.ts # Custom error classes
498523│ └── types/
499524│ └── index.ts # TypeScript interfaces
500525├── cache/ # Disk cache (gitignored)
501- ├── fonts/ # Fonts for OG generation
502526├── templates/ # Custom JSON templates
503527└── tests/
504528 ├── unit/
@@ -507,11 +531,12 @@ pixelserve/
507531
508532## Security
509533
510- - ** SSRF Prevention** : Blocks ` localhost ` , ` 127.0.0.1 ` , ` 0.0.0.0 ` , ` ::1 ` , private IP ranges, and ` file:// ` protocol
534+ - ** SSRF Prevention** : Blocks ` localhost ` , ` 127.0.0.1 ` , ` 0.0.0.0 ` , ` ::1 ` , private IP ranges, link-local addresses, and ` file:// ` protocol. Redirects are followed manually with re-validation at each hop.
535+ - ** Origin Validation** : Optional ` ALLOWED_ORIGINS ` blocks requests without a valid Origin/Referer header (subdomain-aware matching)
511536- ** Input Validation** : TypeBox schemas validate all query parameters
512537- ** Size Limits** : Max image dimensions (4096x4096) and file size (10MB)
513538- ** Timeout** : 30-second request timeout prevents hanging connections
514- - ** Domain Allowlist** : Optional ` ALLOWED_DOMAINS ` to restrict source URLs
539+ - ** Domain Allowlist** : Optional ` ALLOWED_DOMAINS ` to restrict source image URLs
515540
516541## Performance
517542
0 commit comments