Skip to content

E-Procurement v2

Overview

e-procurement-v2 is the primary backend service behind Vendease's food-procurement platform. It is a Node.js/TypeScript application built on Express that exposes a public REST API used by company buyers, guest checkout flows, foragers, account managers, and external partner integrations. The repository describes itself as the platform "taking food procurement automation to the moon" and is published from this codebase as version 2.6.1.

The service holds the core business domain for procurement: companies, branches, users and roles, products and pricing, carts and orders, invoices and payments, deliveries, claims, credit and pay-later, KYC, and audit trails. Each of these is exposed through a route module under server/routes/ and backed by its own service-and-repository pair under server/services/ and server/repositories/. The repository's own README describes the layout in this terms and points to a Confluence space, a dbdocs.io schema, and a Postman collection as the canonical references.

The service is the system of record for procurement-side business state. It owns the Postgres schema (managed with Sequelize migrations and a parallel set of raw-SQL helper scripts), publishes domain events over NATS Streaming for sibling services to consume, and integrates outward to payment providers, credit-bureau services, document/PDF generation, push notifications, geo lookups, and SendGrid for email. Production traffic is served from an EC2 fleet behind AWS CodeDeploy, with New Relic and OpenTelemetry instrumentation enabled.

Architecture & Technology

The runtime is Node.js 14 (pinned in package.json#engines), with TypeScript 4.x compiled to dist/server for production and ts-node plus nodemon used for local development. The HTTP layer is Express 4, hardened with Helmet, rate-limited by express-rate-limit plus rate-limit-redis, and validated with express-validator. Cross-cutting concerns are wired up in server/app.ts, which mounts CORS, JSON parsing, Morgan logging, Swagger UI (off in production), and routes the public API at /api/v2 and a separate partner-facing API at /api/v1/external.

Persistence uses Postgres via Sequelize 6, with around 320 migrations under server/database/migrations/ and roughly a hundred Sequelize models under server/database/models/. A second access path lives in server/database/scripts/ and server/raw-query/: hand-written SQL functions, triggers, and stored procedures executed at startup by helper/DbScriptRunner. These back reporting endpoints (account-manager dashboards, product-price trends, invoice payment statistics, warehouse invoice listings) where ORM-shaped queries would be impractical. A custom.knexfile is also declared on the package for tooling that prefers Knex.

Asynchronous work runs on three pieces of infrastructure that the README requires to be installed locally: RabbitMQ (via amqplib), Redis (via ioredis, also used as a Bull queue backend and a rate-limiter store), and NATS Streaming (via node-nats-streaming). NATS is the event bus: server/events/ is split into publishers/ (company, invoice, payment, product, user) and listeners/ (product, warehouse), with shared queue-group conventions and emitters that other Vendease services subscribe to. Bull queues plus node-cron cover scheduled and deferred work.

Real-time features run over socket.io 4, set up in server/websocket-setup.ts and exposed alongside the HTTP server in server/index.ts. Push notifications go through firebase-admin, transactional email through @sendgrid/mail, file uploads through multer plus multer-sharp-s3 to AWS S3, and PDFs through pdf-creator-node. Geo-IP enrichment uses geoip-lite; XML and Excel ingestion uses xml-js and xlsx. A shared internal package, vendease-common, is consumed as an npm dependency and provides shared types and helpers across Vendease services.

Observability is layered: Winston with daily-rotated files for application logs, Morgan for request logs, New Relic 9 (newrelic.js at the repo root) for APM, and an OpenTelemetry SDK with auto-instrumentations bootstrapped in server/tracing.ts. Authentication uses bcrypt-hashed credentials and JSON Web Tokens, with role-based access control enforced inside services and middleware.

How It Fits With Other Systems

This service is one node in a wider mesh of Vendease backends, and three integration surfaces matter most.

The first is the inbound public API at /api/v2, which the buyer-facing web and mobile clients consume directly. A separate /api/v1/external mount under server/routes/integrations/ is reserved for partner traffic and has its own route file. The Postman collection at the repo root and the Swagger UI at /api-docs (non-production only) document the surface.

The second is the event bus. NATS Streaming carries domain events between this service and the rest of the Vendease platform. Publishers under server/events/publishers/ emit on company, user, product, invoice, and payment activity; listeners under server/events/listeners/ react to product and warehouse events from other services. The server/Integrations/ directory holds direct HTTP integrations with sibling internal services, while server/external-integrations/ covers third-party APIs such as workforce systems.

The third is the relationship with the inventory backend. The repository contains an IMS-MIGRATION.md file noting that the warehouse table from the IMS staging database (IMS-STAGING-API.warehouse) has been migrated into this service's Vendease.tbl_warehouses. The procurement service is the canonical home for warehouse records and treats the inventory service as a downstream consumer of warehouse data. Conversely, the inventory backend's eprocurement.service.ts calls back into this API using a shared app-secret-key header to keep its branch, business, country, state, and city tables aligned. The two systems are tightly coupled around master-data sync.

Outbound third-party integrations include AWS S3 (file storage), SendGrid (email), Firebase Cloud Messaging (push), and a payment-provider layer under server/services/payments/ that contains gateway adapters, direct-debit and transfer flows, wallet handling, fraud checks, and webhook receivers. Credit assessment is handled through server/services/credit-risk/ and server/services/finance/, which include adapters such as OkraService, RemitaService, CreditBureauService, and ProvidusCardService.

Repository Layout

The application code lives entirely under server/, with subdirectories acting as horizontal slices of the architecture rather than feature folders. The README enumerates the conventions, summarised here:

  • server/index.ts and server/app.ts bootstrap the Express server, attach Helmet, mount routes, connect NATS, and start the HTTP plus socket listeners.
  • server/routes/ contains one file per domain area (orders, products, invoices, payments, claims, etc.) plus an integrations/ subdirectory for partner endpoints. Routes wire HTTP verbs to controllers.
  • server/controllers/ and server/services/ split request handling from business logic. Services group domain logic into folders such as payments/, finance/, credit-risk/, dashboard-service/, notifications/, notifiers/, pay-later/, reports/, queue/, search/, and internal-scripts/.
  • server/repositories/ holds the data-access layer over Sequelize models.
  • server/database/ owns persistence: models/ (~97 Sequelize models), migrations/ (~321 timestamped files), seeders/, hook/ (Sequelize lifecycle hooks), config/ (including the Knex file), and scripts/ (raw SQL functions, triggers, and DB-level helpers run at startup).
  • server/raw-query/ contains hand-written SQL strings used for reporting and complex joins.
  • server/events/ carries the NATS layer with publishers/, listeners/, emitters/, and a shared queue-group.ts.
  • server/Integrations/ connects to other internal Vendease services; server/external-integrations/ connects to third parties.
  • server/middleware/ covers auth, validation, rate limiting, geo-IP, and platform-header parsing; server/notifications/ covers push and similar channels.
  • server/helper/, server/exceptions/, server/constants/, server/filesystem/, and server/resources/ hold cross-cutting utilities, error classes, enums, disk caching, and static resources.

Outside server/ the repo carries tests/ (Jest 26 plus Supertest, with a setup.ts consumed by jest.preset), seeders/, infra/eproc-chart/ (Helm chart) plus infra/Jenkinsfile, scripts/ (CodeDeploy lifecycle hooks application_start.sh, application_stop.sh, before_install.sh), root-level Jenkinsfile-ci and Jenkinsfile-release-ci, an appspec.yml for AWS CodeDeploy, a Procfile, a Dockerfile plus docker-compose.yml, JSDoc configuration in jsdoc.json, newrelic.js, sonar-project.properties, and a Staging_Environment.postman_environment.json paired with Vendease_Procurement.postman_collection.json.

Running, Building & Deploying

A local run starts with npm install, then npm run start:dev, which uses nodemon to watch server/**/*.ts and re-execute server/index.ts through ts-node. The README expects three local services to be running first: RabbitMQ, Redis, and NATS Streaming Server, all installed via Homebrew. Postgres must also be available; the README walks through psql, then create database vendease;, then npm run setup:db (which runs db:migrate then db:seed). The db:scripts task compiles and runs helper/DbScriptRunner against dist/ to install raw-SQL functions and triggers. A docker-compose.yml at the root composes the API container, Postgres, Redis, and a SendGrid mock for end-to-end local runs.

Production builds run npm run prod:build, which cleans dist/, runs tsc -p tsconfig.json, and then node dist/server. npm run db:migrate:prod runs Sequelize migrations against $POSTGRES_URL. The Procfile declares web: npm start, used by Heroku-style runners.

CI/CD is split across two Jenkins pipelines. Jenkinsfile-ci handles continuous integration on feature branches, while Jenkinsfile-release-ci packages a tagged release: it reads the latest git tag, builds the project, tar-gzips the output, uploads ${release_tag}.tar.gz to the ci-cd-store S3 bucket under e-procurement-release/, and appends the release filename to eproc-be-release.txt. AWS CodeDeploy then picks up the bundle and runs the lifecycle hooks declared in appspec.yml (scripts/before_install.sh, scripts/application_start.sh, scripts/application_stop.sh) on EC2 instances under /home/ec2-user/e-procurement. A Helm chart under infra/eproc-chart/ is also present for Kubernetes deployments.

Branch conventions per the README: the dev branch is the integration target for staging, and main is what runs in production. Tests use Jest with ts-jest against a dedicated test database; npm test runs migrations and starts Jest in watch mode, npm run test:ci runs the suite with raised heap limits and --forceExit for CI pipelines, and npm run coverage:ci adds coverage. Code coverage is tracked via Codecov (badge in the README).

Operational Notes & Risks

The Node 14 runtime is past end of life. The pinned engines.node of 14.18.1 and the node:14-alpine base image in the Compose definition mean the runtime no longer receives security patches, so an upgrade is overdue.

The data layer mixes Sequelize migrations with raw-SQL scripts run at startup by helper/DbScriptRunner. Triggers, stored procedures, and views live in server/database/scripts/ and are applied every boot. This means schema state is partially version-controlled by Sequelize and partially by idempotent SQL strings; drift between the two is possible if either side is edited without updating the other. The same caution applies to the parallel raw-query files in server/raw-query/, which back several reporting endpoints and are coupled to specific column shapes.

The service depends on RabbitMQ, Redis, and NATS Streaming all being reachable. NATS Streaming has been deprecated upstream in favour of NATS JetStream (which the procurement-integration backend already uses), so any cross-service event coordination work needs to plan for a migration of this side too.

Three integration surfaces are particularly load-bearing and should be monitored together: the warehouse master-data sync with the inventory backend (described in IMS-MIGRATION.md and re-validated by the eprocurement.service.ts consumer there), the NATS event publishers for company/user/product/invoice/payment, and the payment gateway and webhook handlers under server/services/payments/. Failures in any of these silently de-sync downstream consumers.

The Sequelize model count (~97) and migration count (~321) make the schema large enough that the db:hydrate flow (build, migrate, seed, scripts) is the only safe way to reproduce a clean database. Test runs follow the same flow with NODE_ENV=test and a separate test database whose credentials live in tests/setup.ts. Heap pressure on the test suite is real enough that the jest:heap and test:ci scripts pass --max_old_space_size=10096 explicitly.

Static configuration lives in server/config/ and is keyed off environment variables loaded by dotenv; the npm run syncenv script invokes envcheck.js to surface drift between expected and present variables. Several keys (database, S3, SendGrid, Firebase, NATS, Redis, payment providers) must be present at boot for the relevant subsystems to initialise.

Finally, the production API exposes both a versioned public surface at /api/v2 and a partner surface at /api/v1/external, with platform identification via the platform request header; ad-hoc changes to either path or to the platform whitelist in server/constants/SignupTypes.ts will reject otherwise-valid traffic.