A complete and production-ready starter kit for building a subscription-based SaaS platform.
bash1git clone https://github.com/tomangale/indiflow.git 2cd indiflow 3
bash1pnpm install 2
bash1cp .env.example .env 2
Fill in the .env file with your own values:
bash1VITE_BASE_URL=http://localhost:3000 2 3DATABASE_URL=postgresql://username:password@localhost:5432/database 4# You can also use Docker Compose to set up a local PostgreSQL database: 5# docker-compose up -d 6 7# Better Auth setup 8BETTER_AUTH_SECRET=generate_a_random_string_here 9 10# OAuth2 Providers (optional) 11GITHUB_CLIENT_ID= 12GITHUB_CLIENT_SECRET= 13GOOGLE_CLIENT_ID= 14GOOGLE_CLIENT_SECRET= 15 16# Stripe configuration (for subscription features) 17STRIPE_WEBHOOK_SECRET= 18STRIPE_SECRET_KEY= 19VITE_STRIPE_PUBLISHABLE_KEY= 20 21# Email configuration 22RESEND_API_KEY= 23FROM_EMAIL=Your SaaS <onboarding@yourdomain.com> 24 25# S3 Compatible Storage for Files/Media 26S3_ACCESS_KEY= 27S3_SECRET_KEY= 28S3_REGION=us-east-1 29S3_ENDPOINT=https://s3.amazonaws.com 30S3_BUCKET_NAME=your-bucket-name 31S3_PUBLIC_URL=https://your-bucket-name.s3.amazonaws.com 32 33# Cloudflare R2 34# S3_ACCESS_KEY=your_cloudflare_access_key_id 35# S3_SECRET_KEY=your_cloudflare_secret_access_key 36# S3_REGION=auto 37# S3_ENDPOINT=https://<ACCOUNT_ID>.r2.cloudflarestorage.com 38# S3_BUCKET_NAME=your_r2_bucket_name 39# S3_PUBLIC_URL=https://<CUSTOM_DOMAIN>.com 40
This starter uses a type-safe approach to environment variables with Zod validation:
Client-Side Environment Variables
All client-side environment variables must start with VITE_ to be accessible in the browser.
typescript1// Import in client-side code 2import { clientEnv } from "../lib/client/env.client"; 3 4// Access variables 5const stripeKey = clientEnv.VITE_STRIPE_PUBLISHABLE_KEY; 6const baseUrl = clientEnv.VITE_BASE_URL; 7
Server-Side Environment Variables
Server-side variables are for sensitive data that should never be exposed to the client.
typescript1// Import in server-side code only 2import { serverEnv } from "../lib/server/env.server"; 3 4// Access variables 5const stripeSecret = serverEnv.STRIPE_SECRET_KEY; 6const databaseUrl = serverEnv.DATABASE_URL; 7
The environment system ensures:
bash1pnpm dev 2
Visit http://localhost:3000 to see your application.
This starter uses Drizzle ORM with PostgreSQL. You can set up a local PostgreSQL instance or use a cloud provider like Neon or Supabase.
To push schema changes to your database:
bash1pnpm db:push 2
bash1 - customer.subscription.created 2 - customer.subscription.updated 3 - customer.subscription.deleted 4 - price.updated 5 - price.created 6 - price.deleted 7 - product.updated 8 - product.created 9 - product.deleted
For local development, you can use the Stripe CLI to forward webhooks to your local server:
bash1stripe listen --forward-to localhost:3000/api/webhooks/stripe
This starter uses React Email with Resend for sending transactional emails.
bash1pnpm email:dev 2
This will start a local server at http://localhost:3030 where you can preview email templates.
This starter uses Better Auth for authentication. It supports email/password authentication and OAuth providers.
To set up OAuth providers:
To generate new authentication schema:
bash1pnpm auth:generate
This starter can be deployed to any platform that supports Node.js. Here are some recommended options:
Start development server
bash1pnpm dev 2
Build for production
bash1pnpm build 2
Start production server
bash1pnpm start
Lint code
bash1pnpm lint
Format code
bash1pnpm format
Generate UI components
bash1pnpm ui
Manage database schema
bash1pnpm db:push
Generate auth schema
bash1pnpm auth:generate 2
Start email development server
bash1pnpm email:dev
This starter includes support for file uploads to S3-compatible storage services, useful for user avatars and other media:
For non-AWS S3 services, you may need to adjust:
The system uses pre-signed URLs for secure direct browser-to-S3 uploads, removing the load from your server.
To use the media service with Cloudflare R2, you'll need to configure your credentials in the following way:
Get Cloudflare R2 Credentials:
Environment Variables Setup:
bash1S3_ACCESS_KEY=your_cloudflare_access_key_id 2S3_SECRET_KEY=your_cloudflare_secret_access_key 3S3_REGION=auto 4S3_ENDPOINT=https://<ACCOUNT_ID>.r2.cloudflarestorage.com 5S3_BUCKET_NAME=your_r2_bucket_name 6S3_PUBLIC_URL=https://<CUSTOM_DOMAIN>.com
Where:
CORS Configuration:
json1[ 2 { 3 "AllowedOrigins": [ 4 "https://yourdomain.com", 5 "http://localhost:3000" 6 ], 7 "AllowedMethods": [ 8 "GET", 9 "PUT", 10 "POST" 11 ], 12 "AllowedHeaders": [ 13 "*" 14 ], 15 "ExposeHeaders": [], 16 "MaxAgeSeconds": 3000 17 } 18]
Public Access (Optional): If you want files to be publicly accessible, you'll need to:
The media service will work with R2 without code changes because Cloudflare R2 is designed to be S3-compatible with the AWS SDK. The key difference is just the endpoint URL format.