Inventory Backend¶
Overview¶
Inventory-backend is the server-side implementation of Vendease's Inventory Management System (IMS). The repository's package metadata names the service ims-service, and its runtime is a TypeScript Express application that compiles to dist/src/app/index.js and serves the IMS web client (https://ims-staging.vendease.com in the staging environment).
The service holds the operational data behind warehouse and store inventory: branches and warehouses, stores and store racks, units of measure, product catalogue records, product locations, product batches, product flows, product orders, product requests, product returns, product thresholds, cycle counts, and order levels. Around it sit the supporting capabilities you would expect for an inventory system: user and branch-user management, business and analytics, audit trails, notifications, file uploads, sheet imports/exports, and a procurement integration that synchronises master data from the e-procurement service.
It is a focused service with a single clear responsibility: track stock and product movement at the warehouse and store level. It is not the system of record for procurement-side data (companies, branches, users, locations) — those are owned by e-procurement-v2 and pulled into IMS through the eprocurement service module. IMS is the system of record for everything that happens to product inventory once it has been received.
Architecture & Technology¶
The service is a Node.js 14 application written in TypeScript. The HTTP layer is Express 4, with inversify and inversify-express-utils providing a container-based dependency-injection model and decorator-driven controllers. The bootstrap path is short: src/app/index.ts loads dotenv, runs database migrations through DbSetup (which shells out to yarn mg:up), starts cron jobs through CronService, and listens on the configured port via appServer from src/app/server.ts. server.ts builds an InversifyExpressServer rooted at /api/v1 and lets src/app/app.ts mount middleware (CORS, JSON/URL-encoded body parsing with a 250 MB limit, Morgan logging, request-IP capture, Handlebars templating).
Persistence is Postgres via Knex 0.95 plus Objection 3.0 as a model layer on top. knexfile.ts declares all four environments (development, staging, production, test) using the same Postgres connection driven by Env.getAll(), with migrations stored in ims_service_migrations and physically located under src/database/migrations/ (around 36 migrations) and seeds under src/database/seeds/. The dedicated db.setup.ts file at the repo root runs yarn mg:up on boot. Test runs use a parallel database on port 5433 (POSTGRES_PORT=5433 knex migrate:latest --env=test) defined in docker-compose.yaml as db-test.
Caching and queueing run on Redis (ioredis 4) and Bull 4. AWS S3 (via @aws-sdk/client-s3) backs file uploads, with multer and multer-s3 handling the request side; transactional email goes through SendGrid (@sendgrid/mail). Excel handling uses exceljs and xlsx. JSON Web Tokens secure the API; passwords are bcrypt-hashed; Helmet provides standard HTTP hardening. Cron jobs are scheduled with node-cron and live under src/app/services/cron/. Logging is Winston with daily rotation, plus Morgan for request logs and New Relic 9 for APM (the start script sets NEW_RELIC_HOME=dist/).
Domain code is feature-sliced. src/app/controllers/ and src/app/services/ mirror each other with one folder per feature: warehouses, stores, store racks, branches, business, products, product catalogue, product categories, product orders, product requests, product returns, product flow, product batches, product thresholds, products UOMs, UOMs, cycle counts, order levels, locations, users, analytics, audit trails, notifications, email, sheets, eprocurement, external API, AWS, cron, eventEmitter, helper. src/app/repositories/ and src/app/models/ hold the data layer; src/app/entities/ carries domain entity definitions; src/app/middlewares/ contains the cross-cutting filters; src/app/templates/ holds Handlebars views (copied into dist/ by the build script).
Configuration is centralised in src/app/configs/: env.config.ts is the single source of truth for environment variables, joined with Postgres (db.config.ts), Redis (redis.config.ts), Inversify bindings (inversify.config.ts), error messages (message.config.ts), and DI tokens (type.config.ts). The env.sample at the repo root documents the expected variable surface: Postgres connection, S3 bucket and credentials, SendGrid key, Redis cluster, Twilio (placeholders), JWT secret, the e-procurement base URL, the IMS front-end URL, and New Relic settings.
How It Fits With Other Systems¶
IMS is the inventory side of a two-service split with e-procurement-v2. The relationship is captured in two places. From the procurement side, IMS-MIGRATION.md records that the warehouse table was migrated from the IMS staging database (IMS-STAGING-API.warehouse) into the procurement database (Vendease.tbl_warehouses), making procurement the canonical owner of warehouse master data. From the IMS side, src/app/services/eprocurement/eprocurement.service.ts is a long-running synchronisation module that pulls business, branch, user, country, state, and city records from the procurement API (using the app-secret-key header) into local IMS tables via the BusinessRepository, BranchRepository, BranchUserRepository, UserRepository, CountryRepository, StateRepository, and CityRepository. The procurement base URL is configured via E_PROCUREMENT_URL (https://staging-api.vendease.com/api/v2 in the sample) and the shared secret via APP_SECRET_KEY.
A second outbound integration lives under src/app/services/external_api/ as EProcurementAPIService (eapi.service.ts), which wraps the procurement HTTP API for ad-hoc calls including user creation. AWS S3 is used both for product images and for sheet exports under src/app/services/sheets/. Email goes out through SendGrid with templates rendered by ejs, and there is a notification surface under src/app/services/notifications/ plus src/app/services/email/. Twilio fields are present in env.sample but unset, suggesting an SMS/voice channel is wired but not enabled in this environment.
The IMS web frontend at https://ims-staging.vendease.com consumes this service over the /api/v1 path declared in server.ts. There is no separate partner-facing API mount; all controllers sit behind the same root path.
Repository Layout¶
The codebase is rooted at src/, split into the application code under src/app/ and the database layer under src/database/. Top-level files at the repo root cover configuration and deployment.
src/app/index.ts— boots dotenv, runs migrations viaDbSetup, starts cron jobs, and starts the HTTP listener.src/app/server.ts— wraps Express withInversifyExpressServerrooted at/api/v1.src/app/app.ts— mounts middleware: CORS, JSON, URL-encoded, Morgan, Handlebars, request-IP capture.src/app/configs/— environment, Inversify container bindings, type identifiers, database, Redis, and message configuration.src/app/controllers/— one file per feature area (warehouses, stores, products, product orders, product requests, etc.). Each controller is an Inversify-decorated class.src/app/services/— feature services that mirror the controller layout, plus cross-cutting services (aws/,cache/,cron/,email/,eprocurement/,eventEmitter/,excel_parser/,external_api/,helper/,notifications/,sheets/).src/app/repositories/andsrc/app/models/— data-access layer over Knex/Objection; one repository plus one model per table-aligned feature.src/app/entities/— domain entity definitions used as the shared shape between controllers, services, and repositories.src/app/middlewares/— auth, validation, and other request-pipeline filters.src/app/templates/— Handlebars templates for emails and other rendered content; copied intodist/src/app/templates/by thebuildscript.src/app/utils/— shared utilities: HTTP helpers, JWT, logger, request util, error servers, slug generation, validation, and stream helpers.src/app/constants/,src/app/__test_fixtures__/,src/app/__mocks__/— enums, fixtures, and mocks consumed by Jest specs.src/database/— Knex migrations (migrations/), seeds (seeds/), and supportingschema.ts,table.ts, andindex.tsdefinitions.db.setup.ts(repo root) — used by the bootstrap path to runyarn mg:upautomatically.knexfile.ts(repo root) — Knex config for development, staging, production, and test, all driven byEnv.getAll().env.sample— documented surface of expected environment variables.docker-compose.yaml— composeslocalstack(S3 mock),redis,sendgrid(mock),dbanddb-testPostgres instances on ports 5432/5433, and theapicontainer.Dockerfile—node:14-alpinebase, installs Yarn and Knex globally, copies in the project, runsyarn tsc, and startsnode dist/src/app/index.json port 9000.appspec.yml,Procfile,infra/(Jenkinsfile-dev, Jenkinsfile-qa) — deployment configuration for AWS CodeDeploy and Jenkins-based CI.newrelic.ts,sonar-project.properties,jest.config.ts— observability, code quality, and test configuration.public/,uploads/,logs/,dist/— runtime directories.@types/— local TypeScript declaration overrides.
Running, Building & Deploying¶
Local setup is documented in the README. After git clone, cd Inventory-backend, copy env.sample to .env and fill it in, then yarn install. Two terminals are needed for development: yarn tsc -w keeps the TypeScript build current under dist/, and yarn start:dev runs nodemon against dist/src/app/index.js with the New Relic home set to dist/. Database migrations are managed with the Knex CLI: yarn mg:up runs knex migrate:latest, yarn mg:down rolls everything back, yarn mg:make <name> scaffolds a new migration, and the same shape applies to seeds (yarn seed:run, yarn seed:make).
For tests, the README walks through yarn start:services to bring up the docker-compose stack (postgres db and db-test, redis, sendgrid mock), then yarn mg:up-test to migrate the test database (which lives on POSTGRES_PORT=5433), then yarn test. The Jest config is in jest.config.ts; the test script runs Jest with --detectOpenHandles --maxWorkers=50% and NODE_ENV=test. A coverage variant (yarn test:coverage) and a SonarQube integration (sonar-project.properties) round this out.
Production builds run yarn build, which removes dist/, runs tsc, and copies the Handlebars templates from src/app/templates/ into dist/src/app/templates/. The start script then runs node dist/src/app/index.js with New Relic enabled. The Procfile declares web: yarn start for Heroku-style runners, and the Dockerfile produces an image based on node:14-alpine exposing port 9000. The appspec.yml plus infra/Jenkinsfile-dev and infra/Jenkinsfile-qa cover AWS CodeDeploy and Jenkins for the deployed environments. There is no Helm chart in this repo; deployment targets EC2.
The development docker-compose composition wires everything together: the API container builds from the Dockerfile, depends on db, redis, and the SendGrid mock, mounts the project as a volume, and exposes port 5000 (development). Localstack is included for S3 emulation. Database credentials default to postgres / ComplexP@ss and POSTGRES_DB=ims-service to match the values in env.sample.
Operational Notes & Risks¶
The Node 14 runtime has reached end of life. The pinned engines.node = "14" and the node:14-alpine base image both need to be replaced as part of any platform refresh.
Several library versions trail current major releases by enough that an upgrade has compatibility implications: ioredis is on the 4.x line, multer is on 1.4.4, axios is on 0.21.1, bull is on 4.8, knex is pinned to 0.95.14, objection is on 3.0.0, and pg is on 8.5.1. None of these are immediately broken, but Knex and Objection in particular have moved on materially since these versions and migrations between major versions can shake out subtle behavioural changes in the model layer.
The procurement integration deserves attention: EProcurementService reaches into the procurement API as a polling/sync mechanism using a shared app-secret-key. Any change to the procurement API contract or to the auth header format will silently break IMS's master-data sync, with downstream effects on every controller that filters by branch, business, or user. The env.sample ships an example APP_SECRET_KEY value (which is also a real-looking secret); the live value lives in deployed environments only.
env.sample itself contains live-looking secret values for AWS, SendGrid, and the Redis cluster (Redis Labs cluster URL, password, port). Whether these are real or expired credentials, the file should be reviewed before publishing this repo more widely; placeholder values would be safer.
The DbSetup.run() call in src/app/index.ts shells out to yarn mg:up at every boot. This means the application container needs Yarn and Knex available at runtime (the Dockerfile installs both globally), and any migration failure will manifest as an application startup failure rather than a separate, observable migration step. For environments where deployments and migrations should be decoupled, this is worth changing.
CronService.runJobs() runs in-process at startup. There is no leader election, so deploying more than one replica of this service will cause every cron-triggered job to run on every replica at the schedule time. Either the cron jobs need to be made idempotent, or the deployment needs to keep this service to a single replica, or the cron work needs to be lifted out of the app entirely.
The 250 MB Express body limit set in src/app/app.ts is unusually permissive, presumably to support sheet uploads. Combined with the AWS S3 path for the same uploads, it would be worth checking whether requests of that size actually need to be parsed in-memory or whether they can be streamed.
Finally, the request-router is rooted at /api/v1. Documentation or clients that assume an unversioned root will not find the service. The IMS frontend URL (FRONT_END_URL) is environment-configured for CORS and link-generation purposes; the value in env.sample points to the staging frontend.