Procurement Integration Backend¶
Overview¶
procurement-integration-backend is the NestJS service behind Vendease's procurement and marketplace platform. The repository's README describes it as the "procurement & marketplace backend"; the bootstrap log in src/main.ts titles it the "Vendease Procurement Service" and exposes it as version 1.0 of the API at the /v1 prefix. A live Swagger build is published at https://procurement-be-dev.vendease.com/v1/documentation.
Despite the "integration" in its name, the service is broader than a simple integration shim. It owns its own Postgres database (the database.dbml source file describes a project named Procurement_Integration with roughly forty tables), and it is the home of the procurement domain in NestJS form: users, branches, businesses (companies and company categories), purchase orders, recurring orders, invoices, payments, payment collections, claims, shipments, deliveries, GRNs, drafts, carts, products and categories, locations, taxes, credit facilities, pay-later, financing, referrals, FAQs, warehouses, providers, and reports. Around that sit several genuine integration surfaces: a WhatsApp webhook handler powered by Twilio, a Vehicle Information Service (VIS) integration for vehicle lookups, NATS JetStream for cross-service eventing, Redis-backed caches and Bull queues, and Socket.IO for chat.
The service appears to be a newer NestJS-based rewrite of the procurement domain that runs alongside the older e-procurement-v2 Express service. It uses TypeORM rather than Sequelize, JetStream rather than NATS Streaming, and a feature-modular structure rather than the layered controllers/services/repositories split that the older service uses.
Architecture & Technology¶
The runtime is Node.js 18 (the Dockerfile uses node:18.15-slim) and NestJS 10, with TypeScript 5.1. The HTTP server is bootstrapped in src/main.ts, which constructs the AppModule, hardens it with Helmet (in non-production environments), enables CORS against a vendease.com regex whitelist (with a localhost allowance on dev/staging/production), enables compression, registers global pipes (TrimWhitespacePipe and a ValidationPipe that uses class-validator plus an i18n exception factory), parses cookies, sets the global prefix to /v1, mounts Swagger UI at /v1/documentation when enabled, connects a JetStream microservice strategy, starts all microservices, and then begins listening on the configured port.
Persistence is Postgres via TypeORM 0.3 (@nestjs/typeorm), configured under src/common/utils/database/. The AppModule wires TypeOrmModule.forRootAsync with a SnakeNamingStrategy, sets the entity glob to dist/**/*.entity.js, and points migrations at dist/migrations/*{.ts,.js}. The migrations directory under src/migrations/ carries around nine migration files; database.dbml is the dbdiagram-style schema source (~1,178 lines) and serves as the canonical data-model reference. A pre-flight script under src/scripts/db-script-runner.ts runs as part of start:dev and start:prod (npm run db:scripts*), connects DB event listeners after bootstrap when NODE_ENV !== 'development' (via connectDbEventListeners), and supports legacy data migration through src/scripts/migration-eproc.ts and src/scripts/product-cleanup.ts.
Asynchronous communication uses NATS JetStream through @nestjs-plugins/nestjs-nats-jetstream-transport, wired by src/module/jetstream/. Bull queues sit on Redis via @nestjs/bull configured against the same Redis instance used for caching (RedisCacheModule registered globally in AppModule). Real-time chat uses @nestjs/platform-socket.io and @nestjs/websockets with a ChatGateway registered as a provider on the AppModule. Scheduled work uses @nestjs/schedule (ScheduleModule.forRoot()); cron files are colocated inside the relevant feature modules (src/cart/cron/send-cart-abandoned-notification.ts, src/invoice/cron/overdue-reminder.ts, src/recurring-order/cron/process-recurrent-purchase-orders.ts).
Authentication uses JWTs through @nestjs/jwt, configured globally in AppModule from a SecurityConfig provider. An AuthMiddleware and a LoggerMiddleware are applied to all routes via MiddlewareConsumer. Tenant scoping is supported through an x-tenant-id header documented in the Swagger global parameters; an accept-language header drives nestjs-i18n translation through a global I18nInterceptor and an AllExceptionsFilter that translates error messages.
Cross-cutting integrations include AWS S3 (aws-sdk), Google Maps (@googlemaps/google-maps-services-js), Twilio (used for WhatsApp messaging in the webhook module), pdfmake for PDF generation, sharp for image processing, nanoid for short-id generation, moment and ms for time, and xlsx for spreadsheet handling. New Relic is wired through newrelic.ts. core-js is loaded for polyfills.
The repository is set up as an Nx monorepo (nx.json, project.json, turbo.json) even though only one project lives in this folder; nx.json extends nx/presets/npm.json, references core-services/node_modules/nx/schemas/nx-schema.json outside the repo, and configures the Nrwl Cloud runner for caching dev, build, lint, test, and e2e tasks. The Nest CLI configuration in nest-cli.json sets deleteOutDir: true and asks the build to copy i18n bundles, HTML, CSS, PNG, and XLSX assets into dist/.
How It Fits With Other Systems¶
The service's external surfaces fall into four groups.
The first is the public REST API at /v1, with Swagger UI at /v1/documentation (also published to procurement-be-dev.vendease.com). All feature modules registered in AppModule contribute controllers under this prefix. Multi-tenant traffic is identified through the x-tenant-id header.
The second is the JetStream microservice transport. getJetstreamStrategy() (under src/module/jetstream/) provides the strategy connected by app.connectMicroservice plus app.startAllMicroservices(). This is a forward-looking choice: the older Vendease procurement service uses NATS Streaming, while this one uses JetStream, so the two cannot interoperate on the same subject space without a bridge.
The third is the WhatsApp webhook integration in src/webhook/. The WebhookModule ties together a WhatsAppWebhookService, a WhatsAppWebhookActionService for command dispatch, a WhatsAppWebhookAuthService for inbound auth, a generic WebhookRequestHandler, and a TwilioService adapter. It joins purchase orders, credit facilities, branches, product locations, and users through TypeOrmModule.forFeature so WhatsApp users can drive procurement actions conversationally.
The fourth is src/integrations/, which is narrower in scope than the name suggests. The IntegrationsModule currently exports only a VehicleService and a supporting VisHttpServiceService for the Vehicle Information Service integration. Future external API integrations are likely intended to live here.
Outbound, the service talks to S3 for storage, Google Maps for geocoding/directions, Twilio for WhatsApp messaging, the VIS partner for vehicle lookups, and a payment-API surface configured under src/payment/ (with sub-folders for controllers/, services/, enums/, interfaces/, dtos/, and template/). src/notification/ covers email, push, SMS, Google Chat, and a multi-channel notification interface.
Inbound from other Vendease services, the JetStream subscriber side is not enumerated in AppModule directly but lives inside feature modules via @EventPattern decorators (the conventional NestJS approach). The AuthMiddleware and LoggerMiddleware apply to all routes including any controllers that double as event handlers.
Repository Layout¶
The repo is rooted at src/, with one folder per NestJS feature module. Each feature follows the same shape: a *.module.ts, one or more *.controller.ts, *.service.ts, *.entity.ts, a dtos/ subfolder, optionally a cron/ subfolder, and tests/ next to the implementation files. Configuration sits in src/config/, common utilities in src/common/, and shared NestJS modules in src/module/.
src/main.ts— bootstraps the NestJS app: CORS, Helmet, validation, Swagger, JetStream microservice, listener.src/app.module.ts— assembles the module graph: TypeORM, JWT, Bull, RedisCache, JetStream, ScheduleModule, i18n, all feature modules, plus the globalI18nInterceptorandAllExceptionsFilter.src/config/— Nest config providers loaded byConfigModule.forRoot:app.ts,aws.ts,database.ts,firebase.ts,googlemap.ts,i18n.ts,identityPass.ts,jetstream.ts,paymentApi.ts,redis.ts,security.ts,twilio.ts,vis.ts, withinterfaces.tsfor the typed config shape andmock.tsfor tests.src/common/— cross-cutting code:decorators/,exceptions/,guards/,interceptors/,middlewares/,pipes/,pagination/,translation/,helpers/i18n/,types/,utils/, abstract DTO/entity bases, the globalenum.ts, and date/math helpers.src/module/— shared NestJS infrastructure modules:csv/,hash/,jetstream/,redis-cache/.src/migrations/— TypeORM migrations.src/scripts/—db-events/,db-script-runner.ts,migration-eproc.ts,product-cleanup.ts. The first two are part of the boot path; the others are operational scripts.src/integrations/— currently the VIS vehicle integration plus itsIntegrationsModule.src/webhook/— WhatsApp webhook handler powered by Twilio, joined to procurement domain entities.- Feature modules covering the procurement and marketplace domain:
admin/,audit-trail/,branch/,branch-user/,cache/,cart/,cart-item/,chat/,city/,claim/,claim-message/,company/,company-category/,country/,country-tax/,credit-facility/,delivery-fee/,draft/,faq/,favourite-product/,file-system/,file-upload/,financing-bank/,grn/,guest/,invoice/,invoice-item/,location/,miscellaneous/,notification/,pay-later/,payment/,payment-collection/,pickup-source/,product/,product-category/,product-location/,product-tax/,providers/,purchase-order/,purchase-order-item/,purchase-order-miscellaneous/,reason/,recurring-order/,recurring-order-item/,referral-discount/,reports/,shipment/,shipment-address/,state/,uom/,url-shortener/,user/,utils/,warehouse/. src/i18n/— translation bundles (copied intodist/as runtime assets).src/generated/— generated i18n type artefacts.
At the repo root: nest-cli.json, nx.json, project.json, turbo.json, tsconfig.json and tsconfig.build.json, database.dbml, dbml-error.log, dbmls/database.dbml, sqlfile.sql and filtered_sqlfile.sql (raw SQL exports), docker-compose.yml (NATS, Postgres, Redis), Dockerfile, newrelic.ts, infra/Jenkinsfile plus infra/procurement-be-chart/ (Helm chart with values-dev.yaml, values-staging.yaml, values-prod.yaml), sonar-project.properties, upload-products.json, and the standard NestJS test/ plus coverage/ directories.
Running, Building & Deploying¶
Local startup follows the NestJS conventions documented in the README. After npm install, an .env.${environment} file is required (e.g. .env.development). npm run start:dev runs npm run db:scripts:dev — which builds the project and runs node ./dist/scripts/db-script-runner against the development database — and then nest start --watch. The companion npm run start:services brings up the docker-compose stack: NATS JetStream (nats:2.9-alpine3.17 on 4222/6222/8222), Postgres 14 on host port 5431, and Redis 7 on 6379. Database credentials in compose come from DATABASE_USER, DATABASE_PASSWORD, and DATABASE_NAME environment variables.
For production, npm run build runs nest build (which clears dist/ and copies i18n bundles, HTML, CSS, PNG, and XLSX assets per nest-cli.json). npm run start:prod first runs db:scripts:prod against the live database, then node dist/main. The Dockerfile uses node:18.15-slim, runs npm ci, copies the source in, runs npm run build, exposes port 3000, and starts via npm run start. A stop script (pkill -f 'node dist/main.js') is provided for graceful manual shutdown.
Database management uses TypeORM rather than Knex. npm run typeorm builds first, then runs npx typeorm -d dist/common/utils/database/ormconfig.js against the compiled config. npm run migration:run is a thin wrapper for typeorm migration:run.
Tests use Jest with cross-env NODE_ENV=test. The Jest config in package.json sets aggressive coverage thresholds (branches 91, functions 99.5, lines 100, statements 99), excludes a long list of paths from coverage (DTOs, mocks, entities, interfaces, modules, controllers, configs, decorators, interceptors, exceptions, types, i18n, translation, certain cron files, newrelic.ts, main.ts, migrations, scripts), and runs from rootDir: src with testRegex matching *.spec.ts. End-to-end tests are configured via test/jest-e2e.json.
CI/CD uses Jenkins (infra/Jenkinsfile) and a Helm chart (infra/procurement-be-chart/) with per-environment values files for dev, staging, and prod. SonarQube reporting is wired through sonar-project.properties. The Nx Cloud token in nx.json enables remote build cache.
Operational Notes & Risks¶
The Nx setup looks partial: nx.json extends the npm preset and references core-services/node_modules/nx/schemas/nx-schema.json, a path that lives outside this repo. The repo is structured as a single project (project.json is at the root, no apps/ or libs/ directory exists), so the Nx wiring is more aspirational than load-bearing today. The Nx Cloud access token is also committed in nx.json; if the token grants write access, it should be rotated and stored in CI secrets instead.
The Jest coverage thresholds (functions 99.5, lines 100, statements 99) are unusually strict and will block any change that drops coverage even slightly. The long coveragePathIgnorePatterns list keeps the thresholds enforceable but means a meaningful fraction of the codebase (controllers, modules, DTOs, entities, scripts, cron files, configs) is not measured at all. The reported coverage figure should be interpreted with that in mind.
The boot sequence runs db-script-runner.ts before nest start in both dev and prod (via the npm run start:dev and npm run start:prod chains). Any failure in those scripts blocks process startup. connectDbEventListeners is also wired into the Nest bootstrap path for non-development environments, meaning database event listening is part of normal startup rather than a separately-managed concern.
Coexistence with the older Express-based procurement service is the largest architectural risk. Both services own a procurement domain, both publish events on NATS (Streaming versus JetStream), and the database described by database.dbml overlaps significantly with the e-procurement-v2 Sequelize models (purchase-orders, invoices, payments, branches, companies, products, etc.). A clear ownership decision per table and per event subject is needed; otherwise the two services can drift apart silently.
The webhook module wires WhatsApp webhooks straight into procurement entities (PurchaseOrderEntity, CreditFacilityEntity, BranchEntity, ProductLocationEntity, UserEntity). A misconfigured Twilio webhook secret or a malformed payload could update production purchase orders. The WhatsAppWebhookAuthService is the gate; its correctness is critical.
The CORS regex whitelist in main.ts admits any subdomain of vendease.com, plus localhost in dev/staging/production. Helmet is only applied in non-development environments, and the development branch keeps localhost open. Production safety relies on environment being correctly set; a misconfigured NODE_ENV would silently disable the Helmet protections.
The 250 MB-equivalent body sizes used elsewhere in the platform are not configured here; NestJS's defaults apply, so large file uploads need to be handled explicitly through the file-upload module rather than dropped into arbitrary controllers.
sqlfile.sql and filtered_sqlfile.sql at the repo root are raw SQL dumps. Their relationship to the migration history under src/migrations/ is not described in the README; treat them as ad-hoc snapshots rather than schema sources.
Finally, dbml-error.log indicates the dbdocs publishing flow has failed at some point. The published schema at https://dbdocs.io/victor.oluwaleye/procurement-backend and the ERD at https://dbdiagram.io/d/65ccf998ac844320ae265908 referenced in the README should be checked for freshness before being treated as canonical.