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
- QR Generation: 200ms average
- Scan Redirect: 50ms average
- Cost: $0.01 per 100 QR codes
- Scalability: 10,000+ concurrent requests