SmartOps is a full-stack inventory and sales management system for small businesses such as kirana stores. It combines a React Native mobile application with a Node.js and PostgreSQL backend to support product management, stock tracking, sales recording, customer management, and barcode-assisted workflows.
The system is designed as an offline-first application. The mobile app persists data locally using WatermelonDB, allows users to continue working without connectivity, and synchronizes changes with the backend when the device reconnects.
- JWT-based business authentication with register and login flows
- Business-scoped access control enforced by backend middleware
- Multi-tenant data isolation using
business_id
- Product catalog with barcode, category, brand, unit, reorder level, and selling price
- Stock batches with quantity, batch number, expiry date, and cost price
- Stock transaction ledger for stock-in, sales, returns, and adjustments
- Alerting support for low-stock, near-expiry, and expired-stock visibility based on current batch movement history
- Read-only product APIs for inventory views, low-stock checks, and near-expiry queries
- Sales order creation with order totals, payment mode, and sale timestamp
- Line-item level tracking with product, batch, quantity, and unit price
- Customer records with purchase activity and segmentation support
- Sales and order history persisted locally for offline access
- Local-first writes in the mobile app using WatermelonDB
- Pull and push synchronization with backend timestamp checkpoints
- Business-scoped sync payloads for products, stock, sales, and customers
- Reconnect-triggered synchronization with persisted auth and business context
- Barcode lookup against business inventory
- Barcode catalog lookup from a seeded product catalog
- Fallback lookup using the OpenFoodFacts API
- Dashboard summary endpoint
- Sales analytics grouped by period
- Top product reporting
- Customer activity and segment statistics
The repository is organized as a monorepo with two primary applications:
SmartOps/: React Native mobile app built with Expobackend/: Node.js and Express API backed by PostgreSQL
The mobile app stores operational data in WatermelonDB and treats the local database as the primary source of truth for UI interactions. Screens read from local collections and write to local tables through database actions. Sync is handled by a dedicated sync engine that pulls remote changes and pushes locally created records to the backend.
The backend exposes REST endpoints for authentication, sync, barcode lookup, products, and analytics. PostgreSQL is used as the system-of-record database. Data is partitioned by business_id, and protected routes are authenticated using JWT middleware.
- A user performs an action in the mobile app.
- The app writes the change to WatermelonDB immediately.
- The UI updates from local state without requiring network access.
- When connectivity is available, the sync engine sends local changes to the backend.
- The backend persists those changes to PostgreSQL and returns any server-side updates since
lastPulledAt. - The mobile app merges the remote change set into the local database.
- React Native
- Expo
- React Navigation
- WatermelonDB
- AsyncStorage
- NetInfo
- Node.js
- Express
- JSON Web Tokens
- bcryptjs
- express-validator
- helmet
- express-rate-limit
- morgan
- PostgreSQL
- WatermelonDB with LokiJS adapter in development
- Planned SQLite-backed WatermelonDB adapter for production mobile storage
- OpenFoodFacts API for barcode lookup fallback
- Nodemon for backend development
- ESLint and Prettier for code quality
capstone/
├── README.md
├── backend/
│ ├── package.json
│ └── src/
│ ├── app.js
│ ├── server.js
│ ├── controllers/
│ │ ├── authController.js
│ │ ├── barcodeController.js
│ │ ├── productsController.js
│ │ ├── syncController.js
│ │ └── analyticsController.js
│ ├── db/
│ │ ├── pool.js
│ │ └── migrate.js
│ ├── middleware/
│ │ ├── auth.js
│ │ └── errors.js
│ ├── routes/
│ │ └── index.js
│ └── scripts/
│ ├── seedOpenFoodFacts.js
│ └── seed_demo.js
└── SmartOps/
├── App.js
├── package.json
└── src/
├── database/
│ ├── index.js
│ ├── schema.js
│ ├── actions.js
│ └── appInit.js
├── models/
│ ├── Product.js
│ ├── StockBatch.js
│ ├── StockTransaction.js
│ ├── SaleOrder.js
│ ├── SaleItem.js
│ └── Customer.js
├── screens/
├── services/
├── sync/
│ └── syncEngine.js
└── theme/
backend/src/controllers: Business logic for auth, sync, barcode, product, and analytics endpointsbackend/src/db: PostgreSQL connection pooling and SQL schema migrationbackend/src/routes: Express route definitionsSmartOps/src/database: WatermelonDB adapter, schema, initialization, and write/query helpersSmartOps/src/models: WatermelonDB model definitions for synced tablesSmartOps/src/screens: Mobile UI flows such as login, inventory, order entry, and historySmartOps/src/sync: Sync engine that integrates WatermelonDB sync with backend endpointsSmartOps/src/services: External API wrappers used by the mobile app
- Install dependencies:
cd backend
npm install-
Create a
.envfile inbackend/. -
Configure PostgreSQL and set
DATABASE_URL. -
Run database migrations:
npm run migrate- Start the backend server:
npm run devFor production:
npm startOptional: seed demo business data after registering at least one business:
npm run seed:demoTo replace the existing operational data for the target business:
npm run seed:demo -- --reset- Install dependencies:
cd SmartOps
npm install-
Verify the API base URL in
SmartOps/src/sync/syncEngine.jspoints to your backend. -
Start the Expo development server:
npm run start- Run on Android:
npm run android- Run on iOS:
npm run ios- The current development setup uses WatermelonDB with a LokiJS adapter.
- SQLite support is already included through Expo dependencies and is the intended production storage path.
- Development behavior may differ from production persistence characteristics until the SQLite adapter is adopted.
The backend requires the following environment variables:
DATABASE_URL: PostgreSQL connection stringJWT_SECRET: Secret used to sign JWTs
The following are optional but supported by the current codebase:
PORT: HTTP server port, defaults to3000NODE_ENV: Runtime environmentJWT_EXPIRES_IN: JWT expiration window, defaults to30dRATE_LIMIT_WINDOW_MS: Rate limit window in millisecondsRATE_LIMIT_MAX: Maximum requests per windowSEED_BUSINESS_ID: Explicit business id to target when runningseed:demoSEED_RESET: Set totrueto clear the target business's operational data before seeding
There is no dedicated .env file currently wired into the mobile app. The backend base URL is configured directly in:
SmartOps/src/sync/syncEngine.js
- The backend stores sync-critical timestamps as
BIGINTUnix milliseconds. - Examples include
updated_at,sale_at,txn_at,expiry_date, andlast_purchase_at. - Business data is partitioned by
business_idto support multi-tenant isolation. - Sync payloads use backend column names such as
selling_price,unit_price, andsale_at.
The sync system follows WatermelonDB's pull/push model.
- The mobile app calls
GET /sync/pull?last_pulled_at=<unix_ms>. - The backend returns all rows updated after
last_pulled_atfor the authenticated business. - The response contains a
timestampand achangesobject grouped by table.
- The mobile app calls
POST /sync/pushwith a WatermelonDB change set. - The backend processes the incoming changes inside a PostgreSQL transaction.
- Inserts use idempotent upsert or conflict-safe insert patterns depending on the table.
lastPulledAtis the client's checkpoint for the last successful pull.- It is used to request only server-side changes that occurred after the previous sync window.
- The current implementation assumes simple last-write and append-only workflows for most records.
- Orders and sale items are effectively treated as immutable after creation.
- Products and customers are updated through upsert semantics.
- There is no advanced per-field conflict resolution strategy yet.
All API routes are mounted under /api.
Registers a new business and returns a JWT plus business metadata.
Example request:
POST /api/auth/register
Content-Type: application/json
{
"name": "My Store",
"phone": "9999999999",
"password": "secret123",
"type": "kirana"
}Example response:
{
"token": "<jwt>",
"business": {
"id": "uuid",
"name": "My Store",
"phone": "9999999999",
"type": "kirana",
"created_at": "..."
}
}Authenticates an existing business and returns a JWT.
Example request:
POST /api/auth/login
Content-Type: application/json
{
"phone": "9999999999",
"password": "secret123"
}Example response:
{
"token": "<jwt>",
"business": {
"id": "uuid",
"name": "My Store",
"phone": "9999999999",
"type": "kirana"
}
}Returns server-side changes since the supplied timestamp for the authenticated business.
Example request:
GET /api/sync/pull?last_pulled_at=1710000000000
Authorization: Bearer <jwt>Example response:
{
"timestamp": 1710000100000,
"changes": {
"products": {
"created": [],
"updated": [],
"deleted": []
}
}
}Accepts a WatermelonDB change set and persists it for the authenticated business.
Example request:
POST /api/sync/push
Authorization: Bearer <jwt>
Content-Type: application/json
{
"changes": {
"products": {
"created": [],
"updated": [],
"deleted": []
}
},
"lastPulledAt": 1710000000000
}Example response:
{
"success": true,
"synced_at": 1710000200000
}Looks up a barcode in business inventory, local catalog, or OpenFoodFacts.
Example request:
GET /api/barcode/8901030893346
Authorization: Bearer <jwt>Example response:
{
"source": "catalog",
"suggestion": {
"barcode": "8901030893346",
"name": "Aashirvaad Atta 5kg",
"brand": "Aashirvaad",
"category": "Atta & Flour",
"quantity": "5kg"
}
}Returns dashboard summary data for the authenticated business.
Example response:
{
"today": {
"orders": 4,
"revenue": 1250
},
"alerts": {
"low_stock": 3,
"near_expiry": 1
},
"top_products_week": []
}Returns revenue and order totals grouped by daily, weekly, or monthly period.
Returns top-selling products for the recent reporting window.
Returns customer activity summary and customer-level segment data.
Contains mobile UI screens such as login, home dashboard, inventory, stock-in, product registration, new order, alerts, and order history.
Contains the WatermelonDB setup:
schema.js: table definitionsindex.js: database and adapter initializationactions.js: local write and query helpersappInit.js: app bootstrapping and DB warm-up
Defines WatermelonDB model classes used by the app, including field decorators and relationships for products, batches, transactions, sales, items, and customers.
Contains the sync engine responsible for:
- restoring auth and business context
- connecting WatermelonDB sync to backend endpoints
- pulling and pushing changes
- triggering sync after writes and on reconnect
Contains service wrappers for API access outside the sync engine, such as barcode lookup support.
- The current development adapter uses LokiJS for WatermelonDB persistence.
- Development persistence behavior can differ from a production SQLite-backed adapter.
- Sync requires a valid JWT and a valid
businessIdrestored on the device before synchronization will run. - The backend is the source of truth for durable persisted data; the mobile app is optimized for resilient local operation first.
- Alert counts are driven from local WatermelonDB records and batch movement history, so low-stock and expiry indicators work offline.
- The demo seed script creates products, batches, sales, customers, returns, wastage, low-stock, near-expiry, and expired-stock scenarios for UI testing and demos.
- Migrate WatermelonDB storage from LokiJS development setup to SQLite for production
- Introduce stronger conflict resolution beyond current upsert and immutable-record assumptions
- Improve background sync scheduling and retry behavior
- Move mobile API configuration to environment-based app config
- Add sync observability and operational diagnostics for production deployments
- Build a web dashboard for business owners with analytics, operational summaries, and reporting views
- Add richer analytics modules such as trend charts, payment breakdowns, top products, and inventory health metrics
- Expand CRM capabilities with customer profiles, purchase history, segment-based views, and retention insights
- Add customer engagement workflows such as follow-up reminders, loyalty tracking, and repeat-customer reporting
- Support admin-oriented inventory workflows such as reorder recommendations, supplier tracking, and stock adjustment audit trails