In production you’ll typically run:
- Frontend:
https://app.your-domain.tld - API:
https://api.your-domain.tld
Locally, the simplest and most common approach is to run frontend and backend on different ports.
- API:
http://localhost:3000 - Frontend:
http://localhost:5173(or whatever your frontend dev server uses)
Set API env:
CORS_ORIGINS="http://localhost:5173"
Then in the frontend configure the API base URL as:
http://localhost:3000(and call versioned endpoints like/v1/...)
If you want URLs closer to production:
- Frontend:
http://app.localhost:5173 - API:
http://api.localhost:3000
On some systems *.localhost resolves automatically. If not, add to your hosts file:
127.0.0.1 app.localhost127.0.0.1 api.localhost
Then set:
CORS_ORIGINS="http://app.localhost:5173"
On Windows the hosts file is:
C:\Windows\System32\drivers\etc\hosts
You’ll need admin privileges to edit it.
By default, yes — you still run two processes, so they listen on different ports (e.g. 5173 and 3000).
If you want no ports (closer to prod), run a local reverse proxy (optional), for example Caddy:
app.localhost {
reverse_proxy 127.0.0.1:5173
}
api.localhost {
reverse_proxy 127.0.0.1:3000
}
Then you can use:
- Frontend:
http://app.localhost - API:
http://api.localhost
This API loads env files based on NODE_ENV:
- If
NODE_ENV=devit loads (in order):.env.dev,.env - If
NODE_ENV=prodit loads (in order):.env.prod,.env
Set:
S3_POSTERS_PREFIX=gigs-devin.env.devS3_POSTERS_PREFIX=gigsin.env.prod
This affects upload and public URL building for gig poster images.
The bucket is expected to be public. The API:
- Uploads posters into the bucket under the
S3_POSTERS_PREFIXprefix. - Stores only the object key path in DB as
Gig.poster.bucketPath(e.g."/gigs/<uuid>-file.jpg"). - Returns a direct public URL to the object for clients (no proxy routes, no presigned URLs).
S3_BUCKETS3_ACCESS_KEY_IDS3_SECRET_ACCESS_KEYS3_REGION(for R2 useauto)S3_ENDPOINT(for R2:https://<accountid>.r2.cloudflarestorage.com)S3_FORCE_PATH_STYLE(for R2 usuallytrue)S3_PUBLIC_BASE_URL(public bucket base URL / CDN / custom domain)
If your frontend is on another domain (e.g. https://app.your-domain.tld) and the API is on a subdomain (e.g. https://api.your-domain.tld), set:
CORS_ORIGINS="https://your-frontend.tld,https://another.tld"
Or:
CORS_ORIGINS="*"(no credentials)
- Frontend:
https://app.your-domain.tld - API:
https://api.your-domain.tld - Env:
CORS_ORIGINS="https://app.your-domain.tld"
If the browser fetches objects directly from the public bucket domain (or uploads directly to the bucket), you must allow your frontend origin in the bucket CORS config.
Example (adjust origins/methods as needed):
AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... aws s3api put-bucket-cors \
--bucket "$S3_BUCKET" \
--endpoint-url "$S3_ENDPOINT" \
--cors-configuration '{
"CORSRules": [
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET","HEAD","PUT","POST"],
"AllowedOrigins": ["https://your-frontend.tld"],
"MaxAgeSeconds": 3000
}
]
}'