Backend + Owner/Admin dashboard + Vendor dashboard + Marketplace Storefront + PostgreSQL + Redis + Railway Bucket
Any questinos about MercurJS on Railway? Ask here: https://station.railway.com/templates/mercurjs-10ceb1ef
Before starting, ensure you have the following installed:
- Node.js 20+ (Recommended: v22.13.1)
- PostgreSQL 14+ (Running locally on port 5432)
- pnpm (Package manager)
mercurjs-for-railway-boilerplate/
βββ backend/ # Mercur backend (MedusaJS)
βββ admin-panel/ # Admin dashboard (React/Vite)
βββ vendor-panel/ # Vendor/seller dashboard (React/Vite)
βββ storefront/ # Customer-facing storefront (Next.js)
All dependencies are already installed, but if you need to reinstall:
# Backend
cd mercurjs-for-railway-boilerplate/backend
pnpm install
# Admin Panel
cd ../admin-panel
pnpm install
# Vendor Panel
cd ../vendor-panel
pnpm install
# Storefront
cd ../storefront
pnpm installMake sure PostgreSQL is running, then create the database:
# Connect to PostgreSQL
psql -U postgres
# Create database
CREATE DATABASE mercurjs;
# Exit
\qEnsure Redis is running on port 6379. You can verify with:
redis-cli ping
# Should return: PONGcd mercurjs-for-railway-boilerplate/backend
npx medusa db:migratecd mercurjs-for-railway-boilerplate/backend
pnpm seedcd mercurjs-for-railway-boilerplate/backend
npx medusa user -e admin@test.com -p supersecretYou need to run all four services in separate terminal windows:
cd mercurjs-for-railway-boilerplate/backend
pnpm devRuns on: http://localhost:9000
cd mercurjs-for-railway-boilerplate/admin-panel
pnpm devRuns on: http://localhost:5173
cd mercurjs-for-railway-boilerplate/vendor-panel
pnpm devRuns on: http://localhost:7001
cd mercurjs-for-railway-boilerplate/storefront
pnpm devRuns on: http://localhost:3000
| Service | URL | Login Credentials |
|---|---|---|
| Backend API | http://localhost:9000 | N/A |
| Admin Panel | http://localhost:5173 | admin@test.com / supersecret |
| Vendor Panel | http://localhost:7001 | vendor@test.com / supersecret |
| Storefront | http://localhost:3000 | N/A |
All environment files have been created:
- Backend:
mercurjs-for-railway-boilerplate/backend/.env - Storefront:
mercurjs-for-railway-boilerplate/storefront/.env.local - Admin Panel:
mercurjs-for-railway-boilerplate/admin-panel/.env - Vendor Panel:
mercurjs-for-railway-boilerplate/vendor-panel/.env
DATABASE_URL=postgres://postgres:postgres@localhost:5432/mercurjs
REDIS_URL=redis://localhost:6379
JWT_SECRET=supersecret
COOKIE_SECRET=supersecretStripe is optional. Without both STRIPE_SECRET_API_KEY and
STRIPE_WEBHOOK_SECRET, the template uses Medusa's built-in system payment
provider so the demo can launch without a Stripe account. Providing both
enables Stripe checkout. STRIPE_CONNECTED_ACCOUNTS_WEBHOOK_SECRET is the
separate Stripe Connect webhook signing secret used for seller payout accounts.
MEDUSA_BACKEND_URL=http://localhost:9000
NEXT_PUBLIC_BASE_URL=http://localhost:3000VITE_MEDUSA_BACKEND_URL=http://localhost:9000
VITE_MEDUSA_STOREFRONT_URL=http://localhost:3000NEXT_PUBLIC_MEDUSA_BACKEND_URL=http://localhost:9000
VITE_MEDUSA_STOREFRONT_URL=http://localhost:3000New Railway installations use a private Railway Bucket through Medusa's stock
S3 file provider. Browsers retrieve media through a small read-only Railway
Function named Bucket-proxy.
Wire the Backend service exactly as follows (Railway references are case-sensitive):
S3_ACCESS_KEY_ID=${{Bucket.ACCESS_KEY_ID}}
S3_SECRET_ACCESS_KEY=${{Bucket.SECRET_ACCESS_KEY}}
S3_BUCKET=${{Bucket.BUCKET}}
S3_ENDPOINT=${{Bucket.ENDPOINT}}
S3_REGION=${{Bucket.REGION}}
S3_FILE_URL=https://${{Bucket-proxy.RAILWAY_PUBLIC_DOMAIN}}
S3_ACL=falseCreate Bucket-proxy as a Railway Function, give it the same five Bucket
credential variables, and paste this source:
import { S3Client } from "bun";
const CACHE_CONTROL =
Bun.env.CACHE_CONTROL || "public, max-age=31536000, immutable";
const s3 = new S3Client({
virtualHostedStyle: Bun.env.S3_VIRTUAL_HOSTED_STYLE !== "false",
});
export default {
async fetch(req: Request): Promise<Response> {
const url = new URL(req.url);
if (url.pathname === "/health") {
return new Response('{"status":"ok"}', {
headers: { "Content-Type": "application/json" },
});
}
if (req.method !== "GET" && req.method !== "HEAD") {
return new Response("Method Not Allowed", {
status: 405,
headers: { Allow: "GET, HEAD" },
});
}
let key: string;
try {
key = decodeURIComponent(url.pathname.slice(1));
} catch {
return new Response("Not Found", { status: 404 });
}
if (!key || key.includes("\0")) {
return new Response("Not Found", { status: 404 });
}
try {
const file = s3.file(key);
const stat = await file.stat();
const headers: Record<string, string> = {
"Content-Type": stat.type || "application/octet-stream",
"Content-Length": String(stat.size),
"Cache-Control": CACHE_CONTROL,
};
return req.method === "HEAD"
? new Response(null, { headers })
: new Response(file.stream(), { headers });
} catch {
return new Response("Not Found", { status: 404 });
}
},
};S3_BUCKET=${{Bucket.BUCKET}}
S3_REGION=${{Bucket.REGION}}
S3_ENDPOINT=${{Bucket.ENDPOINT}}
S3_ACCESS_KEY_ID=${{Bucket.ACCESS_KEY_ID}}
S3_SECRET_ACCESS_KEY=${{Bucket.SECRET_ACCESS_KEY}}
S3_VIRTUAL_HOSTED_STYLE=trueGenerate its public domain, then set the Storefront variable:
NEXT_PUBLIC_MEDIA_HOSTNAME=${{Bucket-proxy.RAILWAY_PUBLIC_DOMAIN}}Do not set S3_FORCE_PATH_STYLE for Railway Bucket. The local Compose
environment sets it because MinIO is used there strictly as an S3 emulator.
For local development only, Compose makes the emulator bucket publicly
downloadable so it does not need to reproduce Railway Functions.
New Railway Buckets use virtual-hosted-style URLs. For an older Bucket whose
Credentials tab explicitly says it requires path-style URLs, set the Function's
S3_VIRTUAL_HOSTED_STYLE=false.
Production startup rejects missing, partial, mixed, or malformed storage
configuration instead of falling back to ephemeral disk. Do not add anonymous
PUT or DELETE handlers to the public Function: the Backend already performs
authenticated writes directly against S3_ENDPOINT.
Existing MinIO deployments may temporarily retain a complete
MINIO_ENDPOINT, MINIO_ACCESS_KEY, MINIO_SECRET_KEY, and optional
MINIO_BUCKET contract. Never mix those variables with any S3_* variable.
Existing media URLs stored in PostgreSQL are not rewritten; retain the old
MinIO endpoint while migrating objects/records, or re-upload affected media.
This template is intended for new deployments. Updating an existing production installation is not a straightforward in-place upgrade. Keep the existing stack running and read UPGRADE.md before considering a parallel, manually validated migration.
For deterministic local infrastructure:
docker compose up --buildIf you get database connection errors:
-
Verify PostgreSQL is running:
pg_isready -U postgres
-
Check the connection string in
backend/.env:DATABASE_URL=postgres://postgres:postgres@localhost:5432/mercurjs
-
Ensure the database exists:
psql -U postgres -l | grep mercurjs
If you get Redis connection errors:
-
Verify Redis is running:
redis-cli ping
-
Check Redis URL in
backend/.env:REDIS_URL=redis://localhost:6379
If a port is already in use, you can:
-
Kill the process using the port (Windows):
netstat -ano | findstr :9000 taskkill /PID <PID> /F
-
Or change the port in the respective service's configuration
Mercur requires Node.js 20+. Check your version:
node --versionIf using nvm:
nvm use 22# Run migrations
pnpm medusa db:migrate
# Seed database
pnpm seed
# Create admin user
pnpm medusa user -e email@example.com -p password
# Build for production
pnpm build
# Start production server
pnpm start# Development mode
pnpm dev
# Build for production
pnpm build
# Preview production build
pnpm preview# Development mode
pnpm dev
# Build for production
pnpm build
# Preview production build
pnpm preview# Development mode
pnpm dev
# Build for production
pnpm build
# Start production server
pnpm start- Backend uses MedusaJS v2 with the MercurJS B2C marketplace plugin
- Admin Panel is a standalone React/Vite application with custom marketplace administration features
- Vendor Panel is a standalone React/Vite application for vendors/sellers to manage their products and orders
- Storefront is built with Next.js and includes marketplace-specific components
- All services communicate through the backend API on port 9000
After running migrations, the following key tables will be created:
- Products, Variants, Inventory
- Orders, Payments, Fulfillments
- Customers, Users
- Sellers (marketplace-specific)
- Commissions (marketplace-specific)
- And many more...
- Change all secrets in
.envfiles - Use strong passwords for PostgreSQL and admin users
- Configure proper CORS settings
- Enable HTTPS
- Use environment-specific configurations
This project is based on MercurJS and MedusaJS. Please refer to their respective licenses.
- Access the admin panel at http://localhost:5173
- Login with your admin credentials
- Configure your store settings
- Add products
- Visit the storefront at http://localhost:3000
Happy selling! π