Environment variables
What each setting in the API's and the frontends' .env files does, and which ones you need.
For the Full Stack package
Where the settings live
| File | Read by | Holds secrets |
|---|---|---|
back-end/.env | The API, with yarn dev and inside Docker Compose | Yes. Never commit it. |
storefront/.env | The storefront, at build time | No. Every value is public. |
admin-dashboard/.env | The admin dashboard, at build time | No. Every value is public. |
.env beside docker-compose.yml | Docker Compose, for the full stack | No |
Create each file from the .env.example next to it, which documents every variable: cp .env.example .env. The examples work as they are for a local run.
Where the settings live
One file, storefront/.env, read when the storefront is built. Every value in it is public, so it never holds a secret. Leave it out to run on the sample store; create it from .env.example when you connect an API: cp .env.example .env.
Where the settings live
One file, admin-dashboard/.env, read when the dashboard is built. Every value in it is public, so it never holds a secret. Leave it out to run on the sample store; create it from .env.example when you connect an API: cp .env.example .env.
Where the settings live
One file, back-end/.env, read by the API with yarn dev, and by a container when you pass it with --env-file .env. It holds secrets: never commit it. Create it from .env.example, which documents every variable and works as it is for a local run: cp .env.example .env.
API essentials
The API checks JWT_SECRET and CORS_ORIGIN before it starts. If one is missing or unusable, it stops with one line saying what to fix.
| Variable | What it does |
|---|---|
NODE_ENV | development locally, which creates and updates the tables on start. production on a live server, which never touches the tables. |
PORT | The API's port, 8000. Both frontends point at it. |
DB_TYPE | sqlite (the default) or mysql. |
SQLITE_DATABASE | Path of the SQLite file, ./database.sqlite by default. |
JWT_SECRET | Signs every sign-in. Required. The example value is accepted in development only. |
JWT_EXPIRATION | How long a sign-in lasts, 7d by default. |
CORS_ORIGIN | The storefront's and the admin's addresses, comma separated. Unset, it falls back to the two local ports; required in production. |
FRONTEND_URL | The admin dashboard's address, which its live notifications connect from. Required in production for those notifications. |
TRUST_PROXY | Optional. How far the API believes X-Forwarded-For when counting sign-in attempts per visitor. Unset reads it only from a proxy on a private address; false never; a number trusts exactly that many proxies. |
RESEED_REVIEWS | Optional. 1 makes yarn seed rewrite the seeded reviews. |
Generate your own JWT_SECRET with:
node -e "console.log(require('crypto').randomBytes(48).toString('hex'))"Any credential below still holding its exact .env.example value counts as not set, so the feature it belongs to reports itself off instead of failing on its first call.
Use MySQL instead of SQLite
Create an empty database, then set the driver and the connection in back-end/.env:
DB_TYPE=mysql
DB_HOST=your-mysql-host
DB_PORT=3306
DB_USERNAME=your-mysql-username
DB_PASSWORD=your-mysql-password
DB_DATABASE=your-database-nameThen run yarn seed for the demo store, or yarn db:sync for the tables with no data. The running API only creates and updates the tables itself when NODE_ENV=development, so with NODE_ENV=production one of those commands runs before the first start (yarn db:sync:prod or yarn seed:prod after yarn build).
Storefront and admin dashboard
Every NEXT_PUBLIC_* value is compiled into the JavaScript the browser loads, and anyone who opens the page can read it. Never put a secret in these files, and rebuild after changing one.
| Variable | App | What it does |
|---|---|---|
NEXT_PUBLIC_API_BASE_URL | Both | The API's address including /api, for example http://localhost:8000/api. Setting it is what switches the sample store off; empty or missing, the app runs on its sample store. |
NEXT_PUBLIC_MEDIA_HOSTNAME | Both | Your media bucket's public host, the host of R2_PUBLIC_URL, without https:// or a trailing slash. Leave it empty until you have a bucket. |
NEXT_PUBLIC_SITE_URL | Storefront | The storefront's public address, used for canonical and hreflang links, sharing cards, structured data, robots.txt and sitemap.xml. Set it to your real domain before going live. |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY | Storefront | A real pk_test_… or pk_live_… key. Empty, or anything that is not a real key, turns the card option at checkout off, and shoppers are asked to choose cash on delivery. |
NEXT_PUBLIC_DEMO_CHECKOUT | Storefront | true prefills the checkout with a generated buyer, for demos. Leave it false for a real shop. |
API_INTERNAL_URL | Both | Optional, server only. Where the app's own server reaches the API when that differs from the browser's address, as inside Docker Compose. Leave it empty elsewhere. |
NEXT_PUBLIC_WEBSOCKET_BASE_URL | Admin | The API's address without /api, for live notifications and permission updates. Optional: when empty it is taken from NEXT_PUBLIC_API_BASE_URL. |
NEXT_PUBLIC_STOREFRONT_URL | Admin | Where the "Go to storefront" link in the dashboard's navbar goes. |
BUILD_STANDALONE | Both | true makes yarn build emit a self-contained server, which the Dockerfiles set. Leave it unset otherwise. |
The storefront's marketing tags are NEXT_PUBLIC_* values too.
Docker Compose options
Nothing has to be set for a local run. To change something, put it in a .env file next to docker-compose.yml and run docker compose up --build again: the frontends compile these values in.
| Variable | What it does |
|---|---|
SITE_PORT, ADMIN_PORT, API_PORT | The ports on your computer: 3030, 3031 and 8000 by default. Set one when another program already uses that port, such as ADMIN_PORT=3041. The addresses below, CORS_ORIGIN and FRONTEND_URL follow them. |
SITE_URL, ADMIN_URL, API_URL | Where the browser reaches each app. Set all three when serving the stack on your own domains; the API's CORS_ORIGIN and FRONTEND_URL are built from them. |
MEDIA_HOSTNAME | Your bucket's public host, with the R2_* variables in back-end/.env. |
SEED_DEMO_DATA | false starts with empty tables instead of the demo store. |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY | The storefront's card form. |
NEXT_PUBLIC_GTM_ID, NEXT_PUBLIC_GA4_MEASUREMENT_ID, NEXT_PUBLIC_META_PIXEL_ID, NEXT_PUBLIC_TIKTOK_PIXEL_ID, NEXT_PUBLIC_SNAPCHAT_PIXEL_ID, NEXT_PUBLIC_PINTEREST_TAG_ID, NEXT_PUBLIC_ANALYTICS_CURRENCY | The storefront's marketing tags, all optional. |
The API container also reads back-end/.env when it exists, so payments, media and AI keys are set in one place for both yarn dev and Docker. The compose file wins for the few values that differ inside a container: the database path, the port, NODE_ENV=production, CORS_ORIGIN and FRONTEND_URL. The data lives in the ecommerce-data volume.
Media storage
Product photos, category images, avatars, chat attachments, the AI studio's results and the fitting room's photos are uploaded to a Cloudflare R2 bucket, or any S3-compatible one. Set the five variables in back-end/.env and give both frontends the bucket's public host through NEXT_PUBLIC_MEDIA_HOSTNAME (with Docker Compose, MEDIA_HOSTNAME).
R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY=
R2_ENDPOINT=https://your-account-id.r2.cloudflarestorage.com
R2_BUCKET_NAME=
R2_PUBLIC_URL=https://your-public-url.r2.devWithout a bucket, yarn seed stores every product and category image as a /mock-media/… path, and both frontends ship those 35 files in public/mock-media/, so the catalogue renders with nothing uploaded. Only uploading new images stops working: it answers 503 until the bucket is set.
With a bucket, the seed uploads its catalogue images there. Keep public/mock-media/ in both frontends for as long as any product still points at it.
Card payments
Card payments run on Stripe: STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET in back-end/.env, and NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY for the storefront. Without them no card payment is taken, and cash on delivery works as usual.
STRIPE_SECRET_KEY=sk_test_your-stripe-secret-key
STRIPE_WEBHOOK_SECRET=whsec_your-webhook-secretThe template ships no mail transport, so no message is ever emailed. Outside production, a customer's password-reset token is written to the API's log instead; with NODE_ENV=production it goes nowhere. Connect your own mail provider before going live: the hook is forgotPassword in back-end/src/modules/customer-auth/customer-auth.controller.ts.
AI features
Included with your purchase. Sign in to read, or open it in your download.
Turning on the AI features: the provider keys, the models each feature uses, and what each one costs.
The sample store in the frontends
Both frontends run without an API. Started or built without NEXT_PUBLIC_API_BASE_URL, each answers every request from a sample store in the browser and shows a "Sample data" notice. Setting the address is what switches it off.
- Any email and password sign in as the sample shopper on the storefront. In the dashboard, any valid email with a password of at least 6 characters signs in as the Super Admin.
- Writes are real and are kept in the browser's storage, so they survive a reload. To start over, run
localStorage.removeItem("mock_db_v1"); location.reload();in the browser console. - Checkout on the storefront completes without calling Stripe.
- In the dashboard, the AI assistant and the AI studio stay unavailable, because both need the API's keys.
- Once your API is live,
yarn remove:mockin either app deletes the sample store and its notice. It leavespublic/mock-media/in place, because a backend seeded without a bucket points its images there.
How the sample store works
Included with your purchase. Sign in to read, or open it in your download.
How requests are routed to the sample store, and how to change or extend it.
Demo mode
Included with your purchase. Sign in to read, or open it in your download.
Running a public demo: the demo switch, per-visitor accounts and what visitors can change.
Going live
Before you deploy anywhere public:
- Set
NODE_ENV=productionand a long randomJWT_SECRETof your own inback-end/.env. - Set
CORS_ORIGINto your storefront's and admin's addresses, comma separated, andFRONTEND_URLto the admin's address. - On an empty database, run
yarn build, thenyarn db:sync:prodonce (oryarn seed:prodfor the demo store), thenyarn start:prod. - Point your host's health check at
/api/health. - Build each frontend with
NEXT_PUBLIC_API_BASE_URLat your deployed API and the storefront'sNEXT_PUBLIC_SITE_URLat its own address. - Change the Super Admin's password, or start from empty tables.
Not for a shop with real orders
yarn railway:setup builds, fetches the background-removal model, drops every table and seeds, every time it runs. It suits a demo deployment, never the build command of a live shop.