Development workflow¶
This starter kit gives each local service a fixed localhost port and keeps interactive tools in separate terminals. Return to this page while you work. The normal loop is simple: run the app, make one focused change, check it, then run the smallest test that can prove it works.
Replace the starter landing page¶
This starter kit includes a landing page that introduces Launchpad and links to
its documentation. Remove that starter content from
apps/client/src/routes/index.tsx and replace it with the application's own home
page. Update the page title in apps/client/index.html and replace the starter
logo in apps/client/public/ when choosing the application's branding.
The docs/ directory contains the starter's documentation site. Applications
created from the template can remove it and replace the root README.md with
their own setup instructions.
Environment variables¶
The root .env file holds settings that change between your computer, tests, and a
production host. Copy the safe template once:
Generate a value for BETTER_AUTH_SECRET and paste it into .env:
MONGODB_URI identifies the application database. TEST_MONGODB_URI is deliberately
separate because test runs create and remove temporary databases. APP_URL and
BETTER_AUTH_URL must be the same public origin in this starter. Do not put a secret,
database URL, or API key in a variable beginning with VITE_: Vite exposes those to
browser code.
Keep .env private
Commit .env.example, never .env. Production hosts should inject the same values
through their secret settings instead of copying a development file.
File storage¶
This starter kit uses RustFS, an Apache 2.0 S3-compatible server, for local file uploads. Keep local development on RustFS; use separate R2/B2 buckets in production. The existing S3 client already uses the path-style addressing RustFS needs.
Docker (recommended)¶
After copying .env.example to the root .env, run from the repository root:
This starts MongoDB and RustFS from compose.db.yaml. RustFS is pinned to 1.0.0;
its API and console bind to host loopback ports 9000 and 9001. Compose reads
STORAGE_ACCESS_KEY_ID and STORAGE_SECRET_ACCESS_KEY from the root .env using
variable substitution, just like the deployment Compose file. Existing checkouts
must copy the five local storage settings from .env.example into .env first.
Use only local credentials here.
If MongoDB already runs elsewhere, start only storage:
The API runs on the host, so keep STORAGE_ENDPOINT=http://localhost:9000,
STORAGE_REGION=us-east-1, and STORAGE_BUCKET=mern-local. A Docker service name
would not resolve from the host or the browser receiving a presigned URL.
pnpm db:down stops both services but preserves their named volumes. Adding
--volumes deletes the local database and object storage data.
Create the local bucket once¶
RustFS does not create the application's bucket at startup. After either Docker
or Windows setup, open http://localhost:9001, sign in with the configured access
and secret keys, and use Create Bucket on the Buckets page to create
mern-local (or the exact STORAGE_BUCKET value). Keep the bucket private.
The RustFS 1.0.0 console supports this operation; see the
official bucket creation guide.
The bucket and objects survive restarts in rustfs-data or the Windows data directory.
For browser uploads, set the bucket's CORS rule to allow http://localhost:3000,
methods PUT and GET, and header Content-Type. Alternatively, RustFS supports a server-wide
RUSTFS_CORS_ALLOWED_ORIGINS=http://localhost:3000 setting; see the
RustFS CORS guide.
Use the file upload verification sequence
to upload, confirm, and download a file. A healthy container alone does not prove
that the bucket exists or that object access works.
Windows without Docker or WSL2¶
Use one of these alternatives on Windows x86-64, with ports 9000 and 9001 free.
Both run a single-node development process, not a Windows service. Create a dedicated
empty data directory first, for example C:\rustfs\data.
Download the Windows x86-64 installer from the official Launcher releases, install it, and open RustFS Launcher. Choose an installer asset, not the source archive.
Set Data Path to C:\rustfs\data, Host to 127.0.0.1, and API Port
to 9000. Enable Console Endpoint (disabled by default in Launcher) and set
Console Port to 9001. Replace the supplied access and secret keys with
your own non-default local values. Select Launch RustFS and wait for online status.
Use Stop RustFS to stop it. Closing the window leaves it in the tray; choose Quit from the tray to stop and exit.
Download the Windows x86-64 ZIP asset from the
RustFS 1.0.0 release.
Choose rustfs-windows-x86_64-v1.0.0.zip, then extract it:
New-Item -ItemType Directory -Force -Path C:\rustfs\bin, C:\rustfs\data
Expand-Archive -Path "$HOME\Downloads\rustfs-windows-x86_64-v1.0.0.zip" -DestinationPath C:\rustfs\bin -Force
Set-Location C:\rustfs\bin
.\rustfs.exe --help
If the archive has a nested folder, change to the directory containing
rustfs.exe. Replace the credential placeholders with your own local values:
$env:RUSTFS_ACCESS_KEY = "<your-local-access-key>"
$env:RUSTFS_SECRET_KEY = "<your-local-secret-key>"
.\rustfs.exe server `
--address "127.0.0.1:9000" `
--console-enable true `
--console-address "127.0.0.1:9001" `
"C:\rustfs\data"
Keep this PowerShell session open. Press Ctrl+C to stop RustFS.
For either Windows option, set the app's root .env to
STORAGE_ENDPOINT=http://localhost:9000, STORAGE_REGION=us-east-1, and
STORAGE_BUCKET=mern-local. Set STORAGE_ACCESS_KEY_ID and
STORAGE_SECRET_ACCESS_KEY to the same credentials selected above. Restart the API,
then complete Create the local bucket once above. The commands and Launcher
fields follow the official Windows guide;
check that guide when using a newer release.
Production storage and upload policy¶
Cloudflare R2 and Backblaze B2 remain the production providers. Create a private bucket and a bucket-scoped credential that can read, write, and delete objects. Inject these five settings through the production host's secret settings:
| Variable | Value |
|---|---|
STORAGE_ENDPOINT |
The provider's S3 HTTP(S) origin, without a bucket path, query, or embedded credentials |
STORAGE_REGION |
auto for R2; the bucket's region for B2 |
STORAGE_BUCKET |
The private bucket name |
STORAGE_ACCESS_KEY_ID |
The storage access key ID; B2 uses an application key ID |
STORAGE_SECRET_ACCESS_KEY |
The corresponding secret access key; B2 uses the application key |
For R2, the usual endpoint is https://<account-id>.r2.cloudflarestorage.com.
For B2, use https://s3.<region>.backblazeb2.com. Replace the placeholders with the
values shown by the provider. Use HTTPS for hosted buckets.
When all five settings are absent, the API starts but authenticated upload requests
return 503. If any is supplied, all five must be nonempty or startup fails.
Restart the API after changing configuration. Never prefix storage credentials with
VITE_ or send them to clients.
The following policy settings already have defaults:
| Variable | Default | Accepted values |
|---|---|---|
STORAGE_MAX_UPLOAD_BYTES |
26214400 (25 MiB) |
Whole bytes from 1 to 5000000000 |
STORAGE_ALLOWED_MIME_TYPES |
image/jpeg,image/png,image/webp,application/pdf |
Comma-separated MIME types with known extensions; no wildcards or empty entries |
The allowlist is trimmed and lowercased during configuration parsing. Requests must use an exact resulting MIME type.
For browser uploads, configure the bucket's CORS rules to allow the application
origin, PUT and GET, and the Content-Type request header. CORS tells the browser
which cross-origin requests it can make; it does not make the bucket public. Use the
provider's R2 CORS guide or
B2 CORS guide
for the provider-specific format.
Follow File uploads to verify the configuration with a direct PUT, confirmation, and private download. Receiving an upload URL alone does not verify bucket access.
Upload test database¶
The upload integration tests read the test database address from the process
environment, not from .env. Start a dedicated MongoDB server first, then run one
of these commands from the repository root in the terminal that will run tests.
These examples use the local MongoDB server; substitute a dedicated test server
address when needed.
The tests supply dummy storage settings and mock S3 network calls. Real bucket credentials are not needed. See File uploads: Verify and troubleshoot for the test commands and Testing for MongoDB setup.
Start the workspace¶
Run these commands in separate terminals:
pnpm dev: API onhttp://localhost:3001, MailDev inbox onhttp://localhost:3003, and SMTP onlocalhost:3025pnpm dev:ui: interactive Vite client onhttp://localhost:3000pnpm dev:mail: optional React Email preview onhttp://localhost:3002
Each command owns its terminal. Press Ctrl+C there to stop its processes; Vite's keyboard commands work in the pnpm dev:ui terminal.
Type check while you work¶
This generates TanStack Router route types and runs each package's typecheck script.
Format and lint¶
pnpm check reports lint and formatting problems. pnpm format writes
formatting changes and safe lint fixes. See formatting,
linting, and the official Oxfmt
and Oxlint documentation.
Run the tests¶
pnpm test runs the Vitest unit suite followed by the API integration suite.
pnpm test:e2e runs the Playwright browser suite. Integration and E2E tests use
separate randomly named databases when persistence is required. See
Commands and Testing for the current test
commands and database setup.
Build and run¶
pnpm build builds the client and server. pnpm start runs the compiled
server; use the Docker commands in Deployment for the complete
production-style stack.
Next step¶
Continue to Project structure to locate the code each command affects.
References¶
- RustFS Docker installation
- RustFS Windows installation
- Vite environment variables
- Cloudflare R2 S3 client configuration
- Cloudflare R2 CORS configuration
- Backblaze B2 S3-compatible API
- Backblaze B2 CORS rules
Related starter documentation: