Environment Variables
This starter uses environment variables for all configuration. Copy .env.example to .env and fill in the values relevant to your setup. For local overrides, create .env.local (loaded with higher priority).
cp .env.example .env
Variable Reference
Database
| Variable | Required | Default | Description |
|---|---|---|---|
DATABASE_URL | Yes | -- | PostgreSQL connection string, e.g. postgresql://user:pass@localhost:5432/myapp |
Authentication
| Variable | Required | Default | Description |
|---|---|---|---|
JWT_SECRET | Yes | -- | Signing key for access tokens. Minimum 32 characters. |
JWT_REFRESH_SECRET | Yes | -- | Signing key for refresh tokens. Minimum 32 characters. Use a different value than JWT_SECRET. |
GOOGLE_CLIENT_ID | No | -- | Google OAuth 2.0 client ID. Required only if you enable Google login. |
GOOGLE_CLIENT_SECRET | No | -- | Google OAuth 2.0 client secret. |
Billing (Stripe)
STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET are validated by the Zod config schema at startup. The API will not start without them. Use Stripe test keys for local development.
| Variable | Required | Default | Description |
|---|---|---|---|
STRIPE_SECRET_KEY | Yes | -- | Stripe secret API key (starts with sk_). Use sk_test_... locally. |
STRIPE_WEBHOOK_SECRET | Yes | -- | Stripe webhook signing secret (starts with whsec_). Use stripe listen to get a test value. |
STRIPE_PUBLISHABLE_KEY | No | -- | Stripe publishable key (starts with pk_). Returned by GET /billing/config. |
STRIPE_PRICE | No | -- | Stripe Price ID for the subscription plan. Returned by GET /billing/config. |
Email
| Variable | Required | Default | Description |
|---|---|---|---|
RESEND_API_KEY | No | -- | API key for Resend transactional email. When absent, emails are logged to the console. |
EMAIL_FROM | No | hello@example.com | Sender address for outgoing emails. |
Storage (AWS S3)
| Variable | Required | Default | Description |
|---|---|---|---|
AWS_S3_BUCKET | No | -- | S3 bucket name for file uploads. |
AWS_REGION | No | -- | AWS region for the S3 bucket. |
AWS_ACCESS_KEY_ID | No | -- | AWS IAM access key ID. |
AWS_SECRET_ACCESS_KEY | No | -- | AWS IAM secret access key. |
Queue
| Variable | Required | Default | Description |
|---|---|---|---|
REDIS_URL | No | -- | Redis connection string for BullMQ background jobs, e.g. redis://localhost:6379. |
Application
| Variable | Required | Default | Description |
|---|---|---|---|
APP_URL | No | http://localhost:5173 | Frontend URL. Used for CORS configuration and email links. |
API_URL | No | http://localhost:4000 | API base URL. Used in generated links and documentation. |
PORT | No | 4000 | Port the API server listens on. |
Minimal local setup
For local development, you need five variables to start the API:
DATABASE_URL=postgresql://user:pass@localhost:5432/myapp
JWT_SECRET=your-random-string-at-least-32-chars
JWT_REFRESH_SECRET=another-random-string-at-least-32-chars
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET are enforced by the config schema — the API will not start without them. Use Stripe test keys and run stripe listen --forward-to localhost:4000/billing/webhook to get a local signing secret.
Everything else (RESEND_API_KEY, AWS S3, Redis, Google OAuth) is optional and the application degrades gracefully when absent. Emails are logged to stdout when RESEND_API_KEY is not set.
Generating secrets
Use openssl to generate cryptographically secure random strings:
openssl rand -base64 32
Run this twice -- once for JWT_SECRET and once for JWT_REFRESH_SECRET.