A minimal MVP marketplace where farmers list products and buyers pay using the Stellar Network (XLM).
- Frontend: React + Vite
- Backend: Node.js + Express
- Database: SQLite (local dev, default) / PostgreSQL (production)
- Payments: Stellar Testnet (XLM)
FarmersMarketplace/
βββ backend/
β βββ src/
β β βββ index.js # Express app entry
β β βββ stellar.js # Stellar SDK helpers
β β βββ middleware/auth.js
β β βββ db/schema.js # SQLite schema + connection
β β βββ routes/
β β βββ auth.js # register, login
β β βββ products.js # CRUD listings
β β βββ orders.js # place order + pay
β β βββ wallet.js # balance, transactions, fund
β βββ package.json
βββ frontend/
βββ src/
β βββ App.jsx
β βββ api/client.js # API wrapper
β βββ context/AuthContext.jsx
β βββ components/Navbar.jsx
β βββ pages/
β βββ Auth.jsx # Login + Register
β βββ Dashboard.jsx # Farmer: add/view products
β βββ Marketplace.jsx # Buyer: browse
β βββ ProductDetail.jsx # Buy flow
β βββ Wallet.jsx # Balance + transactions
βββ package.json
cd backend
cp .env.example .env
npm install
npm run devRuns on http://localhost:4000
cd frontend
npm install
npm run devRuns on http://localhost:3000
- Register as a buyer and a farmer (two separate accounts)
- Go to Wallet β click "Fund with Testnet XLM" (uses Stellar Friendbot, free testnet tokens)
- As a farmer, go to Dashboard and list a product priced in XLM
- As a buyer, browse the Marketplace, open a product, set quantity, click Buy Now
- The backend signs and submits a real Stellar transaction on testnet
- View the transaction hash in Wallet β Transaction History or on stellar.expert
Interactive API documentation is available at http://localhost:4000/api/docs when the backend is running.
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/auth/register | β | Register user |
| POST | /api/auth/login | β | Login |
| GET | /api/products | β | Browse all products |
| GET | /api/products/:id | β | Product detail |
| POST | /api/products | farmer | Create listing |
| GET | /api/products/mine/list | farmer | My listings |
| DELETE | /api/products/:id | farmer | Remove listing |
| POST | /api/orders | buyer | Place + pay order |
| GET | /api/orders | buyer | Order history |
| GET | /api/orders/sales | farmer | Incoming sales |
| GET | /api/wallet | auth | Balance |
| GET | /api/wallet/transactions | auth | TX history |
| POST | /api/wallet/fund | auth | Fund via Friendbot (testnet) |
| GET | /api/contracts/:contractId/state?prefix= | auth | View Soroban contract storage entries (JSON: key, val, durability) |
Schema changes are managed through versioned SQL migration files in backend/migrations/.
cd backend
npm run migrate # apply all pending migrations
npm run migrate:rollback # revert the last applied migrationMigrations run automatically on app startup β no manual step needed for development.
- Migration files:
backend/migrations/NNN_description.sql - Rollback files:
backend/migrations/NNN_description.undo.sql(optional) - Applied migrations are tracked in a
migrationstable in the database - Running
migratetwice is safe β already-applied migrations are skipped
# Up migration
echo "ALTER TABLE products ADD COLUMN featured INTEGER DEFAULT 0;" \
> backend/migrations/002_add_featured.sql
# Rollback (optional)
echo "ALTER TABLE products DROP COLUMN IF EXISTS featured;" \
> backend/migrations/002_add_featured.undo.sql
npm run migrateThe application includes automated database backup functionality to protect against data loss.
Create a timestamped backup of the database:
cd backend
npm run backupThis creates a backup file in backend/backups/ with format market-YYYY-MM-DD.db.
Restore the database from a backup file:
cd backend
npm run restore -- backups/market-2024-01-01.dbImportant: Before restoring, the current database is automatically backed up to market.db.backup.
- Backups run automatically every day at midnight UTC
- Only the last 7 backups are retained (older ones are automatically deleted)
- Backup status and errors are logged using the structured logging system
- Backup files are stored in:
backend/backups/ - File naming convention:
market-YYYY-MM-DD.db - Maximum retention: 7 days
- Quick Restore: Use
npm run restorewith the desired backup file - Emergency Recovery: Copy
market.db.backup(created before restore) back tomarket.db - Complete Reset: Delete
market.dband restart the application (fresh schema)
The backend supports both SQLite (local dev) and PostgreSQL (production), controlled by the DATABASE_URL environment variable.
No extra setup needed. SQLite is used automatically when DATABASE_URL is not set.
- Add
DATABASE_URLto your.env:DATABASE_URL=postgresql://user:password@localhost:5432/farmersmarketplace - The schema is created automatically on first start.
cp backend/.env.example backend/.env
# Edit backend/.env β set JWT_SECRET etc.
docker compose upThis starts:
postgresβ PostgreSQL 16 on port 5432backendβ Express API on port 4000 (connected to postgres)frontendβ React app on port 3000
DATABASE_URL=postgresql://user:pass@host:5432/dbname \
node backend/scripts/migrate-sqlite-to-pg.jsTest Soroban contracts against a local Stellar node using the built-in test harness.
docker-compose -f docker-compose.test.yml up -dThis starts a stellar/quickstart node on port 8000 with Soroban RPC enabled.
cd backend
npm run test:contractsbackend/src/__tests__/helpers/soroban.js exposes:
fundAccount(publicKey)β fund via local FriendbotdeployContract(wasmBuffer, keypair)β upload WASM and create contract instanceinvokeContract(contractId, method, args, keypair)β call a contract function
| Variable | Default | Description |
|---|---|---|
TEST_HORIZON_URL |
http://localhost:8000 |
Local Horizon endpoint |
TEST_SOROBAN_RPC_URL |
http://localhost:8000/soroban/rpc |
Local Soroban RPC |
TEST_NETWORK_PASSPHRASE |
Standalone Network ; February 2017 |
Local network passphrase |
SKIP_CONTRACT_TESTS |
false |
Set to true to skip in CI without Docker |
- Stellar wallets are auto-created on registration
- All payments use XLM on Stellar Testnet β no real money involved
- SQLite database file (
market.db) is created automatically on first run (whenDATABASE_URLis not set) - To reset SQLite: delete
backend/market.db