Self-hosted eCommerce engine built for small sellers. Optimized for 1 vCPU / 1GB RAM single-node deployment.
- 💡 Feature Requests & Voting — suggest and vote on features
- 💬 Telegram Group — discuss anything OpenUMKM
- 🐛 GitHub Issues — bug reports
| Layer | Technology |
|---|---|
| Backend | NestJS + Fastify |
| ORM | Drizzle ORM (SQL-first) |
| Database | PostgreSQL 15 |
| Frontend | EJS (server-side rendering) |
| Auth | JWT (httpOnly cookie) |
| Payment | Xendit + Manual Bank Transfer |
| Shipping | RajaOngkir (Komerce API v1) + Custom Methods |
| Nodemailer (SMTP) | |
| Storage | Local filesystem or S3-compatible |
| AI | OpenAI-compatible API |
| Styling | Custom CSS (Indigo design system, Inter font) |
- Product catalog with variants, images, SEO meta
- Guest & logged-in checkout with saved address support
- Xendit online payment + manual bank transfer with proof upload & review flow
- Shipping cost calculation via RajaOngkir (8 couriers) + configurable custom shipping methods
- Customer dashboard (order history, address management)
- Admin panel (products CRUD, orders, payment confirmations, full settings)
- Revenue dashboard (daily / weekly / monthly / yearly + breakdown by status)
- Multi-currency (IDR / USD) with manual exchange rates
- Configurable tax rates (multiple rates, togglable)
- AI content generation (product description, SEO meta) — client-side via OpenAI-compatible API
- Bilingual UI (Bahasa Indonesia / English) with cookie-persisted switcher
- Email notifications (order created, payment confirmed, shipped, cancelled, password reset)
- CSRF protection (Double Submit Cookie)
- First-run setup wizard with optional
SETUP_SECRETlock
- Node.js 20+
- PostgreSQL 15+ (or use Docker)
# Install dependencies
npm install
# Generate migrations (if schema changed)
npm run db:generate
# Apply migrations
npm run db:migrate
# Build
npm run build
# Run
node dist/main.jsApp runs at http://localhost:3000. First visit will redirect to /setup.
docker compose up -dThis starts both the app (port 3000) and PostgreSQL with tuned low-memory settings (shared_buffers=128MB, work_mem=4MB, max_connections=20).
# Generate migration from schema changes
npm run db:generate
# Apply migration to database
npm run db:migratesrc/
├── common/ # Shared helpers (auth cookie, CSRF, i18n, view helpers)
├── controllers/ # HTTP route handlers
│ ├── admin.controller.ts
│ ├── admin-orders.controller.ts
│ ├── admin-products.controller.ts
│ ├── admin-settings.controller.ts
│ ├── admin-settings-bank.controller.ts
│ ├── admin-settings-shipping.controller.ts
│ ├── admin-settings-tax.controller.ts
│ ├── auth.controller.ts
│ ├── cart.controller.ts
│ ├── checkout.controller.ts
│ ├── dashboard.controller.ts
│ ├── health.controller.ts
│ ├── payment.controller.ts
│ ├── setup.controller.ts
│ ├── storefront.controller.ts
│ └── xendit-webhook.controller.ts
├── services/ # Business logic
│ ├── address.service.ts
│ ├── auth.service.ts
│ ├── email.service.ts
│ ├── order.service.ts
│ ├── payment-confirmation.service.ts
│ ├── product.service.ts
│ ├── revenue.service.ts
│ ├── session.service.ts
│ ├── settings.service.ts
│ ├── setup.service.ts
│ ├── shipping.service.ts
│ ├── upload.service.ts
│ └── xendit.service.ts
├── db/
│ ├── index.ts # Drizzle + pg pool
│ ├── schema.ts # All table definitions
│ └── migrations/ # Generated SQL
├── mail/templates/ # EJS email templates (9 templates)
├── locales/
│ ├── id/ # Bahasa Indonesia translations
│ └── en/ # English translations
├── views/ # EJS templates
│ ├── admin/ # Admin panel pages + partials
│ ├── auth/ # Login, register, password reset
│ ├── dashboard/ # Customer dashboard
│ ├── partials/ # Shared: head, header, footer
│ ├── setup/ # First-run wizard
│ └── storefront/ # Home, product, cart, checkout
├── public/css/ # Stylesheets
├── app.module.ts
├── config.ts
└── main.ts
| Variable | Required | Default | Description |
|---|---|---|---|
DATABASE_URL |
Yes | postgres://app:app@localhost:5432/app |
PostgreSQL connection string |
JWT_SECRET |
Yes | opencode-secret |
Secret for JWT signing (change in production) |
PORT |
No | 3000 |
Server port |
STORAGE_TYPE |
No | local |
local or s3 |
SETUP_SECRET |
No | (empty) | Lock /setup page with a secret |
S3_ENDPOINT |
No | — | S3-compatible endpoint |
S3_BUCKET |
No | — | S3 bucket name |
S3_ACCESS_KEY |
No | — | S3 access key |
S3_SECRET_KEY |
No | — | S3 secret key |
Most runtime config (SMTP, Xendit, RajaOngkir, AI, store info) is configured at /admin/settings after setup.
14 tables, 5 enums. See src/db/schema.ts for full definitions.
user_role— customer, sellerorder_status— pending, waiting_confirmation, paid, processing, shipped, completed, cancelled, expiredpayment_method— xendit, manual_transferconfirmation_status— pending, approved, rejectedtax_apply_to— subtotal, subtotal_shipping
GET /— Home / product listing (search, pagination, sort)GET /product/:slug— Product detailGET /cart/POST /cart/add/POST /cart/update/POST /cart/removeGET /checkout/POST /checkout/submitGET /checkout/success/:id— Order confirmation + payment instructionsGET /payment/confirm/:id/POST /payment/confirm/:id— Manual transfer proof upload
GET/POST /auth/loginGET/POST /auth/registerGET/POST /auth/forgot-passwordGET/POST /auth/reset-password/:tokenGET /auth/logout
GET /dashboard— OverviewGET /dashboard/orders— Order historyGET /dashboard/orders/:id— Order detailGET /dashboard/addresses— Address CRUD
GET /admin— Dashboard (revenue stats, recent orders)GET /admin/revenue?period=— Revenue breakdown JSONGET /admin/products+ CRUD (with image & variant management)GET /admin/orders+ status/tracking updatesGET /admin/payments/confirmations+ approve/rejectGET /admin/settings(store info, API keys, SMTP, payment, couriers, currency, tax, SEO, AI)GET /admin/settings/bank-accounts+ CRUDGET /admin/settings/taxes+ CRUDGET /admin/settings/currencies+ toggle/rateGET /admin/settings/shipping-methods+ CRUD
GET /health— Health checkGET /setup— First-run wizardPOST /xendit/webhook— Xendit payment callback
Before deploying:
- Set
JWT_SECRETto a strong random value - Set
SETUP_SECRETif you want to lock the/setuppage - After setup, configure at
/admin/settings:- SMTP (required for password reset & order emails)
- Xendit API key + callback token (required for webhook security)
- RajaOngkir API key + origin city (if using RajaOngkir shipping)
- AI endpoint (if using AI content generation)
- Configure bank accounts at
/admin/settings/bank-accounts(if using manual transfer) - Configure shipping mode at
/admin/settings(custom,rajaongkir, orboth) - Add S3 env vars if using S3 storage instead of local uploads
Security note: The Xendit webhook refuses all requests until
xendit_callback_tokenis set in settings — this prevents unauthenticated order status tampering.
Simplicity is not fewer features. Simplicity is fewer decisions at runtime.
- Max 2 containers (app + db)
- No Redis, no background workers, no microservices
- Server-side rendering only, minimal client JS
- Target: <800MB RAM, 10–30 concurrent users
Contributions welcome! Lihat CONTRIBUTING.md untuk panduan lengkap.
Thanks to the sponsors supporting OpenUMKM development:
BOC — provides the domain and VPS for OpenUMKM infrastructure.
Interested in becoming a sponsor? Reach out via our Telegram Group.
MIT License — free to use, modify, and distribute.

