Skip to main content

Hosting & Infrastructure

Overview

BookWish uses a modern, serverless-first hosting architecture designed for scalability and cost efficiency.

ComponentHostService TypePurpose
Backend APIRailwayNode.js serverREST API + WebSocket
Flutter WebVercelStatic + SSRMain app web version
Group StoreVercelNext.js SSRMarketplace at bookwish.io
Store WebsitesVercelNext.js SSRPer-store websites
DatabaseSupabasePostgreSQLPrimary data store
Redis CacheRailwayRedisCaching + job queues
Object StorageSupabase StorageS3-compatibleBook covers, avatars, uploads

Railway (Backend API)

Configuration

Service Type: Node.js/TypeScript Environment: Production + Staging Region: US West (Oregon)

Deployment:

  • Automatic deploys from main branch
  • 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 = true and stripe_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:

  1. User discovers book on group store
  2. Sees which stores have it (sorted by price, with distance when available)
  3. Adds to cart (can be multi-vendor)
  4. Checks out via Stripe
  5. Each store receives order notification
  6. 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

DomainServicePurpose
bookwish.iomarketplace/Group store, discovery
app.bookwish.ioapp/ (Flutter Web)Main app
*.bookwish.shopstores/Individual store sites
share.bookwish.ioweb/Public wishlist sharing
docs.bookwish.iodocs-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:

SourceProcessorCommissionHome Store
App (with home store)Square0%Yes
App (no home store)Stripe Connect15%No
Group StoreStripe Connect15%No
Individual Store SiteSquare0%N/A
BookWish DirectStripeMarginN/A

Store Onboarding Order:

  1. Create account
  2. Stripe Connect onboarding (required for group store participation)
  3. 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_ledger table
  • 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

  1. Development:

    npm run dev
  2. Staging:

    git push origin staging
    # Railway auto-deploys to staging environment
  3. Production:

    git push origin main
    # Railway auto-deploys to production
  4. 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 diff to 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)

ServiceTierMonthly Cost (est.)
Railway (API)Hobby$5 - $20
Railway (Redis)Included$0
Supabase (DB + Storage)Pro$25
Vercel (Web + Stores)Pro$20
StripeTransaction fees2.9% + $0.30 per transaction
SendGridEssentials$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

  1. Database:

    • Automated daily backups (Supabase)
    • Point-in-time recovery (7 days)
    • Weekly manual exports to S3
  2. Storage:

    • Versioned buckets (Supabase)
    • Cross-region replication (future)
  3. Code:

    • Git version control (GitHub)
    • Tagged releases
    • Deployment history (Railway/Vercel)

Recovery Procedures

  1. Database Failure:

    • Restore from latest Supabase backup
    • Verify data integrity
    • Update connection strings if needed
    • Test critical flows
  2. API Downtime:

    • Check Railway dashboard for errors
    • Rollback to last known good deployment
    • Scale up resources if needed
    • Investigate root cause
  3. 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:

  1. Simplicity - Managed services reduce operational burden
  2. Scalability - Auto-scaling and serverless where possible
  3. Cost Efficiency - Pay for what you use
  4. Reliability - Automatic backups and disaster recovery
  5. Developer Experience - Automatic deployments, preview environments