Overview
shrtnr uses Neon PostgreSQL for persistent data storage and Upstash Redis for high-performance caching. This guide covers the database schema, initialization, and caching strategy.Database Architecture
Neon PostgreSQL
Primary Database
- Stores URLs, users, and analytics
- Serverless with auto-scaling
- Connection pooling built-in
Upstash Redis
Cache Layer
- Caches frequently accessed short URLs
- Rate limiting for API endpoints
- Session management
Database Schema
The schema is defined in/app/db/migrations/001_initial.sql:
URLs Table
Stores all shortened URLs and their metadata:Auto-incrementing primary key
Unique identifier for the short URL (e.g.,
abc123)- Generated using
nanoidlibrary - 10 character maximum
- Indexed for fast lookups
The full destination URL that the short code redirects to
When the short URL was created
Optional expiration date - URLs expire and stop working after this time
Indexed for efficient cleanup of expired URLs
Whether the user provided a custom short code (vs randomly generated)
Total number of times this URL has been accessed
Foreign key to
users.id - owner of this short URL- NULL for anonymous/guest links
- SET NULL on user deletion (preserves links)
Clicks Table
Detailed analytics for each click on a short URL:Reference to the shortened URL that was clicked
Not a foreign key to allow analytics retention even if URL is deleted
Exact timestamp of the click
Visitor’s IP address (IPv4 or IPv6)
- IPv4: max 15 characters (
255.255.255.255) - IPv6: max 45 characters
Browser/client user agent string for device detection
HTTP Referer header - where the visitor came from
ISO 3166-1 alpha-2 country code (e.g.,
US, GB, JP)Detected device category:
desktop, mobile, tablet, botUsers Table
Authentication and user management for NextAuth:Unique user identifier (UUID or similar)
User’s email address - must be unique
User’s display name (optional)
Hashed password using
bcryptjsNever stores plaintext passwords - only bcrypt hashes
Account creation timestamp
Last profile update timestamp
Database Initialization
Setup Script
The database is initialized using the migration system:app/db/init.ts
Running Migrations
Run Initialization Script
npx tsx scripts/init-db.ts which:- Connects to Neon PostgreSQL
- Reads all
.sqlfiles fromapp/db/migrations/ - Executes them in alphabetical order
- Creates tables and indexes
Manual Verification
Connect to your Neon database to verify tables:Redis Caching Strategy
Connection Setup
app/lib/db.ts
Caching Pattern
shrtnr uses Redis to cache short URL lookups:Cache Invalidation
Invalidate cache when URLs are updated or deleted:Rate Limiting
Use Redis for API rate limiting:Indexes and Performance
The schema includes strategic indexes for fast queries:Short Code Lookup
SELECT * FROM urls WHERE short_code = 'abc123'User URLs
SELECT * FROM urls WHERE user_id = 'user123'Expiration Cleanup
DELETE FROM urls WHERE expires_at < NOW()Click Analytics
SELECT COUNT(*) FROM clicks WHERE short_code = 'abc123'Backup and Maintenance
Automated Backups (Neon)
Neon provides automatic backups:- Point-in-time recovery: Restore to any point in the last 7 days (free tier) or 30 days (paid)
- Branch-based backups: Create a database branch for testing
Access backups in Neon Console → Project → Branches → Create branch
Manual Backup
Cleanup Expired URLs
Create a scheduled job to remove expired URLs:Troubleshooting
Migration fails with SSL error
Migration fails with SSL error
Error:
SSL SYSCALL error: EOF detectedSolution:- Verify
POSTGRES_URLincludes?sslmode=require - Check Neon project is active (not suspended)
- Use the pooled connection string for serverless environments
Duplicate key error on short_code
Duplicate key error on short_code
Error:
duplicate key value violates unique constraint "urls_short_code_key"Solution:
This is rare with nanoid but can happen:- Implement retry logic when generating short codes
- Increase short code length in
nanoid()call - Check for malicious attempts to guess codes
Slow query performance
Slow query performance
Symptoms: URL lookups taking >100msSolutions:
- Verify indexes exist:
\d urlsin psql - Check Redis cache hit rate
- Enable connection pooling (already enabled in Neon)
- Monitor with Neon’s query insights dashboard
Redis cache misses
Redis cache misses
Symptoms: High database load despite cachingSolutions:
- Verify
UPSTASH_REDIS_REST_URLand token are correct - Check Upstash dashboard for connection errors
- Increase cache TTL (currently 3600s / 1 hour)
- Monitor cache hit/miss ratio in application logs
Query Examples
Next Steps
Environment Variables
Configure database connection strings
Deployment Guide
Deploy your application with the database