A high-performance website screenshot service built with AdonisJS, providing REST APIs for capturing website screenshots and converting them to various image formats.
🎯 Complete Interactive API Documentation: Visit the Swagger UI for comprehensive API documentation with interactive testing:
http://localhost:3333/docs
Features:
- Complete API reference for all endpoints
- Interactive request testing
- Authentication setup
- Request/response examples
- Parameter validation
- Error handling documentation
- RESTful API: Clean API endpoints for screenshot generation
- Multiple Formats: Support for PNG, JPEG, WebP formats
- Queue Processing: Background job processing with BullMQ
- Caching: Redis-based caching for improved performance
- Rate Limiting: Built-in rate limiting and API key authentication
- Batch Processing: Support for bulk screenshot operations
- Error Handling: Comprehensive error handling and logging
- OpenAPI Documentation: Interactive API documentation with Swagger UI
- Dashboard Interface: Web-based dashboard for monitoring and API key management
The Web2Img service includes a built-in dashboard for system monitoring and API key management.
Visit http://localhost:57304/dashboard when your server is running to access:
- System Health Monitoring - Real-time system status and component health
- Performance Metrics - Request rates, processing times, and system resource usage
- API Key Management - Create, view, toggle, and delete API keys
- Statistics Overview - Quick stats on API keys, users, and system uptime
- Real-time Updates - Dashboard automatically refreshes every 30 seconds
- Responsive Design - Works on desktop and mobile devices
- API Key Creation - Generate new API keys with custom names and rate limits
- Security - API keys are masked in listings for security
- Health Checks - Monitor database, Redis, browser, and storage services
Through the dashboard, you can:
- Create new API keys with custom rate limits (1-10,000 requests/hour)
- View all existing API keys (with masked values for security)
- Activate/deactivate API keys as needed
- Delete unused API keys
- Monitor API key usage statistics
For more details, see the Dashboard Documentation.
This application uses a modern, scalable architecture with:
- AdonisJS v6: Modern Node.js framework
- Redis: For caching and queue management
- MySQL: Primary database
- Playwright: For browser automation and screenshot capture
- BullMQ: For background job processing
- OpenAPI 3.0: API documentation and specification
The CentralRedisManager is a singleton class that provides centralized Redis connection management with leak detection and proper resource cleanup. It's designed to prevent connection leaks and ensure all Redis connections are properly tracked and closed.
For most use cases, use the shared Redis client:
import { getCentralRedisManager } from '#services/central_redis_manager'
// Get the singleton instance
const redisManager = getCentralRedisManager()
// Get the shared Redis client (lazily initialized)
const client = redisManager.getClient()
// Use the client for Redis operations
await client.set('key', 'value')
const value = await client.get('key')When you need dedicated connections (e.g., for blocking operations):
import { getCentralRedisManager } from '#services/central_redis_manager'
const redisManager = getCentralRedisManager()
// Create a duplicate connection for dedicated use
const duplicateClient = redisManager.duplicate()
// Use for blocking operations or isolated transactions
await duplicateClient.blpop('queue', 0)
// Remember to close when done
await duplicateClient.quit()For BullMQ workers and queues, use the dedicated BullMQ method:
import { getCentralRedisManager } from '#services/central_redis_manager'
const redisManager = getCentralRedisManager()
// Create a connection specifically for BullMQ (no keyPrefix)
const bullmqClient = redisManager.duplicateForBullMQ()
// Use with BullMQ
const queue = new Queue('screenshots', { connection: bullmqClient })
const worker = new Worker('screenshots', processor, { connection: bullmqClient })Use the shared client for:
- ✅ Simple get/set operations
- ✅ Short-lived operations
- ✅ Cache operations
- ✅ General Redis commands
// ✅ Good: Simple cache operations
const client = redisManager.getClient()
await client.setex('session:123', 3600, 'user-data')Create duplicate connections for:
- ✅ Blocking operations (
BLPOP,BRPOP, etc.) - ✅ Long-running operations
- ✅ Pub/Sub subscribers
- ✅ BullMQ workers and queues
- ✅ Database-like transactions
// ✅ Good: Blocking operation with dedicated connection
const blockingClient = redisManager.duplicate()
try {
const result = await blockingClient.blpop('work-queue', 30)
// Process result...
} finally {
await blockingClient.quit() // Always clean up!
}The CentralRedisManager must be properly shut down in all application lifecycles to prevent connection leaks and ensure graceful cleanup.
In your main application startup file:
// start/app.ts or similar
import { getCentralRedisManager } from '#services/central_redis_manager'
// During application shutdown
process.on('SIGTERM', async () => {
console.log('Shutting down gracefully...')
// Shutdown Redis connections first
const redisManager = getCentralRedisManager()
await redisManager.shutdown()
// Then shutdown other services
// ...
process.exit(0)
})
process.on('SIGINT', async () => {
console.log('Received SIGINT, shutting down...')
const redisManager = getCentralRedisManager()
await redisManager.shutdown()
process.exit(0)
})The test bootstrap already includes automatic connection leak detection:
// tests/bootstrap.ts (already configured)
export const runnerHooks = {
teardown: [
async () => {
// Shutdown Redis manager after all tests
const redisManager = getCentralRedisManager()
await redisManager.shutdown()
},
],
}
// Automatic leak detection after each test
suite.each.teardown(async ({ assert }) => {
const redisManager = getCentralRedisManager()
const openConnectionsCount = redisManager.getOpenConnectionsCount()
// This will fail the test if connections are leaked
assert.equal(0, openConnectionsCount, 'Redis connection leak detected!')
})For tests that create duplicate connections:
import { test } from '@japa/runner'
import { getCentralRedisManager } from '#services/central_redis_manager'
test.group('Redis Operations', (group) => {
let duplicateClient: IORedis
group.teardown(async () => {
// Clean up any duplicate connections created in tests
if (duplicateClient) {
await duplicateClient.quit()
}
})
test('should handle blocking operations', async ({ assert }) => {
const redisManager = getCentralRedisManager()
duplicateClient = redisManager.duplicate()
// Use duplicate client for test
await duplicateClient.lpush('test-queue', 'item')
const result = await duplicateClient.brpop('test-queue', 1)
assert.equal(result[1], 'item')
// Connection will be cleaned up in group teardown
})
})For background workers or queue processors:
// workers/screenshot-worker.ts
import { getCentralRedisManager } from '#services/central_redis_manager'
class ScreenshotWorker {
private redisClient: IORedis
async start() {
const redisManager = getCentralRedisManager()
this.redisClient = redisManager.duplicateForBullMQ()
// Set up worker with dedicated connection
// ...
}
async stop() {
// Always clean up the connection
if (this.redisClient) {
await this.redisClient.quit()
}
}
}
// Graceful shutdown handling
process.on('SIGTERM', async () => {
await worker.stop()
// Final cleanup of all Redis connections
const redisManager = getCentralRedisManager()
await redisManager.shutdown()
})The CentralRedisManager includes built-in connection tracking and leak detection:
const redisManager = getCentralRedisManager()
// Check how many connections are currently open
const count = redisManager.getOpenConnectionsCount()
console.log(`Open connections: ${count}`)
// Get detailed information about open connections
const info = redisManager.getOpenConnectionsInfo()
console.log('Connection details:', info)- Always use the singleton: Never instantiate
CentralRedisManagerdirectly - Prefer shared connections: Use
getClient()for most operations - Clean up duplicates: Always call
quit()on duplicate connections when done - Implement shutdown handlers: Ensure
shutdown()is called during application termination - Monitor in tests: The automatic leak detection will catch connection leaks
- Use BullMQ method for queues: Use
duplicateForBullMQ()for BullMQ connections
// ❌ BAD: Creating duplicate without cleanup
const client = redisManager.duplicate()
await client.set('key', 'value')
// Missing: await client.quit()
// ❌ BAD: Not handling shutdown in workers
class Worker {
start() {
this.client = redisManager.duplicate()
// Missing shutdown handling
}
}
// ✅ GOOD: Proper cleanup pattern
const client = redisManager.duplicate()
try {
await client.blpop('queue', 0)
} finally {
await client.quit() // Always clean up
}
// ✅ GOOD: Shared client for simple operations
const client = redisManager.getClient() // No cleanup needed
await client.get('key')-
Clone the repository
git clone <repository-url> cd web2img
-
Install dependencies
npm install
-
Set up environment variables
cp .env.example .env # Edit .env with your configuration -
Start Redis server
redis-server
-
Run migrations
node ace migration:run
-
Start the development server
npm run dev
Run the test suite with automatic connection leak detection:
npm testThe test framework includes automatic Redis connection leak detection that will fail tests if connections are not properly closed.
POST /screenshots- Generate a single screenshotPOST /screenshots/batch- Generate multiple screenshotsGET /screenshots/:id- Get screenshot status/result
All API endpoints require authentication via API key:
curl -H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}' \
http://localhost:3333/screenshotsFor production deployment, ensure:
- Redis is properly configured with persistence and appropriate memory settings
- Database connections are optimized for your load
- Process management includes proper signal handling for graceful shutdowns
- Health checks monitor Redis connectivity
- Monitoring tracks connection counts and potential leaks
- Fork the repository
- Create a feature branch
- Make your changes
- Ensure all tests pass (including connection leak detection)
- Submit a pull request
[Your License Here]