[TERMINAL · SKILLS]
> mounting /skills...
> indexing 295 manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Terminal.skills
Use Cases/Build a QR Code Generator API

Build a QR Code Generator API

Build a QR code generation API with custom styling, logo embedding, dynamic QR codes with editable destinations, scan analytics, and batch generation — powering marketing campaigns and product packaging.

#redis#caching#database#pub-sub#queues
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 5 skills

Avg quality 93/100·All SAFE
>

typescript

v

Not yet scored
View skill
>

redis

v1.0.0

Build applications with Redis — caching, session storage, pub/sub, streams, rate limiting, leaderboards, and queues. Use when tasks involve in-memory data storage, real-time messaging, distributed locking, or performance optimization with caching layers.

93/100 quality
1.81× impact
SAFE
View skill
>

postgresql

v1.0.0

Assists with designing schemas, writing performant queries, managing indexes, and operating PostgreSQL databases. Use when working with JSONB, full-text search, window functions, CTEs, row-level security, replication, or performance tuning. Trigger words: postgresql, postgres, sql, database, jsonb, rls, window functions, cte.

87/100 quality
1.53× impact
SAFE
View skill
>

hono

v1.0.0

You are an expert in Hono, the ultrafast web framework for the edge. You help developers build APIs and web applications that run on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda, and Vercel Edge — with a tiny footprint (~14KB), middleware ecosystem, JSX support, RPC client, and Web Standards API compatibility that makes code truly portable across runtimes.

93/100 quality
3.00× impact
SAFE
View skill
>

zod

v1.0.0

You are an expert in Zod, the TypeScript-first schema declaration and validation library. You help developers define schemas that validate data at runtime AND infer TypeScript types at compile time — eliminating the need to write types and validators separately. Used for API input validation, form validation, environment variables, config files, and any data boundary.

100/100 quality
1.21× impact
SAFE
View skill
$

The Problem

Liam leads marketing at a 20-person consumer brand. They print QR codes on product packaging, event materials, and ads. Each QR code is generated manually in a free online tool, downloaded, and sent to the printer. When a campaign URL changes, they can't update printed QR codes — they've wasted $15K on packaging with dead links. They have no idea which QR codes get scanned, from where, or on what devices. They need dynamic QR codes (URL can change after printing), branded with their logo, and tracked with scan analytics.

Step 1: Build the QR Code Engine

typescript
// src/qr/generator.ts — QR code generation with dynamic links and scan tracking
import QRCode from "qrcode";
import sharp from "sharp";
import { pool } from "../db";
import { Redis } from "ioredis";
import { createHash } from "node:crypto";

const redis = new Redis(process.env.REDIS_URL!);

interface QRCodeConfig {
  id: string;
  name: string;
  type: "static" | "dynamic";
  destinationUrl: string;
  shortCode: string;           // for dynamic QR: resolves to destination
  style: {
    foreground: string;        // hex color
    background: string;
    cornerRadius: number;      // 0-50
    logoUrl: string | null;
    logoSize: number;          // percentage of QR size (10-30)
    errorCorrection: "L" | "M" | "Q" | "H";
    size: number;              // pixels
    margin: number;
  };
  scanCount: number;
  createdBy: string;
  createdAt: string;
}

