Deploying E-Label
Introduction
E-Label is a modern, open-source digital label and QR code management system designed for inventory management, product labeling, asset tracking, and digital signage. Built with a focus on simplicity and efficiency, E-Label enables organizations to create, manage, and track physical items through customizable digital labels and QR codes. Whether you’re managing warehouse inventory, tracking assets, organizing retail products, or creating interactive labels for museums and exhibitions, E-Label provides the tools you need.
The platform combines label design capabilities with robust tracking features, allowing users to generate professional labels with embedded QR codes, barcodes, and dynamic content. E-Label supports batch generation, template management, and real-time updates, making it ideal for businesses that need scalable labeling solutions without the complexity of enterprise systems.
Key Features:
- Custom Label Design: Create professional labels with drag-and-drop design tools, custom templates, and branding options
- QR Code Generation: Generate dynamic QR codes with tracking capabilities, custom URLs, and embedded data
- Barcode Support: Multiple barcode formats including Code 128, EAN-13, UPC, QR codes, and Data Matrix
- Template Management: Save and reuse label templates for consistent branding across products and locations
- Batch Processing: Generate hundreds or thousands of labels at once with CSV import and bulk operations
- Asset Tracking: Track physical items through the entire lifecycle with scan history and location updates
- Multi-Format Export: Export labels to PDF, PNG, SVG for printing or digital display
- API Integration: RESTful API for programmatic label generation and management
- Database Storage: PostgreSQL backend for reliable data persistence and complex queries
- Responsive Design: Mobile-friendly interface for scanning and updating labels in the field
- User Management: Role-based access control with team collaboration features
- Print Optimization: Pre-configured settings for common label printers and paper sizes
This comprehensive guide walks you through deploying E-Label on Klutch.sh using Docker, configuring PostgreSQL for data storage, setting up persistent volumes for uploaded assets, and implementing production best practices for reliable label management.
Why Deploy E-Label on Klutch.sh
Deploying E-Label on Klutch.sh provides several advantages for digital label management:
- Automated Deployment: Klutch.sh automatically detects your Dockerfile and builds E-Label with zero manual configuration
- PostgreSQL Integration: Easy connection to managed PostgreSQL databases for storing labels, templates, and tracking data
- Persistent Storage: Attach persistent volumes for uploaded images, generated labels, and template assets
- Scalable Infrastructure: Handle thousands of concurrent label generation requests with automatic resource scaling
- Secure Environment Variables: Store database credentials and API keys securely without committing them to version control
- GitHub Integration: Automatic redeployment when you push updates to your E-Label configuration or custom themes
- Custom Domains: Use your own branded domain for the label management interface
- HTTPS by Default: Automatic SSL/TLS certificates for secure label access and API communication
- High Availability: Deploy with built-in redundancy for critical labeling operations
- Cost Efficiency: Pay only for the resources you use, ideal for businesses with variable labeling needs
Prerequisites
Before deploying E-Label on Klutch.sh, ensure you have:
- A Klutch.sh account
- A GitHub account for repository hosting
- Docker installed locally for testing (optional but recommended)
- Basic understanding of label printing, QR codes, and inventory management
- Familiarity with Docker and web applications
- A PostgreSQL database for storing label data (can be deployed on Klutch.sh)
- (Optional) Label printer compatible with standard formats (PDF, PNG)
- (Optional) Mobile device with QR code scanning capability for testing
Understanding E-Label Architecture
E-Label uses a modular architecture designed for scalability and ease of deployment:
Core Components
Web Application: Node.js/Express backend serving the label management interface and API
- Built with modern JavaScript frameworks (React/Vue for frontend)
- RESTful API for label operations and tracking
- Template engine for dynamic label generation
- Image processing for logo uploads and custom graphics
- PDF generation engine for printable labels
- QR code and barcode generation libraries
Database Layer: PostgreSQL for structured data storage
- Label definitions and metadata
- Template configurations
- User accounts and permissions
- Scan history and tracking logs
- Asset information and location data
File Storage: Persistent volume for uploaded assets
- Custom logos and images
- Generated label PDFs and images
- Template exports and backups
- Temporary files for batch processing
Data Flow
- User creates label template or uses existing template
- Label data entered manually or imported from CSV
- E-Label generates preview with QR code/barcode
- User approves and triggers batch generation
- Labels rendered to PDF or image formats
- Files stored in persistent volume
- Download links provided for printing or digital use
- QR code scans tracked in database for analytics
Port and Protocol Requirements
- HTTP/HTTPS: Port 3000 (internal) for web interface and API
- Database: PostgreSQL connection on port 5432 (or 8000 via Klutch.sh)
Preparing Your Repository
To deploy E-Label on Klutch.sh, create a GitHub repository with the necessary configuration files.
Step 1: Create Repository Structure
Create a new directory for your E-Label deployment:
mkdir e-label-klutchcd e-label-klutchgit initCreate the following directory structure:
e-label-klutch/├── Dockerfile├── docker-compose.yml # For local testing├── .dockerignore├── .env.example├── config/│ └── settings.js├── public/│ └── uploads/ # Will be mounted as volume├── scripts/│ └── init-db.sql└── README.mdStep 2: Create the Dockerfile
Create a production-ready Dockerfile:
# Build stage - compile frontend assetsFROM node:20-alpine AS build
WORKDIR /app
# Install build dependenciesRUN apk add --no-cache python3 make g++ cairo-dev jpeg-dev pango-dev giflib-dev
# Copy package filesCOPY package*.json ./
# Install dependenciesRUN npm ci --only=production
# Copy application codeCOPY . .
# Build frontend if neededRUN npm run build 2>/dev/null || echo "No build step required"
# Production stage - minimal runtimeFROM node:20-alpine
WORKDIR /app
# Install runtime dependencies for PDF and image generationRUN apk add --no-cache \ cairo \ jpeg \ pango \ giflib \ fontconfig \ ttf-dejavu \ curl
# Copy from build stageCOPY --from=build /app/node_modules ./node_modulesCOPY --from=build /app .
# Create directories for uploads and generated filesRUN mkdir -p /app/public/uploads \ && mkdir -p /app/public/generated \ && mkdir -p /app/data \ && chown -R node:node /app
# Switch to non-root userUSER node
# Expose application portEXPOSE 3000
# Health checkHEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ CMD curl -f http://localhost:3000/health || exit 1
# Start applicationCMD ["node", "server.js"]Dockerfile Explanation:
- Multi-stage build: Separates build dependencies from runtime for smaller image
- Alpine base: Lightweight Linux distribution reduces image size
- Cairo/Pango: Required for PDF generation and custom fonts
- Non-root user: Security best practice for production deployments
- Health check: Monitors application availability
- Port 3000: Default E-Label application port
Step 3: Create Application Entry Point
Create server.js if not already present in the E-Label codebase:
const express = require('express');const path = require('path');const { Pool } = require('pg');const QRCode = require('qrcode');const PDFDocument = require('pdfkit');const bwipjs = require('bwip-js');
const app = express();const PORT = process.env.PORT || 3000;
// Database configurationconst pool = new Pool({ host: process.env.DATABASE_HOST || 'localhost', port: process.env.DATABASE_PORT || 5432, database: process.env.DATABASE_NAME || 'elabel', user: process.env.DATABASE_USER || 'elabel', password: process.env.DATABASE_PASSWORD || '', max: 20, idleTimeoutMillis: 30000, connectionTimeoutMillis: 2000,});
// Middlewareapp.use(express.json());app.use(express.urlencoded({ extended: true }));app.use('/uploads', express.static(path.join(__dirname, 'public/uploads')));app.use('/generated', express.static(path.join(__dirname, 'public/generated')));app.use(express.static('public'));
// Health check endpointapp.get('/health', async (req, res) => { try { await pool.query('SELECT 1'); res.status(200).json({ status: 'healthy', timestamp: new Date().toISOString() }); } catch (error) { res.status(503).json({ status: 'unhealthy', error: error.message }); }});
// Generate QR code endpointapp.post('/api/labels/qr', async (req, res) => { try { const { data, format = 'png', size = 300 } = req.body;
if (!data) { return res.status(400).json({ error: 'Data is required' }); }
const qrCode = await QRCode.toDataURL(data, { width: size, margin: 2, errorCorrectionLevel: 'M' });
res.json({ qrCode, format: 'data-url' }); } catch (error) { console.error('QR generation error:', error); res.status(500).json({ error: 'Failed to generate QR code' }); }});
// Generate barcode endpointapp.post('/api/labels/barcode', async (req, res) => { try { const { data, type = 'code128', width = 2, height = 50 } = req.body;
if (!data) { return res.status(400).json({ error: 'Data is required' }); }
const png = await bwipjs.toBuffer({ bcid: type, text: data, scale: width, height: height, includetext: true, });
res.set('Content-Type', 'image/png'); res.send(png); } catch (error) { console.error('Barcode generation error:', error); res.status(500).json({ error: 'Failed to generate barcode' }); }});
// Create label endpointapp.post('/api/labels', async (req, res) => { const client = await pool.connect();
try { const { name, content, template_id, metadata } = req.body;
const result = await client.query( 'INSERT INTO labels (name, content, template_id, metadata, created_at) VALUES ($1, $2, $3, $4, NOW()) RETURNING *', [name, content, template_id, metadata] );
res.status(201).json(result.rows[0]); } catch (error) { console.error('Label creation error:', error); res.status(500).json({ error: 'Failed to create label' }); } finally { client.release(); }});
// List labels endpointapp.get('/api/labels', async (req, res) => { try { const { limit = 50, offset = 0 } = req.query;
const result = await pool.query( 'SELECT * FROM labels ORDER BY created_at DESC LIMIT $1 OFFSET $2', [limit, offset] );
res.json({ labels: result.rows, count: result.rowCount }); } catch (error) { console.error('Label fetch error:', error); res.status(500).json({ error: 'Failed to fetch labels' }); }});
// Generate PDF label endpointapp.get('/api/labels/:id/pdf', async (req, res) => { const client = await pool.connect();
try { const { id } = req.params;
const result = await client.query('SELECT * FROM labels WHERE id = $1', [id]);
if (result.rows.length === 0) { return res.status(404).json({ error: 'Label not found' }); }
const label = result.rows[0]; const doc = new PDFDocument({ size: 'A4', margin: 50 });
res.setHeader('Content-Type', 'application/pdf'); res.setHeader('Content-Disposition', `attachment; filename=label-${id}.pdf`);
doc.pipe(res);
// Add label content doc.fontSize(16).text(label.name, { align: 'center' }); doc.moveDown(); doc.fontSize(12).text(label.content);
// Generate and add QR code if (label.metadata && label.metadata.qr_data) { const qrCodeDataURL = await QRCode.toDataURL(label.metadata.qr_data, { width: 200 }); const qrBuffer = Buffer.from(qrCodeDataURL.split(',')[1], 'base64'); doc.image(qrBuffer, { fit: [200, 200], align: 'center' }); }
doc.end(); } catch (error) { console.error('PDF generation error:', error); res.status(500).json({ error: 'Failed to generate PDF' }); } finally { client.release(); }});
// Track scan endpointapp.post('/api/scans', async (req, res) => { const client = await pool.connect();
try { const { label_id, location, metadata } = req.body;
const result = await client.query( 'INSERT INTO scans (label_id, location, metadata, scanned_at) VALUES ($1, $2, $3, NOW()) RETURNING *', [label_id, location, metadata] );
res.status(201).json(result.rows[0]); } catch (error) { console.error('Scan tracking error:', error); res.status(500).json({ error: 'Failed to track scan' }); } finally { client.release(); }});
// Initialize database on startupasync function initializeDatabase() { const client = await pool.connect();
try { // Create tables if they don't exist await client.query(` CREATE TABLE IF NOT EXISTS labels ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, content TEXT, template_id INTEGER, metadata JSONB, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() );
CREATE TABLE IF NOT EXISTS templates ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, config JSONB NOT NULL, created_at TIMESTAMP DEFAULT NOW() );
CREATE TABLE IF NOT EXISTS scans ( id SERIAL PRIMARY KEY, label_id INTEGER REFERENCES labels(id), location VARCHAR(255), metadata JSONB, scanned_at TIMESTAMP DEFAULT NOW() );
CREATE INDEX IF NOT EXISTS idx_labels_created_at ON labels(created_at); CREATE INDEX IF NOT EXISTS idx_scans_label_id ON scans(label_id); CREATE INDEX IF NOT EXISTS idx_scans_scanned_at ON scans(scanned_at); `);
console.log('Database initialized successfully'); } catch (error) { console.error('Database initialization error:', error); throw error; } finally { client.release(); }}
// Start server(async () => { try { await initializeDatabase();
app.listen(PORT, '0.0.0.0', () => { console.log(`E-Label server running on port ${PORT}`); console.log(`Health check: http://localhost:${PORT}/health`); }); } catch (error) { console.error('Failed to start server:', error); process.exit(1); }})();
// Graceful shutdownprocess.on('SIGTERM', async () => { console.log('SIGTERM received, shutting down gracefully'); await pool.end(); process.exit(0);});Step 4: Create Package Configuration
Create package.json:
{ "name": "e-label", "version": "1.0.0", "description": "Digital label and QR code management system", "main": "server.js", "scripts": { "start": "node server.js", "dev": "nodemon server.js", "build": "echo 'No build step required'" }, "dependencies": { "express": "^4.18.2", "pg": "^8.11.3", "qrcode": "^1.5.3", "pdfkit": "^0.13.0", "bwip-js": "^4.1.1", "dotenv": "^16.3.1", "multer": "^1.4.5-lts.1", "sharp": "^0.33.0" }, "devDependencies": { "nodemon": "^3.0.2" }, "engines": { "node": ">=18.0.0", "npm": ">=9.0.0" }}Step 5: Create Environment Configuration
Create .env.example:
# E-Label Environment Configuration
# ApplicationNODE_ENV=productionPORT=3000APP_NAME=E-LabelBASE_URL=https://example-app.klutch.sh
# Database ConfigurationDATABASE_HOST=your-postgres-app.klutch.shDATABASE_PORT=8000DATABASE_NAME=elabelDATABASE_USER=elabelDATABASE_PASSWORD=your-secure-password
# File StorageUPLOAD_DIR=/app/public/uploadsGENERATED_DIR=/app/public/generatedMAX_FILE_SIZE=10485760
# Label GenerationDEFAULT_LABEL_WIDTH=400DEFAULT_LABEL_HEIGHT=300DEFAULT_QR_SIZE=200SUPPORTED_BARCODES=code128,ean13,upc,qrcode,datamatrix
# API ConfigurationAPI_KEY=your-api-key-hereAPI_RATE_LIMIT=100
# Session (optional)SESSION_SECRET=your-session-secret
# Email (optional for notifications)SMTP_HOST=SMTP_PORT=587SMTP_USER=SMTP_PASSWORD=SMTP_FROM=noreply@example.comStep 6: Create Docker Ignore File
Create .dockerignore:
node_modulesnpm-debug.log.env.env.local.git.gitignore.DS_Store*.mdpublic/uploads/*public/generated/*docker-compose.ymlStep 7: Create Docker Compose for Local Testing
Create docker-compose.yml:
version: '3.8'
services: postgres: image: postgres:16-alpine container_name: elabel-postgres environment: POSTGRES_DB: elabel POSTGRES_USER: elabel POSTGRES_PASSWORD: elabel_password ports: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data - ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init.sql healthcheck: test: ["CMD-SHELL", "pg_isready -U elabel"] interval: 10s timeout: 5s retries: 5
elabel: build: . container_name: elabel-app ports: - "3000:3000" environment: NODE_ENV: development PORT: 3000 DATABASE_HOST: postgres DATABASE_PORT: 5432 DATABASE_NAME: elabel DATABASE_USER: elabel DATABASE_PASSWORD: elabel_password BASE_URL: http://localhost:3000 volumes: - ./public/uploads:/app/public/uploads - ./public/generated:/app/public/generated depends_on: postgres: condition: service_healthy healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s
volumes: postgres_data:Step 8: Create Database Initialization Script
Create scripts/init-db.sql:
-- E-Label Database Schema
-- Labels tableCREATE TABLE IF NOT EXISTS labels ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, content TEXT, template_id INTEGER, metadata JSONB DEFAULT '{}', created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW());
-- Templates tableCREATE TABLE IF NOT EXISTS templates ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, description TEXT, config JSONB NOT NULL, is_default BOOLEAN DEFAULT false, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW());
-- Scans table for trackingCREATE TABLE IF NOT EXISTS scans ( id SERIAL PRIMARY KEY, label_id INTEGER REFERENCES labels(id) ON DELETE CASCADE, location VARCHAR(255), device_info VARCHAR(255), ip_address INET, metadata JSONB DEFAULT '{}', scanned_at TIMESTAMP DEFAULT NOW());
-- Users table (optional for authentication)CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, full_name VARCHAR(255), role VARCHAR(50) DEFAULT 'user', created_at TIMESTAMP DEFAULT NOW(), last_login TIMESTAMP);
-- Create indexesCREATE INDEX IF NOT EXISTS idx_labels_created_at ON labels(created_at DESC);CREATE INDEX IF NOT EXISTS idx_labels_template_id ON labels(template_id);CREATE INDEX IF NOT EXISTS idx_scans_label_id ON scans(label_id);CREATE INDEX IF NOT EXISTS idx_scans_scanned_at ON scans(scanned_at DESC);CREATE INDEX IF NOT EXISTS idx_templates_is_default ON templates(is_default);
-- Insert default templateINSERT INTO templates (name, description, config, is_default)VALUES ( 'Standard Product Label', 'Default template for product labels with QR code', '{ "width": 400, "height": 300, "fontSize": 14, "fontFamily": "Arial", "includeQR": true, "qrSize": 150, "backgroundColor": "#FFFFFF", "textColor": "#000000" }', true) ON CONFLICT DO NOTHING;
-- Create function to update updated_at timestampCREATE OR REPLACE FUNCTION update_updated_at_column()RETURNS TRIGGER AS $$BEGIN NEW.updated_at = NOW(); RETURN NEW;END;$$ language 'plpgsql';
-- Create triggersCREATE TRIGGER update_labels_updated_at BEFORE UPDATE ON labels FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_templates_updated_at BEFORE UPDATE ON templates FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();Step 9: Create README
Create README.md:
# E-Label on Klutch.sh
Digital label and QR code management system for inventory, asset tracking, and product labeling.
## Features
- Custom label design with templates- QR code and barcode generation- Batch label processing- Asset tracking with scan history- Multi-format export (PDF, PNG, SVG)- PostgreSQL backend- RESTful API- Mobile-friendly scanning
## Local Development
Test locally with Docker Compose:
```bashdocker-compose up -dAccess E-Label at: http://localhost:3000
Production Deployment on Klutch.sh
Required Environment Variables
Set these in the Klutch.sh dashboard:
Database Configuration:
DATABASE_HOST: PostgreSQL host (e.g., your-postgres-app.klutch.sh)DATABASE_PORT: PostgreSQL port (8000 for Klutch.sh deployments)DATABASE_NAME: Database name (e.g., elabel)DATABASE_USER: Database userDATABASE_PASSWORD: Database password
Application Settings:
BASE_URL: Your app URL (e.g., https://elabel.klutch.sh)APP_NAME: Application name for brandingAPI_KEY: API key for programmatic access
Persistent Volumes
Attach a persistent volume for uploaded assets:
- Mount Path:
/app/public/uploads - Recommended Size: 10-50GB depending on usage
Traffic Configuration
- Traffic Type: HTTP
- Internal Port: 3000
API Usage
Generate QR Code
curl -X POST https://example-app.klutch.sh/api/labels/qr \ -H "Content-Type: application/json" \ -d '{"data": "https://example.com/product/12345", "size": 300}'Create Label
curl -X POST https://example-app.klutch.sh/api/labels \ -H "Content-Type: application/json" \ -d '{ "name": "Product XYZ", "content": "Description of product", "metadata": {"sku": "12345", "qr_data": "https://example.com/product/12345"} }'Track Scan
curl -X POST https://example-app.klutch.sh/api/scans \ -H "Content-Type: application/json" \ -d '{ "label_id": 1, "location": "Warehouse A", "metadata": {"device": "Mobile Scanner"} }'License
MIT License
### Step 10: Initialize Git and Push to GitHub
```bash# Add all filesgit add .
# Commitgit commit -m "Initial E-Label deployment configuration"
# Add GitHub remote (replace with your repository URL)git remote add origin https://github.com/yourusername/e-label-klutch.git
# Push to GitHubgit branch -M maingit push -u origin mainDeploying to Klutch.sh
Now that your repository is prepared, follow these steps to deploy E-Label on Klutch.sh.
Prerequisites: Deploy PostgreSQL Database
E-Label requires PostgreSQL for data storage. Deploy it first:
- **Follow PostgreSQL Guide**
Deploy PostgreSQL using our PostgreSQL deployment guide.
- **Create E-Label Database**
Connect to your PostgreSQL instance and create the database:
CREATE DATABASE elabel;CREATE USER elabel WITH PASSWORD 'your-secure-password';GRANT ALL PRIVILEGES ON DATABASE elabel TO elabel; - **Note Connection Details**
You’ll need these for E-Label configuration:
- Host:
your-postgres-app.klutch.sh - Port:
8000 - Database:
elabel - User:
elabel - Password: Your chosen password
- Host:
Deployment Steps
- **Navigate to Klutch.sh Dashboard**
Visit klutch.sh/app and log in to your account.
- **Create a New Project**
Click “New Project” and give it a name like “E-Label System” to organize your deployment.
- **Create a New App**
Click “New App” or “Create App” and select GitHub as your source.
- **Connect Your Repository**
- Authenticate with GitHub if not already connected
- Select your E-Label repository from the list
- Choose the
mainbranch for deployment
- **Configure Application Settings**
- App Name: Choose a unique name (e.g.,
elabel-prod) - Traffic Type: Select HTTP for web interface access
- Internal Port: Set to
3000(E-Label’s default port)
- App Name: Choose a unique name (e.g.,
- **Set Environment Variables**
Configure these environment variables in the Klutch.sh dashboard:
Required - Database Configuration:
DATABASE_HOST=your-postgres-app.klutch.shDATABASE_PORT=8000DATABASE_NAME=elabelDATABASE_USER=elabelDATABASE_PASSWORD=your-secure-passwordRequired - Application Settings:
NODE_ENV=productionPORT=3000BASE_URL=https://your-app-name.klutch.shAPP_NAME=E-Label ProductionOptional - API Configuration:
API_KEY=your-generated-api-keyAPI_RATE_LIMIT=100Optional - File Upload Limits:
MAX_FILE_SIZE=10485760UPLOAD_DIR=/app/public/uploadsGENERATED_DIR=/app/public/generated - **Attach Persistent Volume**
Critical for storing uploaded images and generated labels:
- Click “Add Volume” in the Volumes section
- Mount Path:
/app/public/uploads - Size: 10GB minimum, 20-50GB recommended for production
This volume stores:
- Uploaded logos and images
- Generated label files
- Template assets
- Temporary batch processing files
- **Deploy Application**
Click “Create” or “Deploy” to start the deployment. Klutch.sh will:
- Automatically detect your Dockerfile
- Build the Docker image with Node.js and dependencies
- Install PDF generation libraries (Cairo, Pango)
- Attach the persistent volume
- Start your E-Label container
- Assign a URL for external access
The first deployment takes 3-5 minutes as dependencies are installed and the database is initialized.
- **Verify Deployment**
Once deployed, your E-Label instance will be available at:
https://your-app-name.klutch.shTest the deployment:
Health Check:
Terminal window curl https://your-app-name.klutch.sh/healthExpected response:
{"status": "healthy","timestamp": "2024-12-17T10:00:00.000Z"}Database Connection:
The health check endpoint also verifies database connectivity. If you see
"status": "healthy", your PostgreSQL connection is working. - **Initialize Database Schema**
The application automatically creates tables on first startup. Verify by checking logs or testing the API:
Terminal window curl https://your-app-name.klutch.sh/api/labelsExpected response:
{"labels": [],"count": 0}
Getting Started with E-Label
After deploying E-Label on Klutch.sh, follow these steps to create your first labels.
Creating Your First Label
Use the API to create a simple label with a QR code:
curl -X POST https://your-app-name.klutch.sh/api/labels \ -H "Content-Type: application/json" \ -d '{ "name": "Warehouse Item A001", "content": "Premium Quality Product - Serial: WH-A001", "metadata": { "sku": "A001", "location": "Warehouse Section A", "qr_data": "https://your-company.com/items/A001" } }'Response:
{ "id": 1, "name": "Warehouse Item A001", "content": "Premium Quality Product - Serial: WH-A001", "template_id": null, "metadata": { "sku": "A001", "location": "Warehouse Section A", "qr_data": "https://your-company.com/items/A001" }, "created_at": "2024-12-17T10:00:00.000Z", "updated_at": "2024-12-17T10:00:00.000Z"}Generating a QR Code
Generate a standalone QR code:
curl -X POST https://your-app-name.klutch.sh/api/labels/qr \ -H "Content-Type: application/json" \ -d '{ "data": "https://your-company.com/products/12345", "size": 300, "format": "png" }'Response includes data URL that can be embedded in HTML:
{ "qrCode": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...", "format": "data-url"}Generating a Barcode
Create a Code 128 barcode:
curl -X POST https://your-app-name.klutch.sh/api/labels/barcode \ -H "Content-Type: application/json" \ -d '{ "data": "123456789012", "type": "code128", "width": 2, "height": 50 }' --output barcode.pngSupported barcode types:
code128: Code 128 (alphanumeric)ean13: EAN-13 (13 digits)upca: UPC-A (12 digits)qrcode: QR Codedatamatrix: Data Matrix
Downloading Label as PDF
Generate a printable PDF for a label:
curl https://your-app-name.klutch.sh/api/labels/1/pdf --output label.pdfThis creates a formatted PDF with:
- Label name and content
- Embedded QR code
- Professional layout for printing
Tracking Label Scans
Record when a label is scanned (useful for inventory tracking):
curl -X POST https://your-app-name.klutch.sh/api/scans \ -H "Content-Type: application/json" \ -d '{ "label_id": 1, "location": "Warehouse Loading Dock", "metadata": { "scanner": "Mobile App", "user": "John Doe", "notes": "Outbound shipment scan" } }'Response:
{ "id": 1, "label_id": 1, "location": "Warehouse Loading Dock", "metadata": { "scanner": "Mobile App", "user": "John Doe", "notes": "Outbound shipment scan" }, "scanned_at": "2024-12-17T14:30:00.000Z"}Batch Label Creation
For creating multiple labels at once, you can script the API:
#!/bin/bash
# labels.csv format: name,content,sku,locationwhile IFS=',' read -r name content sku location; do curl -X POST https://your-app-name.klutch.sh/api/labels \ -H "Content-Type: application/json" \ -d "{ \"name\": \"$name\", \"content\": \"$content\", \"metadata\": { \"sku\": \"$sku\", \"location\": \"$location\", \"qr_data\": \"https://your-company.com/items/$sku\" } }" sleep 0.5 # Rate limitingdone < labels.csvAPI Integration Examples
Node.js Client
const axios = require('axios');
class ELabelClient { constructor(baseURL, apiKey = null) { this.client = axios.create({ baseURL, headers: apiKey ? { 'X-API-Key': apiKey } : {} }); }
async createLabel(name, content, metadata = {}) { const response = await this.client.post('/api/labels', { name, content, metadata }); return response.data; }
async generateQRCode(data, size = 300) { const response = await this.client.post('/api/labels/qr', { data, size }); return response.data.qrCode; }
async downloadLabelPDF(labelId) { const response = await this.client.get(`/api/labels/${labelId}/pdf`, { responseType: 'arraybuffer' }); return response.data; }
async trackScan(labelId, location, metadata = {}) { const response = await this.client.post('/api/scans', { label_id: labelId, location, metadata }); return response.data; }
async getLabels(limit = 50, offset = 0) { const response = await this.client.get('/api/labels', { params: { limit, offset } }); return response.data; }}
// Usageconst elabel = new ELabelClient('https://example-app.klutch.sh', 'your-api-key');
(async () => { // Create a new label const label = await elabel.createLabel( 'Product ABC', 'High-quality product with extended warranty', { sku: 'ABC123', price: 99.99 } );
console.log('Label created:', label.id);
// Download PDF const pdf = await elabel.downloadLabelPDF(label.id); require('fs').writeFileSync('label.pdf', pdf);
console.log('Label PDF saved');})();Python Client
import requestsimport json
class ELabelClient: def __init__(self, base_url, api_key=None): self.base_url = base_url self.headers = {'Content-Type': 'application/json'} if api_key: self.headers['X-API-Key'] = api_key
def create_label(self, name, content, metadata=None): url = f"{self.base_url}/api/labels" data = { 'name': name, 'content': content, 'metadata': metadata or {} } response = requests.post(url, json=data, headers=self.headers) response.raise_for_status() return response.json()
def generate_qr_code(self, data, size=300): url = f"{self.base_url}/api/labels/qr" payload = {'data': data, 'size': size} response = requests.post(url, json=payload, headers=self.headers) response.raise_for_status() return response.json()['qrCode']
def download_label_pdf(self, label_id, output_path): url = f"{self.base_url}/api/labels/{label_id}/pdf" response = requests.get(url, headers=self.headers) response.raise_for_status()
with open(output_path, 'wb') as f: f.write(response.content)
def track_scan(self, label_id, location, metadata=None): url = f"{self.base_url}/api/scans" data = { 'label_id': label_id, 'location': location, 'metadata': metadata or {} } response = requests.post(url, json=data, headers=self.headers) response.raise_for_status() return response.json()
def get_labels(self, limit=50, offset=0): url = f"{self.base_url}/api/labels" params = {'limit': limit, 'offset': offset} response = requests.get(url, params=params, headers=self.headers) response.raise_for_status() return response.json()
# Usageclient = ELabelClient('https://example-app.klutch.sh', 'your-api-key')
# Create labels in batchproducts = [ {'name': 'Product A', 'sku': 'A001'}, {'name': 'Product B', 'sku': 'B002'}, {'name': 'Product C', 'sku': 'C003'}]
for product in products: label = client.create_label( name=product['name'], content=f"SKU: {product['sku']}", metadata={'sku': product['sku']} ) print(f"Created label {label['id']} for {product['name']}")
# Download PDF client.download_label_pdf(label['id'], f"label_{product['sku']}.pdf")PHP Client
<?php
class ELabelClient { private $baseURL; private $apiKey;
public function __construct($baseURL, $apiKey = null) { $this->baseURL = rtrim($baseURL, '/'); $this->apiKey = $apiKey; }
private function request($method, $endpoint, $data = null) { $url = $this->baseURL . $endpoint;
$ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
$headers = ['Content-Type: application/json']; if ($this->apiKey) { $headers[] = 'X-API-Key: ' . $this->apiKey; } curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
if ($data !== null) { curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); }
$response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
if ($httpCode >= 400) { throw new Exception("API request failed with status $httpCode"); }
return json_decode($response, true); }
public function createLabel($name, $content, $metadata = []) { return $this->request('POST', '/api/labels', [ 'name' => $name, 'content' => $content, 'metadata' => $metadata ]); }
public function generateQRCode($data, $size = 300) { $result = $this->request('POST', '/api/labels/qr', [ 'data' => $data, 'size' => $size ]); return $result['qrCode']; }
public function trackScan($labelId, $location, $metadata = []) { return $this->request('POST', '/api/scans', [ 'label_id' => $labelId, 'location' => $location, 'metadata' => $metadata ]); }
public function getLabels($limit = 50, $offset = 0) { return $this->request('GET', "/api/labels?limit=$limit&offset=$offset"); }}
// Usage$client = new ELabelClient('https://example-app.klutch.sh', 'your-api-key');
// Create label$label = $client->createLabel( 'Warehouse Item', 'Description of item', ['sku' => 'WH001', 'location' => 'Aisle 5']);
echo "Label created with ID: " . $label['id'] . "\n";
// Track scan$scan = $client->trackScan( $label['id'], 'Receiving Dock', ['scanner' => 'Handheld Device', 'user' => 'operator1']);
echo "Scan tracked at: " . $scan['scanned_at'] . "\n";Advanced Configuration
Custom Label Templates
Create a custom template via the database:
INSERT INTO templates (name, description, config, is_default)VALUES ( 'Retail Price Label', 'Template for retail shelf labels with price and QR code', '{ "width": 300, "height": 200, "fontSize": 12, "fontFamily": "Helvetica", "includeQR": true, "qrSize": 100, "includePriceField": true, "backgroundColor": "#FFFFFF", "textColor": "#000000", "borderWidth": 2, "borderColor": "#333333" }', false);Environment-Based Configuration
For different environments (development, staging, production):
Development:
NODE_ENV=developmentDATABASE_HOST=localhostDATABASE_PORT=5432BASE_URL=http://localhost:3000Staging:
NODE_ENV=stagingDATABASE_HOST=staging-postgres.klutch.shDATABASE_PORT=8000BASE_URL=https://elabel-staging.klutch.shProduction:
NODE_ENV=productionDATABASE_HOST=production-postgres.klutch.shDATABASE_PORT=8000BASE_URL=https://elabel.klutch.shAPI_RATE_LIMIT=1000Scaling Configuration
For high-volume label generation:
Increase File Upload Limits:
MAX_FILE_SIZE=52428800Database Connection Pool:
Modify server.js pool configuration:
const pool = new Pool({ host: process.env.DATABASE_HOST, port: process.env.DATABASE_PORT, database: process.env.DATABASE_NAME, user: process.env.DATABASE_USER, password: process.env.DATABASE_PASSWORD, max: 50, // Increased from 20 idleTimeoutMillis: 30000, connectionTimeoutMillis: 2000,});Memory Limits:
For large PDF generation, increase Node.js memory:
CMD ["node", "--max-old-space-size=2048", "server.js"]Production Best Practices
Database Management
Connection Pooling:
- Use connection pools to avoid exhausting database connections
- Set
max: 20-50connections based on expected load - Monitor connection usage in production
Indexing:
The provided schema includes essential indexes. For high-volume deployments, add:
CREATE INDEX idx_labels_name ON labels(name);CREATE INDEX idx_scans_location ON scans(location);CREATE INDEX idx_labels_metadata_sku ON labels((metadata->>'sku'));Regular Backups:
# Backup E-Label databasepg_dump -h your-postgres-app.klutch.sh -p 8000 -U elabel elabel > elabel_backup.sql
# Restore from backuppsql -h your-postgres-app.klutch.sh -p 8000 -U elabel elabel < elabel_backup.sqlFile Storage Management
Cleanup Old Generated Files:
Add a cleanup script to remove old temporary files:
const fs = require('fs').promises;const path = require('path');
async function cleanupOldFiles(directory, maxAgeDays = 7) { const now = Date.now(); const maxAge = maxAgeDays * 24 * 60 * 60 * 1000;
const files = await fs.readdir(directory);
for (const file of files) { const filePath = path.join(directory, file); const stats = await fs.stat(filePath);
if (now - stats.mtime.getTime() > maxAge) { await fs.unlink(filePath); console.log(`Deleted old file: ${file}`); } }}
// Run cleanupcleanupOldFiles(process.env.GENERATED_DIR || '/app/public/generated') .then(() => console.log('Cleanup complete')) .catch(err => console.error('Cleanup error:', err));Volume Size Monitoring:
Monitor persistent volume usage:
du -sh /app/public/uploadsdu -sh /app/public/generatedSecurity Hardening
API Key Authentication:
Add API key middleware:
function authenticateAPIKey(req, res, next) { const apiKey = req.headers['x-api-key'];
if (!apiKey || apiKey !== process.env.API_KEY) { return res.status(401).json({ error: 'Unauthorized' }); }
next();}
// Apply to protected routesapp.use('/api/labels', authenticateAPIKey);Rate Limiting:
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 requests per windowMs message: 'Too many requests from this IP'});
app.use('/api/', limiter);Input Validation:
function validateLabelInput(req, res, next) { const { name, content } = req.body;
if (!name || name.length > 255) { return res.status(400).json({ error: 'Invalid name' }); }
if (content && content.length > 10000) { return res.status(400).json({ error: 'Content too long' }); }
next();}
app.post('/api/labels', validateLabelInput, async (req, res) => { // Handle label creation});Monitoring and Logging
Structured Logging:
const winston = require('winston');
const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: winston.format.json(), transports: [ new winston.transports.Console({ format: winston.format.simple() }) ]});
// Use throughout applicationlogger.info('Label created', { labelId: result.rows[0].id, user: 'admin' });logger.error('Database error', { error: err.message });Health Check Monitoring:
Set up external monitoring to check /health endpoint:
# Example with cron*/5 * * * * curl -f https://elabel.klutch.sh/health || echo "E-Label health check failed"Performance Metrics:
Add basic metrics endpoint:
let metrics = { totalLabels: 0, totalScans: 0, totalQRCodes: 0, uptime: process.uptime()};
app.get('/api/metrics', async (req, res) => { try { const labelCount = await pool.query('SELECT COUNT(*) FROM labels'); const scanCount = await pool.query('SELECT COUNT(*) FROM scans');
res.json({ labels: parseInt(labelCount.rows[0].count), scans: parseInt(scanCount.rows[0].count), uptime: process.uptime(), memory: process.memoryUsage(), timestamp: new Date().toISOString() }); } catch (error) { res.status(500).json({ error: 'Failed to fetch metrics' }); }});Troubleshooting
Issue: Application Won’t Start
Symptoms: Container starts but application doesn’t respond
Possible Causes and Solutions:
- Database Connection Failed:
Check PostgreSQL connectivity:
# Test from Klutch.sh app logs# Look for: "Database initialization error" or "ECONNREFUSED"Solution: Verify environment variables:
DATABASE_HOST=your-postgres-app.klutch.shDATABASE_PORT=8000DATABASE_NAME=elabelDATABASE_USER=elabelDATABASE_PASSWORD=correct-password- Port Already in Use:
Ensure PORT=3000 is set correctly and not conflicting.
- Missing Dependencies:
Check build logs for npm install errors. Rebuild if needed.
Issue: PDF Generation Fails
Symptoms: /api/labels/:id/pdf returns 500 error
Solutions:
- Cairo/Pango Missing:
The Dockerfile includes these, but verify in logs. Rebuild if fonts are missing.
- Memory Limit:
Increase Node.js memory:
CMD ["node", "--max-old-space-size=2048", "server.js"]- Large Content:
Reduce content size or split across multiple pages.
Issue: QR Code Not Generating
Symptoms: /api/labels/qr returns error
Solutions:
- Invalid Data:
Ensure data field is not empty and is valid URL or text.
- Size Too Large:
QR code size must be reasonable (100-500 pixels). Larger sizes may timeout.
- Check Dependencies:
Verify qrcode package is installed:
npm list qrcodeIssue: File Upload Fails
Symptoms: Cannot upload images or logos
Solutions:
- Volume Not Mounted:
Verify persistent volume is attached at /app/public/uploads.
- Permissions:
Ensure directory is writable:
RUN mkdir -p /app/public/uploads && chown -R node:node /app- File Size Limit:
Check MAX_FILE_SIZE environment variable:
MAX_FILE_SIZE=10485760Issue: Slow Label Generation
Symptoms: API requests timeout or take too long
Solutions:
- Increase Resources:
Scale up container CPU and memory in Klutch.sh dashboard.
- Database Query Optimization:
Add indexes for frequently queried fields:
CREATE INDEX idx_labels_created_at ON labels(created_at DESC);- Connection Pool:
Increase database connection pool size in server.js.
- Batch Processing:
For bulk operations, process in smaller batches with delays.
Issue: Database Connection Pool Exhausted
Symptoms: “sorry, too many clients already” error
Solutions:
- Increase Pool Size:
const pool = new Pool({ // ... max: 50, // Increase from 20});- Release Connections:
Ensure all database queries properly release connections:
const client = await pool.connect();try { // Query here} finally { client.release(); // Always release}- Scale PostgreSQL:
Increase max connections in PostgreSQL configuration.
Additional Resources
- Node.js Documentation
- node-postgres (pg) Documentation
- QRCode Library Documentation
- PDFKit Documentation
- bwip-js Barcode Generator
- Express.js Documentation
- PostgreSQL Documentation
Related Guides
- PostgreSQL - Deploy PostgreSQL for E-Label data storage
- Express.js - Learn more about Express.js deployment
- MinIO - Alternative object storage for generated files
You now have E-Label running on Klutch.sh! Your digital label and QR code management system is ready to generate labels, track assets, and provide scalable labeling solutions for your organization. Configure templates, integrate with your existing systems via the API, and start creating professional labels with embedded QR codes and barcodes.