Backend Packages Overview
This page documents the complete backend architecture of the BANA POS system. It covers all 15 packages in the packages/ directory, the shared PostgreSQL database with 10 schemas, event-driven infrastructure (Kafka + Redis Pub/Sub + BullMQ), security mechanisms, and the IGNIS framework patterns that unify the entire codebase.
Technology Stack
Core Technologies
| Technology | Version | Purpose |
|---|---|---|
| Bun | >=1.3.8 | JavaScript/TypeScript runtime |
| TypeScript | ~5.9.3 | Static type system |
| IGNIS Framework | 0.0.7-7 | IoC container, DI, application lifecycle |
| IGNIS Helpers | 0.0.6-7 | Framework utility extensions |
| Hono | 4.11.3 | Lightweight HTTP framework |
| Drizzle ORM | ^0.45.1 | Type-safe SQL ORM |
| Drizzle Kit | ^0.31.8 | Schema migrations CLI |
| Drizzle Zod | ^0.8.3 | Drizzle-to-Zod schema bridge |
| Zod | 4.1.13 | Runtime schema validation |
| PostgreSQL | 16+ | Primary relational database |
| Redis | 7+ | Caching, Pub/Sub, BullMQ |
API and Documentation
| Technology | Version | Purpose |
|---|---|---|
| @hono/zod-openapi | 1.2.0 | OpenAPI route definitions |
| @scalar/hono-api-reference | ^0.9.28 | Interactive API explorer UI |
Supporting Libraries
| Technology | Version | Purpose |
|---|---|---|
| pg | ^8.16.3 | PostgreSQL client driver |
| bcrypt | ^6.0.0 | Password hashing (Identity) |
| dayjs | ^1.11.19 | Date/time manipulation |
| BullMQ | ^5.14.3 | Distributed job queues |
| dotenv-flow | ^4.1.0 | Environment variable management |
| nodemailer | ^7.0.12 | Transactional email |
System Topology
All 15 Backend Packages
| Package | Port | Role | Key Responsibilities | Primary Dependencies |
|---|---|---|---|---|
@nx/core | -- | Foundation | Base classes, DB models across 10 schemas, utilities, auth config | -- |
@nx/identity | 31010 | Service | Authentication, authorization, user management, RBAC | core |
@nx/commerce | 31020 | Service | Products, pricing engine, merchants, categories, CDC | core, asset, inventory, search |
@nx/sale | 31030 | Service | Order lifecycle, checkout, kitchen orders, payment orchestration | core, mq-pay |
@nx/inventory | 31050 | Service | Stock tracking, purchase orders, vendor management | core |
@nx/finance | 31040 | Service | Wallets, income/expense tracking, financial categories | core |
@nx/pricing | 31070 | Service | Fare sets, tax engine, promotions, pricing rules | core |
@nx/payment | 31080 | Service | Webhook config, credential loading, payment dispatch | core, mq-pay |
@nx/ledger | 31060 | Service | HKD accounting ledger generation (PDF/XLSX), encryption, S3 | core |
@nx/outreach | 31110 | Service | Newsletter subscriptions, contact form inquiries | core |
@nx/licensing | 31120 | Service | Policy-based license management, certificate signing, validation pipeline | core |
@nx/helpdesk | 31130 | Service | Multi-channel customer support — tickets, SLA, auto-assignment, knowledge base, surveys | core |
@nx/signal | 31090 | Service | WebSocket with ECDH E2E encryption, Redis Pub/Sub | core |
@nx/invoice | -- | Service | E-invoice generation (IIAPI + T-VAN) | core, iiapi, t-van |
@nx/search | -- | Library | Typesense integration, filter conversion, CDC sync | core |
@nx/asset | -- | Library | Minio/disk file storage, MetaLink management | core |
Packages marked
--for Port operate as embedded libraries or event-driven workers rather than standalone HTTP services.
Package Architecture
Build Dependency Graph
The Makefile enforces this build order. A package cannot be built until all of its dependencies have been compiled.
Runtime Dependency Graph
Per-Package Reference
@nx/core -- Foundation
Purpose: Shared kernel providing base classes, all 55 database models across 7 PostgreSQL schemas, cross-cutting utilities, and authentication configuration. Every other package depends on @nx/core.
Key Exports:
| Export | Type | Description |
|---|---|---|
DefaultApplication | Class | Base application with auth, CORS, Swagger, health checks |
SoftDeletableRepository | Class | Repository with soft-delete (sets deletedAt instead of physical delete) |
PostgresCoreDataSource | DataSource | Shared Drizzle + node-postgres data source |
MigrationRepository | Repository | Tracks migration status in the database |
IdGenerator | Utility | Snowflake ID generation singleton |
IdentityNetworkService | Service | Cross-service HTTP calls to the Identity service |
useRequestContext() | Function | Extracts authenticated user, roles, and response normalizers |
CryptoUtility | Utility | AES-256-GCM encryption/decryption for credentials |
@logged | Decorator | Performance measurement logging |
bootstrapApplication() | Helper | Application entry point factory |
bootstrapMigration() | Helper | Migration entry point factory |
createAppConfig() | Helper | Centralized application configuration builder |
createMigrationProcessLoader() | Helper | Dynamic migration process importer |
Constants:
| Constant | Values |
|---|---|
FixedUserRoles | SUPER_ADMIN (999), ADMIN (998), OPERATOR (997), ORGANIZER_OWNER (899), EMPLOYEE (898) |
MerchantTypes | DEFAULT, TICKET, FNB, THEATER |
PaymentProviders | VNPAY_QR_MMS, SYSTEM |
InvoiceProviders | T_VAN, IIAPI |
TIP
Detailed documentation: @nx/core
@nx/identity -- Authentication and Authorization
Purpose: User authentication, authorization, RBAC, and employee management. Runs as a standalone HTTP service on port 3001.
Services:
| Service | Responsibility |
|---|---|
AuthenticationService | Sign-in, sign-up, password change |
UserService | Atomic user creation (user + identifiers + profile + roles) |
EmployeeService | Employee management with organizer/merchant mapping |
MailVerificationService | Email verification with 6-digit codes and tokens |
Authentication Strategies: JWT, Basic (via IGNIS AuthenticateComponent)
Identifier Schemes: USERNAME, EMAIL, PHONE_NUMBER, USER_NUMBER, NX_AUTH
Email Verification Limits:
- 6-digit code: 10-minute expiry, 3 attempts max
- 32-byte token: 24-hour expiry
- Rate limit: 60-second cooldown, 5 resends/day, 15-minute lockout
TIP
Detailed documentation: @nx/identity
@nx/commerce -- Product Catalog and Pricing
Purpose: Product management, dynamic pricing engine, merchant onboarding, and category system. Runs on port 3002. Integrates asset storage, inventory tracking, and Typesense search.
Services:
| Service | Responsibility |
|---|---|
ProductService | Aggregate creation (info, identifiers, variants) |
ProductVariantService | Variant management with pricing integration |
FareService | Static/dynamic pricing with context-based rule evaluation |
MerchantService | Merchant onboarding with categories and sale channels |
Pricing Engine:
| Concept | Description |
|---|---|
Fare | Price point with amount, status, time-based activation |
FareRule | Dynamic condition (quantity, date, custom context) |
| Rule Operators | EQ, NE, GT, GTE, LT, LTE, IN, NIN |
| Rule Types | OVERRIDE (stops evaluation), DISCOUNT, MARKUP |
Integrated Components: ApplicationAssetComponent, ApplicationInventoryComponent, ApplicationSearchComponent, NxTVanComponent
TIP
Detailed documentation: @nx/commerce
@nx/sale -- Order Management
Purpose: Sale order lifecycle from cart to completion, checkout validation, and payment integration via MQ-Pay. Runs on port 3003.
Order Lifecycle:
Services:
| Service | Responsibility |
|---|---|
SaleOrderService | Create orders, add items, cancel orders |
CheckoutService | DRAFT to PROCESSING transitions, validation |
SaleOrderItemService | Batch item updates with auto-merge |
PaymentWebhookService | Handle MQ-Pay payment status callbacks |
Order Constraints: Max 9,999 per item quantity. Max 100 items per order.
Components: RedisComponent (cache + BullMQ + Pub/Sub), QueueComponent, ApplicationWebSocketComponent
TIP
Detailed documentation: @nx/sale
@nx/inventory -- Stock Management
Purpose: Multi-location inventory tracking, purchase order processing, and audit trails. Operates as an embedded library consumed by @nx/commerce.
Services:
| Service | Responsibility |
|---|---|
InventoryService | Create/update inventory for product variants |
PurchaseOrderService | PO workflow: DRAFT to PROCESSING to CONFIRMED to COMPLETED |
PurchaseOrderItemService | PO line item management |
Stock Quantities:
| Field | Meaning |
|---|---|
quantityOnHand | Total physical stock |
quantityAvailable | On-hand minus reserved (available for sale) |
quantityReserved | Allocated for pending orders |
Tracking Types (20 predefined):
- Inbound: STOCK_IN, PURCHASE, TRANSFER_IN, RETURN_FROM_CUSTOMER, ADJUSTMENT_IN
- Outbound: STOCK_OUT, SALE, TRANSFER_OUT, RETURN_TO_VENDOR, EXPIRED, LOST, DAMAGED
- Neutral: INVENTORY_COUNT, ADJUSTMENT_NEUTRAL
@nx/search -- Typesense Integration
Purpose: Real-time search powered by Typesense with automatic IGNIS filter-to-Typesense query conversion. Data flows from PostgreSQL via Debezium CDC.
Collections: products, organizers, merchants, categories, devices, sale-channels
Services:
| Service | Responsibility |
|---|---|
SearchService | Core search with filter conversion |
BaseTypesenseSearchService | Abstract base for custom search implementations |
TypesenseConverter Operators: eq, neq, gt, gte, lt, lte, between, inq (IN), nin (NOT IN), and, or
Environment Variables: APP_ENV_TYPESENSE_API_KEY, APP_ENV_TYPESENSE_NODES (format: protocol:host:port, comma-separated)
@nx/asset -- Media Storage
Purpose: Dual-backend file storage (Minio S3-compatible + local disk) with metadata tracking via the MetaLink entity.
Storage Configuration:
| Backend | Endpoint | Use Case |
|---|---|---|
| Minio | /assets | Uploaded media (images, documents) |
| Local disk | /resources | Static resources (templates, banners) |
MetaLink Fields: bucketName, objectName, link (presigned URL), mimetype, size, etag, storageType, principalId, principalType
TIP
Detailed documentation: @nx/asset
@nx/finance -- Financial Tracking
Purpose: Income/expense tracking with wallet management. Event-driven -- listens to commerce and payment events to automatically create financial records.
Services:
| Service | Responsibility |
|---|---|
FinanceWorkerService | Event and queue handler for all financial operations |
FinanceWorkerService Methods:
| Method | Trigger | Action |
|---|---|---|
handleCommerceInitialized() | COMMERCE_INITIALIZED event | Creates default Cash wallet for new merchant |
handlePaymentSuccess() | PAYMENT_SUCCESS event | Creates INCOME transaction |
handlePurchaseOrderReceived() | PURCHASE_ORDER_RECEIVED queue job | Creates EXPENSE transaction |
Wallet Types: CASH, BANK, EWALLET, CREDIT_CARD
Transaction Types: INCOME, EXPENSE, TRANSFER
Controllers:
| Controller | Path | Type |
|---|---|---|
FinanceWalletController | /finance-wallets | ControllerFactory CRUD |
FinanceCategoryController | /finance-categories | ControllerFactory CRUD |
FinanceTransactionController | /finance-transactions | ControllerFactory CRUD |
@nx/payment -- Payment Orchestration
Purpose: Bridge layer between MQ-Pay and the application. Manages webhook configurations, loads and decrypts payment credentials, and dispatches webhook events with retry logic.
Services:
| Service | Responsibility |
|---|---|
PaymentConfigurationService | Fetch and decrypt payment configs from Configuration table |
WebhookDispatcherService | Fire-and-forget webhook dispatch with exponential backoff |
Controllers:
| Controller | Path | Type |
|---|---|---|
WebhookConfigController | /webhook-configs | ControllerFactory CRUD |
Supported Providers: VNPAY_QR_MMS, VNPAY_PHONE_POS
Migration Seeds: payment-0001-seed-vnpay-qr-mms-configuration, payment-0002-seed-vnpay-phone-pos-configuration
TIP
Detailed documentation: @nx/payment
@nx/signal -- WebSocket Service
Purpose: Centralized real-time communication with end-to-end encryption. Stateless (no database, no migrations). Uses Redis Pub/Sub for cross-instance message delivery.
Services:
| Method | Description |
|---|---|
broadcast({ topic, data }) | Send to all connected clients across all instances |
sendToRoom({ room, topic, data }) | Send to all clients in a room |
sendToClient({ clientId, topic, data }) | Send to a specific client (local or remote via Redis) |
disconnectClient({ clientId }) | Force-close a client connection |
REST API (base: /socket/websocket/clients):
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /status | None | Server readiness + client count |
| GET | / | JWT/Basic | List connected clients |
| POST | /broadcast | JWT/Basic | Broadcast to all clients |
| POST | /rooms/:roomName/send | JWT/Basic | Send to a room |
| POST | /:clientId/send | JWT/Basic | Send to specific client |
| POST | /:clientId/disconnect | JWT/Basic | Disconnect a client |
Encryption: ECDH P-256 key exchange, AES-256-GCM per-message encryption. WebSocket endpoint: /stream.
Redis Modes: Single instance (default) or Cluster mode via APP_ENV_WEBSOCKET_REDIS_MODE.
TIP
Detailed documentation: @nx/signal
@nx/invoice -- E-Invoice Generation
Purpose: Vietnam e-invoice integration through IIAPI (VNPAY viiAPI) and T-VAN providers. Supports VAT invoices, sales invoices, POS invoices, and more.
Dependencies: @nx/core, @nx/iiapi (third-party), @nx/t-van (third-party)
TIP
Detailed documentation: @nx/invoice
Layered Architecture
Every backend service follows the Controller-Service-Repository pattern enforced by the IGNIS framework.
| Layer | Responsibility | IGNIS Base Class |
|---|---|---|
| Controller | HTTP transport, input validation via Zod, response formatting | ControllerFactory.defineCrudController() or custom |
| Service | Pure business logic, orchestration, event handling | BaseService |
| Repository | Database access abstraction, Drizzle ORM queries | SoftDeletableRepository or DefaultCRUDRepository |
| Component | Cross-cutting concerns: Redis, queues, external integrations | BaseComponent |
| DataSource | Connection pooling and Drizzle connector setup | BaseDataSource |
Request Flow
1. HTTP Request --> Controller (route handler)
2. Controller validates input with Zod schemas via @hono/zod-openapi
3. Controller calls Service layer via DI
4. Service executes business logic
5. Service calls Repository for data access
6. Repository executes Drizzle ORM queries against PostgreSQL
7. Response flows back through layersDatabase Overview
All database models are defined in @nx/core under src/models/schemas/. Services share a single PostgresCoreDataSource connection.
7 PostgreSQL Schemas, 55 Models
| Schema | Models | Count | Key Entities |
|---|---|---|---|
public | General domain | 30 | User, UserCredential, UserProfile, Role, Permission, Product, ProductInfo, ProductIdentifier, ProductVariant, Merchant, Organizer, Category, Device, Terminal, SaleChannel, Configuration, Employee, Vendor, MetaLink, MerchantType, CategoryTemplate, etc. |
pricing | Pricing engine | 7 | Fare, FareSet, FareRule, Cost, Tax, TaxSet, TaxType |
allocation | Event seating | 4 | Venue, Section, Seat, Allocation |
inventory | Stock management | 8 | Inventory, PurchaseOrder, PurchaseOrderItem, InventoryTracking, InventoryTrackingType, etc. |
sale | Orders | 2 | SaleOrder, SaleOrderItem |
finance | Financial tracking | 3 | FinanceWallet, FinanceCategory, FinanceTransaction |
payment | Webhook config | 1 | WebhookConfig |
Soft-Delete Pattern
All deletable entities use the SoftDeletableRepository from @nx/core. Instead of physical deletion, a deletedAt timestamp is set. Records can be restored with restoreById().
// Schema includes deletedAt column
deletedAt: (timestamp('deleted_at', { withTimezone: true }),
// Repository extends SoftDeletableRepository
@repository({ dataSource: PostgresCoreDataSource, model: Category })
export class CategoryRepository extends SoftDeletableRepository<
typeof Category.schema,
TCategory,
TCategoryPersist
> {});
// deleteById sets deletedAt instead of removing the row
// restoreById sets deletedAt back to null
// Pass { shouldHardDelete: true } for physical deletionEvent-Driven Architecture
The system uses two complementary messaging patterns: Redis Pub/Sub for real-time event broadcasting and BullMQ for reliable asynchronous job processing.
Redis Pub/Sub Channels
| Channel | Publisher | Subscribers | Purpose |
|---|---|---|---|
PaymentSuccess | Payment / Sale | Finance | Record income transactions on successful payment |
SellerRegistered | Identity | Commerce | Initialize merchant defaults on new seller signup |
CommerceInitialized | Commerce | Finance | Create default Cash wallet for new merchant |
BullMQ Queue Systems
Each queue type uses 3 partitions (P01, P02, P03) for load distribution.
| Package | Queue Types | Purpose |
|---|---|---|
| Commerce | 3 types | Product indexing, category sync, merchant setup |
| Finance | 2 types | PURCHASE_ORDER_RECEIVED (expense recording), transaction processing |
| Inventory | 1 type | Stock adjustment processing |
| Sale | 2 types | Order expiration scheduling, payment confirmation |
| MQ-Pay | 2 types (scheduler, confirmation) | Payment attempt expiration, IPN/webhook confirmation |
Event Flow Example
Security
Authentication and Authorization
| Mechanism | Location | Description |
|---|---|---|
| JWT Authentication | DefaultApplication.configureSecurity() | Token-based auth for API access |
| Basic Authentication | DefaultApplication.configureSecurity() | Username/password via IdentityNetworkService |
| bcrypt Hashing | @nx/identity | Password storage with bcrypt (^6.0.0) |
| Role-Based Access | All controllers | 5 fixed roles with numeric precedence |
Default Roles
| Role | Code | Scope |
|---|---|---|
| SUPER_ADMIN | 999 | Full system access |
| ADMIN | 998 | Administrative operations |
| OPERATOR | 997 | Operational management |
| ORGANIZER_OWNER | 899 | Organizer-scoped access |
| EMPLOYEE | 898 | Employee-scoped access |
Encryption
| Mechanism | Algorithm | Usage |
|---|---|---|
| WebSocket E2E | ECDH P-256 + AES-256-GCM | Signal package -- per-client key derivation |
| Credential Storage | AES-256-GCM | CryptoUtility -- payment provider secrets |
| Password Hashing | bcrypt | Identity package -- user credentials |
IGNIS Framework Patterns
Application Lifecycle
Every service extends DefaultApplication which provides authentication, CORS, Swagger, and health check configuration out of the box.
// packages/sale/src/application.ts
import {
createAppConfig,
DefaultApplication,
MigrationRepository,
PostgresCoreDataSource,
} from '@nx/core';
import { CoreBindings, IApplicationInfo } from '@venizia/ignis';
export const appConfig = createAppConfig();
export class Application extends DefaultApplication {
override getAppInfo(): IApplicationInfo {
return {
name: '@nx/sale',
version: '0.0.0',
description: 'Sale order management with payment integration',
author: { name: 'Nexpando', email: 'contact@nexpando.com' },
};
}
override getProjectRoot(): string {
const projectRoot = __dirname;
this.bind<string>({ key: CoreBindings.APPLICATION_PROJECT_ROOT }).toValue(projectRoot);
return projectRoot;
}
override configureComponents(): void {
super.configureComponents(); // HealthCheck + Swagger + Auth
this.component(RedisComponent);
this.component(QueueComponent);
}
override preConfigure(): void {
super.preConfigure();
this.dataSource(PostgresCoreDataSource);
this.repository(SaleOrderRepository);
this.service(SaleOrderService);
this.service(CheckoutService);
this.controller(SaleOrderController);
}
}Bootstrap Helpers
Application and migration entry points are standardized via @nx/core helper functions.
// packages/sale/src/index.ts -- Application entry point
import { bootstrapApplication } from '@nx/core';
import { resolve } from 'node:path';
import { appConfig, Application } from './application';
bootstrapApplication({
ApplicationClass: Application,
config: appConfig,
options: { bannerPath: resolve(process.cwd(), 'resources/banner.txt') },
});// packages/sale/src/migrations/processes/migration-process.ts
import { createMigrationProcessLoader } from '@nx/core';
export const getMigrationProcesses = createMigrationProcessLoader({
seedPaths: ['sale-0001-seed-data', 'sale-0002-seed-channels'],
importFn: path => import(`../processes/${path}.js`),
});Dependency Injection
IGNIS uses constructor injection with the @inject() decorator. Binding keys follow the namespace.ClassName convention.
// Service with repository injection
import { BaseService, BindingKeys, BindingNamespaces, inject } from '@venizia/ignis';
export class CheckoutService extends BaseService {
constructor(
@inject({
key: BindingKeys.build({
namespace: BindingNamespaces.REPOSITORY,
key: SaleOrderRepository.name,
}),
})
private readonly _saleOrderRepository: SaleOrderRepository,
@inject({
key: BindingKeys.build({
namespace: BindingNamespaces.SERVICE,
key: SaleSocketEventService.name,
}),
})
private readonly _saleSocketEventService: SaleSocketEventService,
) {
super({ scope: CheckoutService.name });
}
}The BindingKeys.build() helper produces keys like repositories.SaleOrderRepository and services.SaleSocketEventService. You can also write binding keys as plain strings:
@inject({ key: 'repositories.SaleOrderRepository' })
private readonly saleOrderRepository: SaleOrderRepository,Component Pattern
Components encapsulate cross-cutting concerns (Redis, queues, external services) and register bindings during the binding() lifecycle hook.
import { BaseApplication, BaseComponent, CoreBindings, inject, RedisHelper } from '@venizia/ignis';
export class RedisComponent extends BaseComponent {
constructor(
@inject({ key: CoreBindings.APPLICATION_INSTANCE })
protected application: BaseApplication,
) {
super({
scope: RedisComponent.name,
initDefault: { enable: true, container: application },
bindings: {},
});
}
override async binding(): Promise<void> {
const cacheRedis = new RedisHelper({ name: 'cache-redis', host: 'localhost', port: 6379 });
await cacheRedis.connect();
this.application.bind({ key: BindingKeys.APPLICATION_REDIS_CACHE }).toValue(cacheRedis);
}
}ControllerFactory (Auto CRUD)
ControllerFactory.defineCrudController() generates a full CRUD controller with OpenAPI documentation, authentication, and standard REST endpoints.
import {
ControllerFactory,
controller,
inject,
BindingKeys,
BindingNamespaces,
} from '@venizia/ignis';
const _Controller = ControllerFactory.defineCrudController({
repository: { name: FinanceWalletRepository.name },
authenticate: { strategies: ['jwt', 'basic'] },
controller: {
name: 'FinanceWalletController',
basePath: '/finance-wallets',
isStrict: { path: true, requestSchema: true },
},
entity: () => FinanceWallet,
});
@controller({ path: '/finance-wallets' })
export class FinanceWalletController extends _Controller {
constructor(
@inject({
key: BindingKeys.build({
key: FinanceWalletRepository.name,
namespace: BindingNamespaces.REPOSITORY,
}),
})
financeWalletRepository: FinanceWalletRepository,
) {
super(financeWalletRepository);
}
// Override individual methods for custom logic (role-based filtering, etc.)
@logged()
override async findById(opts: { context: TRouteContext<Env> }) {
// Custom implementation...
}
}Database Transactions
Repositories support transactional operations through the data source.
await this.repository.dataSource.withTransaction(async tx => {
await this.repository.create({ data: orderData, options: { transaction: tx } });
await this.itemRepository.create({ data: itemData, options: { transaction: tx } });
});Environment Configuration
All packages use dotenv-flow for environment management. Variables follow the APP_ENV_* prefix convention.
Environment Files
| File | Purpose | Git Tracked |
|---|---|---|
.env.example | Template with all required variables | Yes |
.env.development | Development settings | Yes |
.env.test | Test environment | Yes |
.env.local | Local overrides | No (gitignored) |
Common Environment Variables
# Application
APP_ENV_NODE_ENV=development
APP_ENV_APPLICATION_NAME=nx-sale
APP_ENV_APPLICATION_SECRET=<secret>
APP_ENV_SERVER_HOST=0.0.0.0
APP_ENV_SERVER_PORT=3003
APP_ENV_SERVER_BASE_PATH=/api
# Authentication
APP_ENV_JWT_SECRET=<jwt-secret>
APP_ENV_JWT_EXPIRES_IN=3600
# PostgreSQL
APP_ENV_POSTGRES_HOST=localhost
APP_ENV_POSTGRES_PORT=5432
APP_ENV_POSTGRES_DATABASE=bana
APP_ENV_POSTGRES_USERNAME=postgres
APP_ENV_POSTGRES_PASSWORD=password
# Redis (Cache)
APP_ENV_CACHE_REDIS_HOST=localhost
APP_ENV_CACHE_REDIS_PORT=6379
APP_ENV_CACHE_REDIS_DB=0
# Redis (BullMQ Queues)
APP_ENV_BULLMQ_REDIS_HOST=localhost
APP_ENV_BULLMQ_REDIS_PORT=6379
APP_ENV_BULLMQ_REDIS_DB=1
# Redis (Pub/Sub)
APP_ENV_PUBSUB_SUBSCRIBER_REDIS_HOST=localhost
APP_ENV_PUBSUB_PUBLISHER_REDIS_HOST=localhost
# Minio (Assets)
APP_ENV_MINIO_HOST=localhost
APP_ENV_MINIO_API_PORT=9000
APP_ENV_MINIO_ACCESS_KEY=minioadmin
APP_ENV_MINIO_SECRET_KEY=minioadmin
# Typesense (Search)
APP_ENV_TYPESENSE_API_KEY=xyz
APP_ENV_TYPESENSE_NODES=http:localhost:8108
# Snowflake ID
APP_ENV_SNOWFLAKE_WORKER_ID=1
APP_ENV_SNOWFLAKE_EPOCH_CHECKPOINT=1704067200000
# Service URLs
APP_ENV_IDENTITY_SERVICE_URL=http://localhost:3001
APP_ENV_COMMERCE_SERVICE_URL=http://localhost:3002Development Workflow
Build Commands
| Command | Description |
|---|---|
make install | Install all dependencies (bun install) |
make build | Build everything in dependency order |
make build-packages | Build only packages/* |
make build-3rd | Build only third-parties/* |
make core | Build @nx/core |
make sale | Build @nx/sale |
make commerce | Build @nx/commerce |
make identity | Build @nx/identity |
make finance | Build @nx/finance |
make payment | Build @nx/payment |
make signal | Build @nx/signal |
WARNING
Always use bun run rebuild within a package. Never run tsc directly -- path aliases (@/common, @/services) require the tsc-alias post-processing step.
Development Servers
| Command | Service | Port |
|---|---|---|
make dev-sale-order | Sale service | 3003 |
make dev-commerce | Commerce service | 3002 |
make dev-identity | Identity service | 3001 |
make dev-finance | Finance service | -- |
make dev-payment | Payment service | -- |
make dev-signal | Signal (WebSocket) | -- |
Per-Package Commands
cd packages/sale # Navigate to package
bun run rebuild # Clean + build (tsc + tsc-alias)
bun run server:dev # Start with .env.development
bun run test # Run tests (requires .env.test)
bun run test:watch # Watch mode
bun run lint:fix # ESLint + Prettier auto-fix
bun run migrate:dev # Apply database migrationsTesting
# Run all tests for a package
cd packages/sale && bun run rebuild && bun run test
# Run a single test file
bun test --env-file=.env.test dist/__tests__/path/to/file.test.jsLinting
| Command | Scope |
|---|---|
make lint | Everything |
make lint-packages | All packages |
make lint-3rd | All third-parties |
make lint-sale-order | @nx/sale only |
make lint-finance | @nx/finance only |
make lint-payment | @nx/payment only |
make lint-signal | @nx/signal only |
Git Hooks
make setup-tools # Configure git to use .githooks directory
make pre-commit # Run all linting checks (used by pre-commit hook)API Standards
Route Conventions
| Method | Pattern | Purpose |
|---|---|---|
GET | /resources | List with filtering, pagination |
GET | /resources/:id | Get single resource |
GET | /resources/count | Count matching resources |
POST | /resources | Create resource |
PUT | /resources/:id | Full update |
PATCH | /resources/:id | Partial update |
DELETE | /resources/:id | Soft delete (sets deletedAt) |
OpenAPI Documentation
Every service exposes Swagger/OpenAPI documentation:
- OpenAPI JSON:
/doc/openapi.json - Scalar Explorer:
/doc/explorer - Health Check:
/health
Quick Links
Package Documentation
| Package | Link |
|---|---|
| @nx/core | Architecture, components, configuration, database, utilities |
| @nx/identity | Authentication, authorization, user management |
| @nx/commerce | Products, pricing, merchants |
| @nx/sale | Orders, checkout, payments |
| @nx/payment | Webhook config, payment orchestration |
| @nx/signal | WebSocket, encryption, real-time messaging |
| @nx/asset | File storage, MetaLink management |
| @nx/invoice | E-invoice generation |
| API Gateway | Gateway routing and middlewares |
External References
| Resource | Link |
|---|---|
| IGNIS Framework | https://venizia-ai.github.io/ignis/ |
| Hono | https://hono.dev |
| Drizzle ORM | https://orm.drizzle.team |
| Zod | https://zod.dev |
| BullMQ | https://docs.bullmq.io |
| Typesense | https://typesense.org/docs |