AWS Serverless QR Platform

Written by Terrell Flautt on October 3, 2025

· 6 min · snapitqr.com

Architecture

React Frontend (S3/CloudFront)
    ↓
API Gateway + Lambda Functions
    ↓
DynamoDB + S3 Storage
    ↓
SSM Parameters (secrets)

Core Components

1. QR Generation Lambda

exports.generateQR = async (event) => {
    const { url, options } = JSON.parse(event.body);

    // Get API keys from SSM
    const qrApiKey = await ssm.getParameter({
        Name: '/snapitqr/qr-api-key',
        WithDecryption: true
    }).promise();

    // Generate QR with custom branding
    const qrData = await qrcode.toDataURL(url, {
        errorCorrectionLevel: 'H',
        width: options.size || 512,
        color: {
            dark: options.darkColor || '#000',
            light: options.lightColor || '#fff'
        }
    });

    // Store in S3
    const s3Key = `qr-codes/${uuidv4()}.png`;
    await s3.upload({
        Bucket: 'snapitqr-assets',
        Key: s3Key,
        Body: Buffer.from(qrData.split(',')[1], 'base64'),
        ContentType: 'image/png'
    }).promise();

    // Save metadata to DynamoDB
    await dynamoDB.put({
        TableName: 'qr_codes',
        Item: {
            id: s3Key.split('/')[1].split('.')[0],
            url,
            s3Key,
            createdAt: new Date().toISOString(),
            userId: event.requestContext.authorizer?.userId,
            options
        }
    }).promise();

    return {
        statusCode: 200,
        body: JSON.stringify({
            id: s3Key.split('/')[1].split('.')[0],
            downloadUrl: `https://cdn.snapitqr.com/${s3Key}`
        })
    };
};

2. Analytics Tracking Lambda

exports.trackScan = async (event) => {
    const { qrId } = event.pathParameters;
    const userAgent = event.headers['User-Agent'];
    const ip = event.requestContext.identity.sourceIp;

    // Record scan analytics
    await dynamoDB.put({
        TableName: 'qr_scans',
        Item: {
            qrId,
            scanId: uuidv4(),
            timestamp: Date.now(),
            userAgent,
            ip,
            country: await getCountryFromIP(ip)
        }
    }).promise();

    // Get original URL
    const qrData = await dynamoDB.get({
        TableName: 'qr_codes',
        Key: { id: qrId }
    }).promise();

    // Redirect to original URL
    return {
        statusCode: 302,
        headers: {
            Location: qrData.Item.url,
            'Cache-Control': 'no-cache'
        }
    };
};

3. React Frontend

// QR Generator Component
function QRGenerator() {
    const [qrOptions, setQrOptions] = useState({
        url: '',
        size: 512,
        darkColor: '#000000',
        lightColor: '#ffffff'
    });

    const generateQR = async () => {
        const response = await fetch('/api/generate-qr', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(qrOptions)
        });

        const result = await response.json();
        setGeneratedQR(result);
    };

    return (
        <div className="qr-generator">
            <input
                value={qrOptions.url}
                onChange={(e) => setQrOptions({...qrOptions, url: e.target.value})}
                placeholder="Enter URL to encode"
            />
            <ColorPicker
                value={qrOptions.darkColor}
                onChange={(color) => setQrOptions({...qrOptions, darkColor: color})}
            />
            <button onClick={generateQR}>Generate QR Code</button>
        </div>
    );
}

DynamoDB Schema

qr_codes table:
- id (string) - partition key
- url (string) - original URL
- s3Key (string) - S3 object key
- createdAt (string) - ISO timestamp
- userId (string) - owner ID
- options (map) - QR styling options

qr_scans table:
- qrId (string) - partition key
- scanId (string) - sort key
- timestamp (number) - scan time
- userAgent (string) - device info
- ip (string) - source IP
- country (string) - geo location

SSM Parameters

/snapitqr/qr-api-key - QR generation service key
/snapitqr/db-connection - Database connection string
/snapitqr/jwt-secret - JWT signing secret
/snapitqr/s3-bucket - Asset storage bucket
/snapitqr/google-oauth-client - OAuth client ID

Key Fixes

Cold Start Performance

Problem: Lambda cold starts causing 2s+ delays.
Fix: Provisioned concurrency + connection pooling.

// Lambda connection pooling
let dynamoClient;
const getDynamoClient = () => {
    if (!dynamoClient) {
        dynamoClient = new AWS.DynamoDB.DocumentClient({
            region: process.env.AWS_REGION,
            maxRetries: 3
        });
    }
    return dynamoClient;
};

CORS Configuration

Problem: React app blocked by CORS policy.
Fix: API Gateway CORS + preflight handling.

// API Gateway CORS response
const corsHeaders = {
    'Access-Control-Allow-Origin': 'https://snapitqr.com',
    'Access-Control-Allow-Headers': 'Content-Type,Authorization',
    'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS'
};

Image Storage Optimization

Problem: Large QR images slow download.
Fix: CloudFront CDN + automatic compression.

// S3 upload with optimization
await s3.upload({
    Bucket: 'snapitqr-assets',
    Key: s3Key,
    Body: optimizedImageBuffer,
    ContentType: 'image/png',
    CacheControl: 'max-age=31536000', // 1 year
    Metadata: {
        'original-size': originalSize.toString(),
        'compressed-size': compressedSize.toString()
    }
}).promise();

Monitoring

CloudWatch Metrics:
- Lambda duration/errors
- DynamoDB read/write capacity
- S3 request metrics
- API Gateway latency

Custom Dashboards:
- QR generation rate
- Scan analytics
- Error rates by function
- Cost per request

Performance