The backend service for Fluxity — a token-streaming and lockup platform built on the Stellar / Soroban network.
It exposes a REST API for token and lockup data, mirrors on-chain lockup contract events into MongoDB, and provides a testnet faucet (free token mint + sample stream).
| Concern | Choice |
|---|---|
| Runtime | Node.js 22+ (Docker image uses Node 24 LTS) |
| Language | TypeScript 5.9 (strict) |
| HTTP framework | Express 5 |
| Database | MongoDB via Mongoose 8 |
| Blockchain | @stellar/stellar-sdk 16 (Soroban RPC) |
| Validation | Zod 4 (environment + request input) |
| Logging | Pino (pretty in dev, JSON in prod) |
| API docs | Swagger / OpenAPI 3.1 (swagger-jsdoc) |
| Tooling | ESLint 9 (flat config) · Prettier · tsx |
- Node.js 22 or newer
- A reachable MongoDB instance
- Stellar admin secret key with access to the Soroban contracts (for faucet/mint)
# 1. Install dependencies
npm install
# 2. Configure environment
cp .env.example .env
# then edit .env with real values (see "Environment variables" below)
# 3. Run in development (hot-reload via tsx, no build step)
npm run devThe API starts on http://localhost:${PORT} (default 3000). Interactive API
docs are served at /swagger, and a health probe at /health.
npm run build # compile TypeScript to ./dist
npm start # run the compiled server (node dist/index.js)All variables are validated at startup. If any are missing or malformed, the process prints a detailed report and exits.
| Variable | Required | Default | Description |
|---|---|---|---|
NODE_ENV |
no | development |
development | production | test |
PORT |
no | 3000 |
HTTP port |
DB_URI |
yes | — | MongoDB connection string |
DB_NAME |
yes | — | MongoDB database name |
BASE_FEE |
no | 100000 |
Soroban base fee (stroops, numeric string) |
ADMIN_SECRET_KEY |
yes | — | Stellar admin secret key (S...) used to sign faucet/mint txs |
TESTNET_SOROBAN_RPC_URL |
yes | — | Testnet Soroban RPC URL |
MAINNET_SOROBAN_RPC_URL |
yes | — | Mainnet Soroban RPC URL |
TESTNET_CONTRACT_ID |
yes | — | Testnet lockup contract id (C...) |
MAINNET_CONTRACT_ID |
yes | — | Mainnet lockup contract id (C...) |
CLAIM_STREAM_AMOUNT |
yes | — | Faucet stream amount (numeric string) |
CLAIM_TOKEN_AMOUNT |
yes | — | Faucet mint amount (numeric string) |
ADMIN_PASSWORD |
yes | — | Shared secret for the Authorization header on admin endpoints |
LOG_LEVEL |
no | info |
trace…fatal | silent |
LOG_FILE_PATH |
no | stdout | In production, write JSON logs to this file instead of stdout |
CORS_ORIGIN |
no | * |
* or a comma-separated origin allowlist |
Responses share a consistent envelope:
{ "status": "success" | "error", "message": "string", "result": {} }Endpoints (except /health and /swagger) are namespaced by network:
/:network/... where network is testnet or mainnet.
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /health |
— | Liveness/readiness probe |
| GET | /swagger |
— | Swagger UI |
| GET | /:network/token |
— | List tokens |
| POST | /:network/token |
admin | Add a token |
| PUT | /:network/token/:token |
admin | Edit a token (logo, claimable) |
| DELETE | /:network/token |
admin | Delete a token |
| GET | /:network/token/already-minted/:user |
— | Has a testnet user already minted? |
| POST | /:network/token/mint |
— | Testnet faucet: mint + sample stream |
| GET | /:network/lockup |
— | List lockups (filterable) |
| GET | /:network/lockup/:id |
— | Get a lockup by id |
| POST | /:network/lockup |
— | Mirror a created lockup |
| PUT | /:network/lockup |
— | Mirror a withdrawal |
| DELETE | /:network/lockup |
— | Mirror a cancellation |
Admin authentication: send the ADMIN_PASSWORD value in the Authorization
header. The comparison is constant-time.
In the background, the service polls the lockup contract for CREATED /
WITHDRAWN / CANCELLED events and mirrors them into MongoDB.
A multi-stage Dockerfile (Node 24 Alpine, non-root, healthcheck) and a
compose.yaml that bundles MongoDB are included.
# Build and run the whole stack (API + MongoDB)
cp .env.example .env # edit values; DB_URI is overridden to the mongo service
docker compose up --build- The API is published on
127.0.0.1:${HOST_PORT:-3000}. compose.yamlwaits for MongoDB to be healthy before starting the API.- In production mode logs are emitted as JSON to stdout (
docker compose logs api).
Build just the image:
docker build -t fluxity-api .| Script | Description |
|---|---|
npm run dev |
Hot-reloading dev server (tsx) |
npm run build |
Compile TypeScript to ./dist |
npm start |
Run the compiled server |
npm run typecheck |
Type-check without emitting |
npm run lint |
ESLint |
npm run lint:fix |
ESLint with autofix |
npm run format |
Prettier write |
npm run format:check |
Prettier check |
src/
├── index.ts # app bootstrap, middleware, graceful shutdown
├── env.ts # zod-validated, typed environment config
├── logger.ts # pino logger
├── db.ts # mongoose connection + background migration
├── constant/ # enums (networks)
├── middleware/ # auth, json envelope, network guard, error handler
├── models/ # mongoose schemas (Token, Lockup, Ledger, AlreadyMinted)
├── routes/ # token, lockup and swagger routers
├── event/ # contract event listener + sync handlers
├── migrations/ # startup lockup backfill
├── swagger/ # OpenAPI JSDoc definitions
├── types/ # shared types + Express augmentation
└── utils/ # validation, error helpers, Soroban helpers
MIT