// Create a QR code
export async function createQRCode(
  name: string,
  destinationUrl: string,
  options?: {
    type?: "static" | "dynamic";
    foreground?: string;
    background?: string;
    logoUrl?: string;
    logoSize?: number;
    size?: number;
    userId?: string;
  }
): Promise<{ qrCode: QRCodeConfig; imageBuffer: Buffer; imageUrl: string }> {
  const id = `qr-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
  const type = options?.type || "dynamic";
  const shortCode = generateShortCode();

  // For dynamic QR, the encoded URL is our redirect endpoint
  const encodedUrl = type === "dynamic"
    ? `${process.env.APP_URL}/q/${shortCode}`
    : destinationUrl;

  const style: QRCodeConfig["style"] = {
    foreground: options?.foreground || "#000000",
    background: options?.background || "#FFFFFF",
    cornerRadius: 0,
    logoUrl: options?.logoUrl || null,
    logoSize: options?.logoSize || 20,
    errorCorrection: options?.logoUrl ? "H" : "M", // H for logo overlay
    size: options?.size || 1024,
    margin: 2,
  };

  // Generate QR code
  const imageBuffer = await generateQRImage(encodedUrl, style);

  // Store config
  const qrCode: QRCodeConfig = {
    id, name, type, destinationUrl, shortCode,
    style, scanCount: 0,
    createdBy: options?.userId || "",
    createdAt: new Date().toISOString(),
  };

  await pool.query(
    `INSERT INTO qr_codes (id, name, type, destination_url, short_code, style, created_by, created_at)
     VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())`,
    [id, name, type, destinationUrl, shortCode, JSON.stringify(style), options?.userId]
  );

  // Cache redirect for dynamic QR
  if (type === "dynamic") {
    await redis.set(`qr:redirect:${shortCode}`, JSON.stringify({ id, url: destinationUrl }));
  }

  // Cache generated image
  const imageKey = `qr:image:${id}`;
  await redis.setex(imageKey, 86400 * 30, imageBuffer);

  return {
    qrCode,
    imageBuffer,
    imageUrl: `${process.env.APP_URL}/api/qr/${id}/image`,
  };
}

// Generate QR image with custom styling
async function generateQRImage(url: string, style: QRCodeConfig["style"]): Promise<Buffer> {
  // Generate base QR code
  const qrBuffer = await QRCode.toBuffer(url, {
    errorCorrectionLevel: style.errorCorrection,
    width: style.size,
    margin: style.margin,
    color: {
      dark: style.foreground,
      light: style.background,
    },
  });

  let image = sharp(qrBuffer);

  // Overlay logo in center
  if (style.logoUrl) {
    const logoResponse = await fetch(style.logoUrl);
    const logoBuffer = Buffer.from(await logoResponse.arrayBuffer());

    const logoPixels = Math.round(style.size * (style.logoSize / 100));
    const resizedLogo = await sharp(logoBuffer)
      .resize(logoPixels, logoPixels, { fit: "contain", background: { r: 255, g: 255, b: 255, alpha: 1 } })
      .toBuffer();

    // Add white padding around logo
    const padding = 8;
    const paddedLogo = await sharp({
      create: {
        width: logoPixels + padding * 2,
        height: logoPixels + padding * 2,
        channels: 4,
        background: style.background,
      },
    }).composite([{
      input: resizedLogo,
      left: padding,
      top: padding,
    }]).png().toBuffer();

    const offset = Math.round((style.size - logoPixels - padding * 2) / 2);
    image = image.composite([{
      input: paddedLogo,
      left: offset,
      top: offset,
    }]);
  }

  return image.png().toBuffer();
}

// Resolve dynamic QR code (redirect endpoint)
export async function resolveQR(shortCode: string, scanData: {
  ip: string; userAgent: string; referrer: string;
}): Promise<string | null> {
  const cached = await redis.get(`qr:redirect:${shortCode}`);
  if (!cached) return null;

  const { id, url } = JSON.parse(cached);

  // Track scan (non-blocking)
  trackScan(id, shortCode, scanData).catch(() => {});

  return url;
}

// Update destination URL (dynamic QR only)
export async function updateDestination(qrId: string, newUrl: string): Promise<void> {
  const { rows: [qr] } = await pool.query(
    "SELECT short_code, type FROM qr_codes WHERE id = $1", [qrId]
  );
  if (!qr || qr.type !== "dynamic") throw new Error("Can only update dynamic QR codes");

  await pool.query("UPDATE qr_codes SET destination_url = $2 WHERE id = $1", [qrId, newUrl]);
  await redis.set(`qr:redirect:${qr.short_code}`, JSON.stringify({ id: qrId, url: newUrl }));
}

// Track scan analytics
async function trackScan(qrId: string, shortCode: string, data: {
  ip: string; userAgent: string; referrer: string;
}): Promise<void> {
  const day = new Date().toISOString().slice(0, 10);

  await redis.hincrby(`qr:scans:${qrId}:daily`, day, 1);
  await redis.incr(`qr:scans:total:${qrId}`);

  // Parse device
  const device = data.userAgent.includes("Mobile") ? "mobile" : "desktop";
  await redis.hincrby(`qr:scans:${qrId}:device`, device, 1);

  await pool.query(
    `INSERT INTO qr_scans (qr_id, ip_hash, device, user_agent, referrer, scanned_at)
     VALUES ($1, $2, $3, $4, $5, NOW())`,
    [qrId, createHash("md5").update(data.ip).digest("hex").slice(0, 12),
     device, data.userAgent.slice(0, 200), data.referrer]
  );
}

// Get scan analytics
export async function getScanAnalytics(qrId: string): Promise<{
  totalScans: number;
  scansByDay: Record<string, number>;
  scansByDevice: Record<string, number>;
  uniqueScans: number;
}> {
  const [total, daily, devices, unique] = await Promise.all([
    redis.get(`qr:scans:total:${qrId}`),
    redis.hgetall(`qr:scans:${qrId}:daily`),
    redis.hgetall(`qr:scans:${qrId}:device`),
    pool.query("SELECT COUNT(DISTINCT ip_hash) as unique_count FROM qr_scans WHERE qr_id = $1", [qrId]),
  ]);

  return {
    totalScans: parseInt(total || "0"),
    scansByDay: Object.fromEntries(Object.entries(daily).map(([k, v]) => [k, parseInt(v)])),
    scansByDevice: Object.fromEntries(Object.entries(devices).map(([k, v]) => [k, parseInt(v)])),
    uniqueScans: parseInt(unique.rows[0].unique_count),
  };
}

// Batch generate QR codes (for product packaging)
export async function batchGenerate(items: Array<{
  name: string; url: string; sku?: string;
}>, style?: Partial<QRCodeConfig["style"]>): Promise<Array<{ name: string; imageBuffer: Buffer }>> {
  const results = [];
  for (const item of items) {
    const { imageBuffer } = await createQRCode(item.name, item.url, {
      type: "dynamic",
      ...style,
    });
    results.push({ name: item.name, imageBuffer });
  }
  return results;
}

function generateShortCode(): string {
  const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
  return Array.from({ length: 8 }, () => chars[Math.floor(Math.random() * chars.length)]).join("");
}

Results

  • $15K packaging waste eliminated — dynamic QR codes let them change the destination URL after printing; expired campaign → updated to new landing page in 5 seconds
  • Scan analytics reveal campaign performance — "The subway poster got 3,200 scans (82% mobile), the flyer got 45" — budget reallocated to high-performing channels
  • Branded QR codes with logo — company logo embedded in center; QR codes look professional on packaging instead of generic black-and-white squares
  • Batch generation for product lines — 500 SKU-specific QR codes generated in 2 minutes; each links to the product page and tracks scans independently
  • Unique vs total scans — IP hashing distinguishes "100 people scanned" from "1 person scanned 100 times"; accurate reach measurement