Hosting & Infrastructure
Overview
BookWish uses a modern, serverless-first hosting architecture designed for scalability and cost efficiency.
| Component | Host | Service Type | Purpose |
|---|---|---|---|
| Backend API | Railway | Node.js server | REST API + WebSocket |
| Flutter Web | Vercel | Static + SSR | Main app web version |
| Group Store | Vercel | Next.js SSR | Marketplace at bookwish.io |
| Store Websites | Vercel | Next.js SSR | Per-store websites |
| Database | Supabase | PostgreSQL | Primary data store |
| Redis Cache | Railway | Redis | Caching + job queues |
| Object Storage | Supabase Storage | S3-compatible | Book covers, avatars, uploads |
Railway (Backend API)
Configuration
Service Type: Node.js/TypeScript Environment: Production + Staging Region: US West (Oregon)
Deployment:
- Automatic deploys from
mainbranch - Railway CLI for manual deploys
- Environment variables via Railway dashboard
- Automatic HTTPS with custom domain
Resources:
- Memory: 512 MB - 2 GB (auto-scaling)
- CPU: Shared (fair-use)
- Persistent volume for logs (optional)
Environment Variables:
# Database
DATABASE_URL=postgresql://...
# Redis
REDIS_URL=redis://...
# Secrets
JWT_SECRET=...
ENCRYPTION_KEY=...
# External APIs
STRIPE_SECRET_KEY=...
SQUARE_APP_ID=...
GOOGLE_BOOKS_API_KEY=...
INGRAM_API_KEY=...
EASYPOST_API_KEY=...
BOOKSRUN_API_KEY=...
# Email
SENDGRID_API_KEY=...
# Push Notifications
FCM_SERVER_KEY=...
APNS_KEY_ID=...
APNS_TEAM_ID=...
Health Check:
- Endpoint:
GET /health - Interval: 30 seconds
- Timeout: 10 seconds
Logs:
- Structured JSON logging
- Log retention: 7 days
- Real-time viewing via Railway dashboard
Redis (Railway)
Service Type: Redis 7+ Memory: 256 MB - 1 GB Persistence: RDB snapshots (daily)
Use Cases:
- Session storage
- Feed page caching (5-min TTL)
- Rate limiting counters
- Background job queues (Bull)
- Real-time pub/sub
Connection:
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL, {
maxRetriesPerRequest: 3,
enableReadyCheck: true,
retryStrategy: (times) => Math.min(times * 50, 2000),
});
Supabase (Database + Storage)
PostgreSQL
Service Type: Managed PostgreSQL 14+ Region: US East (closest to users) Tier: Pro (for production)
Features Used:
- Connection pooling (PgBouncer)
- Automatic backups (daily)
- Point-in-time recovery (7 days)
- Read replicas (future scaling)
Connection:
- Prisma via connection string
- Connection pooling in transaction mode
- SSL/TLS required
Database Management:
# Migrations via Prisma
npx prisma migrate dev --name migration_name
# Production migrations
npx prisma migrate deploy
# Database introspection
npx prisma db pull
# Studio (GUI)
npx prisma studio
Backup Strategy:
- Automated daily backups (Supabase)
- Manual backups before major migrations
- Export scripts for critical data
- Disaster recovery plan documented
Supabase Storage
Service Type: S3-compatible object storage Buckets:
book-covers- Book cover images (public)user-avatars- User profile images (public)store-assets- Store logos, banners (public)uploads- General file uploads (private)
Configuration:
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_ANON_KEY
);
// Upload example
await supabase.storage
.from('user-avatars')
.upload(`${userId}/avatar.jpg`, file, {
cacheControl: '3600',
upsert: true,
});
Access Control:
- Public buckets: Direct CDN URLs
- Private buckets: Signed URLs with expiration
- RLS (Row Level Security) policies
Vercel (Flutter Web + Store Websites)
Flutter Web (Main App)
Project: bookwish-app-web
Framework: Flutter Web (compiled to JS)
Deployment: Automatic from main branch
Build Configuration:
{
"buildCommand": "flutter build web --release",
"outputDirectory": "build/web",
"framework": null
}
Environment Variables:
API_BASE_URL=https://api.bookwish.io/v1
STRIPE_PUBLISHABLE_KEY=pk_live_...
Features:
- Edge caching for static assets
- Gzip/Brotli compression
- Automatic HTTPS
- Custom domain:
app.bookwish.io - Preview deployments for PRs
Group Store (Marketplace)
Project: bookwish-marketplace
Framework: Next.js 16+ (App Router with Turbopack)
Deployment: Automatic from main branch
Domain: bookwish.io (root domain)
Purpose: Aggregated inventory discovery for users without a home store
Tech Stack:
- Next.js with App Router
- Tailwind CSS (design system colors: Parchment, Ink Blue, Brick Red)
- Stripe Connect for multi-vendor payments
- Real-time inventory aggregation
Business Logic:
- Only shows inventory from stores with
group_store_opt_in = trueandstripe_account_status = 'active' - 15% commission on all sales
- Multi-vendor cart and checkout
- Payments via Stripe Connect:
- Single-vendor: Destination charge (funds go directly to store minus 15%)
- Multi-vendor: Platform charge with weekly batch transfers
User Flow:
- User discovers book on group store
- Sees which stores have it (sorted by price, with distance when available)
- Adds to cart (can be multi-vendor)
- Checks out via Stripe
- Each store receives order notification
- BookWish receives 15% commission
Conversion Goals:
- CTA to download app
- CTA to set a home store (reduces future commission to 0%)
- Promotes local bookstore relationships
Build Configuration:
{
"buildCommand": "npm run build",
"outputDirectory": ".next",
"framework": "nextjs"
}
Routes:
/marketplace
├── /app
│ ├── page.tsx # Homepage
│ ├── /books
│ │ ├── page.tsx # Browse books
│ │ └── /[bookId]
│ │ └── page.tsx # Book detail with vendor options
│ ├── /stores
│ │ └─ ─ page.tsx # Store directory
│ ├── /cart
│ │ └── page.tsx # Multi-vendor cart
│ └── /checkout
│ └── page.tsx # Stripe Connect checkout
└── /lib
├── api.ts # Backend API client
├── stripe.ts # Stripe Elements
└── config.ts # API URLs
Store Websites (Next.js)
Project: bookwish-stores
Framework: Next.js 14+ (App Router)
Deployment: Automatic from main branch
Routing:
- Path-based:
bookwish.shop/{store-slug} - Future: Custom domains per store
Build Configuration:
{
"buildCommand": "npm run build",
"outputDirectory": ".next",
"framework": "nextjs"
}
Dynamic Routes:
/stores
├── /app
│ ├── /[storeSlug]
│ │ ├── page.tsx # Store home
│ │ ├── /books
│ │ │ ├── page.tsx # Browse inventory
│ │ │ └── /[bookId]
│ │ │ └── page.tsx # Book detail
│ │ ├── /cart
│ │ ├── /checkout
│ │ └── /subscription
│ └── layout.tsx
Theming:
// Store branding applied via CSS variables
const storeTheme = {
'--primary-color': store.primary_color || '#233548', // Ink Blue default
'--logo-url': `url(${store.logo_url})`,
};
ISR (Incremental Static Regeneration):
- Store pages: Revalidate every 60 seconds
- Book pages: Revalidate every 300 seconds
- On-demand revalidation via webhook
Domain Configuration:
- Default:
bookwish.shop/{slug} - Custom domains (Phase 3):
- Add domain to Vercel project
- Configure DNS (CNAME → Vercel)
- Auto-provision SSL certificate
- Map slug to domain in database
Domain Architecture
| Domain | Service | Purpose |
|---|---|---|
bookwish.io | marketplace/ | Group store, discovery |
app.bookwish.io | app/ (Flutter Web) | Main app |
*.bookwish.shop | stores/ | Individual store sites |
share.bookwish.io | web/ | Public wishlist sharing |
docs.bookwish.io | docs-site/ | Documentation |
Note: The backend API is hosted on Railway at bookwishmonorepo-production.up.railway.app and does not use a bookwish.io subdomain.
Commission & Payment Model
BookWish uses different payment processors and commission structures based on the purchase context:
| Source | Processor | Commission | Home Store |
|---|---|---|---|
| App (with home store) | Square | 0% | Yes |
| App (no home store) | Stripe Connect | 15% | No |
| Group Store | Stripe Connect | 15% | No |
| Individual Store Site | Square | 0% | N/A |
| BookWish Direct | Stripe | Margin | N/A |
Store Onboarding Order:
- Create account
- Stripe Connect onboarding (required for group store participation)
- Square connection (optional, enables 0% commission direct sales)
Payment Flow Details:
Single-Vendor Marketplace Orders:
- Uses Stripe Connect destination charge
- Funds go directly to store's connected account
- 15% application_fee deducted by platform
- No transfer fees (fee-optimized)
Multi-Vendor Marketplace Orders:
- Uses Stripe Connect platform charge
- Funds collected by platform
- Weekly automated transfers to stores (every Monday at 9:00 AM)
- Balance tracked in
store_balance_ledgertable - Minimum $25 threshold for automatic payout
- Early payout available: $10 minimum with $0.25 fee
CDN & Caching
Edge Caching (Vercel)
Static Assets:
- Immutable assets:
Cache-Control: public, max-age=31536000, immutable - Book covers: 1 year cache
- Avatars: 1 hour cache (user can update)
- App bundle: Cache by version hash
Dynamic Content:
- Store pages: 60-second cache with stale-while-revalidate
- Book availability: No cache (real-time inventory)
- API responses: No edge cache (handled by Redis)
Redis Caching (Backend)
Feed Pages:
// Cache key: feed:{userId}:{scope}:{page}
// TTL: 5 minutes
const cacheKey = `feed:${userId}:${scope}:${page}`;
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
const feed = await generateFeed(userId, scope, page);
await redis.setex(cacheKey, 300, JSON.stringify(feed));
return feed;
Book Data:
// Cache key: book:{isbn}
// TTL: 1 day
const cacheKey = `book:${isbn}`;
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
const book = await fetchBookData(isbn);
await redis.setex(cacheKey, 86400, JSON.stringify(book));
return book;
Monitoring & Observability
Application Monitoring
Railway Dashboard:
- CPU and memory usage
- Request/response metrics
- Error rates
- Deployment history
Custom Logging:
import winston from 'winston';
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'error.log', level: 'error' }),
],
});
logger.info('Order created', { orderId, userId, total });
logger.error('Payment failed', { error, orderId });
Database Monitoring
Supabase Dashboard:
- Query performance
- Connection pool usage
- Database size and growth
- Slow query log
Alerting:
- Database size > 80% quota
- Connection pool > 80% capacity
- Slow queries > 1 second
- Failed backups
Error Tracking
Sentry (Optional):
- Backend error tracking
- Frontend error tracking
- Performance monitoring
- User session replay
Deployment Strategy
Backend Deployment
-
Development:
npm run dev -
Staging:
git push origin staging
# Railway auto-deploys to staging environment -
Production:
git push origin main
# Railway auto-deploys to production -
Rollback:
railway rollback [deployment-id]
Database Migrations
Development:
npx prisma migrate dev --name add_feature
Production:
# Run before backend deployment
npx prisma migrate deploy
Best Practices:
- Always test migrations on staging first
- Write reversible migrations when possible
- Backup database before major schema changes
- Use
prisma migrate diffto review changes
Frontend Deployment
Flutter Web:
flutter build web --release
# Vercel auto-deploys from git push
Store Websites:
npm run build
# Vercel auto-deploys from git push
Security
HTTPS/TLS
- All services enforce HTTPS
- TLS 1.2+ required
- Automatic certificate management
- HSTS headers enabled
Environment Variables
- Never commit secrets to git
- Use Railway/Vercel secret management
- Rotate secrets quarterly
- Different secrets per environment
Network Security
- Database: Private network (Railway ↔ Supabase)
- API rate limiting (Redis-based)
- DDoS protection (Vercel/Railway)
- CORS configured per environment
Access Control
- Database: Role-based access (Prisma)
- Storage: RLS policies (Supabase)
- API: JWT authentication
- Admin: Separate authentication tier
Cost Estimation (Production)
| Service | Tier | Monthly Cost (est.) |
|---|---|---|
| Railway (API) | Hobby | $5 - $20 |
| Railway (Redis) | Included | $0 |
| Supabase (DB + Storage) | Pro | $25 |
| Vercel (Web + Stores) | Pro | $20 |
| Stripe | Transaction fees | 2.9% + $0.30 per transaction |
| SendGrid | Essentials | $15 (40k emails/month) |
| Total | ~$65 - $80 + transaction fees |
Scaling Considerations:
- Railway auto-scales within plan limits
- Supabase: Read replicas for high traffic
- Redis: Upgrade memory as needed
- Vercel: Usage-based pricing for high traffic
Disaster Recovery
Backup Strategy
-
Database:
- Automated daily backups (Supabase)
- Point-in-time recovery (7 days)
- Weekly manual exports to S3
-
Storage:
- Versioned buckets (Supabase)
- Cross-region replication (future)
-
Code:
- Git version control (GitHub)
- Tagged releases
- Deployment history (Railway/Vercel)
Recovery Procedures
-
Database Failure:
- Restore from latest Supabase backup
- Verify data integrity
- Update connection strings if needed
- Test critical flows
-
API Downtime:
- Check Railway dashboard for errors
- Rollback to last known good deployment
- Scale up resources if needed
- Investigate root cause
-
Redis Failure:
- Redis is cache-only (no data loss)
- Restart Redis service
- Cache will rebuild naturally
Monitoring & Alerts
Critical Alerts:
- API response time > 2 seconds
- Error rate > 1%
- Database connections > 80%
- Storage > 80% capacity
Notification Channels:
- Email to operations team
- Slack webhook
- PagerDuty (for production incidents)
Summary
BookWish's hosting strategy prioritizes:
- Simplicity - Managed services reduce operational burden
- Scalability - Auto-scaling and serverless where possible
- Cost Efficiency - Pay for what you use
- Reliability - Automatic backups and disaster recovery
- Developer Experience - Automatic deployments, preview environments