Skip to content

Core Services

Overview

core-services is the Vendease core platform monorepo. It is the home of the newer-generation services that anchor the multi-tenant ERP — tenant and user management, business sign-up, authentication, file uploads, PDF generation, notifications, and the API gateway that fronts them. The repo is consciously consolidated: a single Nx + npm workspaces tree, NestJS framework, Turborepo for task orchestration, and a shared library set that every service consumes by name. This shape is a deliberate departure from earlier polyrepo layouts where each service shipped its own duplicated helpers.

The repository solves Vendease's need to run several aligned services with a consistent shape — same framework, same logging, same DI container, same NATS JetStream client, same database tooling — and to pull internal helpers out into versioned packages instead of duplicating them per service. The README and the tums service README make the multi-tenant intent explicit: TUMS (Tenant User Management Service) sits at the centre, business-service handles tenant sign-up and provisioning, and the rest serve cross-tenant concerns.

Architecture & Technology

Languages and runtimes

  • TypeScript across every application and package.
  • Node.js >= 18 (root engines); individual services state >= 16.17 in their READMEs.
  • npm workspaces (apps/*, packages/*) coordinated by Nx (15.8.7) and Turborepo (1.13.x).

Framework and core libraries

  • NestJS 9 for every service in apps/ (except the API gateway, which is KrakenD).
  • TypeORM 0.3 with PostgreSQL as the primary persistence layer, plus Knex 2 in some places (TUMS, the utils package, the migration scripts under scripts/).
  • NATS JetStream as the messaging substrate, via @nestjs-plugins/nestjs-nats-jetstream-transport — distinct from the older NATS Streaming client used elsewhere on the platform.
  • BullMQ / Bull with a Bull Board UI for background jobs (TUMS).
  • Redis for caching and session storage (ioredis).
  • Joi and class-validator for request validation.
  • Swagger / OpenAPI documentation per service, plus AsyncAPI specs (asyncapi.json) for event-driven contracts. Each service has a npm run build:docs target that renders a single-file AsyncAPI HTML doc into ./docs/.
  • NewRelic APM is wired in across services as a runtime dependency.
  • SendGrid for transactional email and Termii for SMS, both surfaced through the notification service.
  • Google Calendar API appears as a TUMS dependency (@googleapis/calendar).

Component layout

The monorepo is split into deployable applications and reusable packages.

Applications (apps/)

App Purpose (where discoverable)
tums Tenant User Management Service — manages users across all tenants in the multi-tenant ERP. Houses the largest set of migration scripts and the Bull-backed job runner. Public docs at tums-dev.vendease.com/v1/documentation.
business-service Onboards businesses into the ERP. Public docs at business-dev.vendease.com/v1/documentation.
auth-service Authentication and credential issuance.
admin-service Administrative surface for platform-level operations; ships a database.dbml schema diagram alongside source.
notification-service Email/SMS/in-app dispatch via SendGrid and Termii. Public docs at notification-dev.vendease.com/v1/documentation.
file-upload-service File ingestion and storage.
pdf-service PDF generation.
api-gateway KrakenD Community Edition gateway, configured by krakend.tmpl, partials/, templates/, and per-environment settings/. Built with FC_ENABLE=1 flexible-config and lint-checked at image build.
rms Service shipping a Dockerfile, NestJS structure, and Sonar config, but no README; purpose isn't documented in the repo.
product-catalog-service Present as a folder but only contains dist/ and node_modules/ at this checkout — not actively built from source here.

The capability split groups roughly into: identity and tenancy (TUMS, auth-service, business-service, admin-service), platform utilities (file-upload-service, pdf-service, notification-service), front-door (api-gateway), and one less-documented service (rms).

Packages (packages/)

The @vendease/* package set is the contractual core. Every NestJS app's package.json lists most of these as * workspace dependencies.

Package Role
decorators Custom NestJS decorators.
filters Exception filters for NestJS.
guards Auth/role guards.
helpers Cross-cutting helper functions.
interceptors NestJS interceptors.
middlewares Express/Nest middleware.
modules Reusable NestJS modules (auth, mail, etc.).
pagination Pagination utilities.
permissions Permission system primitives — also targeted by the migration scripts under scripts/.
pipes Validation/transform pipes.
providers Shared NestJS providers.
testing Shared test fixtures and helpers.
types TypeScript shared types.
utils General utilities — bcrypt wrappers, date helpers (dayjs), JWT helpers, Joi schemas, knex/typeorm helpers, Redis helpers, uuid.

Each package has its own tsconfig.json, builds to dist/, and is consumed by name (@vendease/utils, @vendease/permissions, etc.).

Scripts (scripts/)

Standalone Knex/ts-node scripts (migrate-eprocurement-permissions.ts, migrate-flow-and-pop-permissions.ts, migrate-tums-permissions.ts) for moving permissions data into TUMS. They have their own package.json so they can be installed and run independently of the apps.

How It Fits With Other Systems

Inside the Vendease platform

  • The api-gateway (KrakenD) sits in front of the NestJS services and shapes the public HTTP surface. Its templates and per-environment settings define which routes route to which service.
  • TUMS owns tenant and user state. Other services authenticate against tokens issued by auth-service and read tenant context from TUMS.
  • The migration scripts and migrate:* npm targets in TUMS (migrate:costeasy, migrate:flow, migrate:pop, migrate:eprocurement) indicate that TUMS is the convergence point for several legacy products being unified into the ERP — Costeasy, Flow, POP, and the e-procurement platform that sits in adjacent repositories (e-procurement-v2, procurement-integration-backend).
  • NATS JetStream is the event bus. Each service exposes an asyncapi.json to describe what subjects it produces and consumes; this is the contractual cross-service surface alongside REST.

External integrations referenced in code/configs

  • NewRelic — declared as a runtime dependency in TUMS and others; configured via NEW_RELIC_* environment variables documented in service READMEs (NEW_RELIC_LICENSE_KEY, NEW_RELIC_APP_NAME, NEW_RELIC_DISTRIBUTED_TRACING_ENABLED, etc.).
  • SonarCloud / Sonar — every NestJS app ships a sonar-project.properties.
  • SendGrid@sendgrid/mail in TUMS and notification-service. Configured via SENDGRID_API_KEY, DEFAULT_EMAIL_SENDER_*.
  • Termii — SMS provider for the notification service. Configured via TERMII_API_KEY and TERMII_API_URL.
  • Google Calendar@googleapis/calendar in TUMS, used to integrate calendar entries into tenant workflows.
  • Nx Cloudnx.json references an Nx Cloud access token and cacheableOperations. The flag neverConnectToCloud: true is set, indicating remote caching is configured but currently disabled.
  • AWS ECR (per the api-gateway Jenkinsfile, 607492160452.dkr.ecr.us-east-1.amazonaws.com) and hub.vendease.com (per the root Jenkinsfile-deploy) are the container registries.

Shared libraries

The packages/ tree is the only first-party shared library tier. There are no relative file: imports across services in this repo; every cross-service code path goes through a @vendease/* workspace package.

Repository Layout

core-services/
  apps/
    api-gateway/             KrakenD config + Dockerfile + Jenkinsfile
    auth-service/            NestJS service
    admin-service/           NestJS service + database.dbml
    business-service/        NestJS service (tenant sign-up)
    file-upload-service/     NestJS service
    notification-service/    NestJS service (SendGrid, Termii)
    pdf-service/             NestJS service
    product-catalog-service/ dist + node_modules only at this checkout
    rms/                     NestJS service (no README)
    tums/                    NestJS service — tenant user mgmt (largest)
  packages/
    decorators, filters, guards, helpers, interceptors,
    middlewares, modules, pagination, permissions, pipes,
    providers, testing, types, utils
  infra/
    Jenkinsfile              shared CI entry-point
    {api-gateway, tums, business-service, auth-service,
     admin-service, file-upload-service, notification-service,
     pdf-service, rms}/      per-service Helm charts / values
  scripts/                   standalone permission-migration scripts
  docker-compose.yml         shared NATS + Redis for local dev
  Dockerfile                 root image (Node 18-slim)
  Jenkinsfile-deploy         shared deploy pipeline
  nx.json, turbo.json        workspace orchestration configs
  package.json               npm workspaces + nx + turbo + eslint
  README.md                  short repo intro and Nx commands

Within each NestJS app, the layout follows Nest conventions: src/main.ts, modular feature folders, test/ for jest specs, Dockerfile per app, nest-cli.json, tsconfig.build.json, and a service-level Jenkinsfile for CI.

Running, Building & Deploying

Local development

The root README documents the Nx-based workflow:

  • npx nx graph to see the dependency graph between apps and packages.
  • npx nx run start:services <service> (e.g. business-service) to bring up Docker dependencies for one service.
  • npx nx build <service> to build a single service in CI; npx nx run-many --target=build builds the whole workspace.
  • The root package.json also exposes Turborepo equivalents (npm run turbo:dev, turbo:test, turbo:test:cov).

The root docker-compose.yml provides the shared infrastructure that local services need: NATS 2.9 with JetStream enabled and Redis 7. Each NestJS app additionally has its own docker-compose.yml for a per-service Postgres dev database.

Per-service commands are uniform NestJS (npm run start, npm run start:dev, npm run start:debug, npm run start:prod). Migrations are TypeORM-driven (migration:generate:main, migration:run:main, plus tenants variants in TUMS for the multi-tenant schema split). Seeding is via seed:run.

Environment configuration

There is no top-level .env.sample. Each service's README enumerates its environment variables; categories are consistent across the NestJS apps:

  • Application identity (NAME, VERSION, NODE_ENV, ENVIRONMENT, SERVER_HOST, SERVER_PORT).
  • Database (DATABASE_HOST, DATABASE_PORT, DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD, plus DATABASE_SYNCHRONIZE_* and DATABASE_LOGGING).
  • Redis (REDIS_HOST, REDIS_PORT).
  • NATS JetStream (JETSTREAM_URL, JETSTREAM_DELIVER_GROUP, JETSTREAM_CONSUMER_DURABLE, JETSTREAM_CONSUMER_DELIVER_TO, JETSTREAM_STREAM_NAME, JETSTREAM_STREAM_SUBJECTS).
  • Observability (SWAGGER_ENABLED, LOG_LEVEL, NEW_RELIC_*).
  • Service-specific keys (TUMS seed admin, SendGrid, Termii, Google API).

Container builds and CI/CD

  • The root Dockerfile is a Node 18-slim base used for the workspace as a whole; per-service Dockerfiles sit inside each apps/*. TUMS, file-upload-service, and rms additionally ship Dockerfile-prod variants.
  • infra/Jenkinsfile is the shared CI entry-point. The root Jenkinsfile-deploy is parameterised by service, environment, and commit_id; it pulls a pre-built image from hub.vendease.com, then runs helm upgrade --install against values-staging.yaml or values-prod.yaml under infra/<service>/. Namespaces are erp-staging and erp-prod. Notifications post to Google Chat via configured webhook credentials.
  • Per-service Jenkinsfiles (e.g. apps/api-gateway/Jenkinsfile) build the image, push to AWS ECR (607492160452.dkr.ecr.us-east-1.amazonaws.com), and deploy to the dev cluster via Helm.
  • SonarCloud is wired per service for static analysis and coverage reporting.

Operational Notes & Risks

  • Two task runners cohabit. The repo uses both Nx and Turborepo. The README documents Nx commands; the root package.json defines turbo:* scripts as alternatives. There is no documentation of when to prefer one over the other; expect drift if both are kept indefinitely.
  • Nx Cloud token exposed. nx.json contains an Nx Cloud accessToken (read-write) inline. neverConnectToCloud: true is set so it is not currently used, but the token is still in version control. Rotate or remove if the cloud cache is not intended to be used.
  • product-catalog-service is a stub at this checkout. It contains only dist/ and node_modules/ — no src/, no package.json, no Dockerfile. Treat it as not-yet-implemented or removed.
  • rms lacks documentation. It has Sonar config, a Dockerfile, a Jenkinsfile, and source, but no README and no obvious description in package.json. Inspect the source before assuming purpose.
  • Service surface naming is inconsistent. tums, rms, and pdf-service use abbreviated names; business-service, notification-service, etc. use full names. Public docs URLs follow the abbreviated pattern (tums-dev.vendease.com).
  • Heavy dependency footprint per service. Each NestJS app keeps its own node_modules/ (not deduplicated by the workspace), so a checkout of the full repo is sizeable. The repository ships a 688 KB package-lock.json.
  • Test coverage signals. Every NestJS app has a test/ folder, a Jest configuration, and a sonar-project.properties referencing coverage/lcov.info. Coverage exclusion lists in TUMS are extensive (DTOs, controllers, entities, modules, providers, scripts, seeds, datasource files), which inflates the reported percentage.
  • Multiple message contracts. Both Swagger (REST) and AsyncAPI (events) specifications coexist; the build:docs script renders AsyncAPI to a single HTML file. Drift between the two surfaces should be checked when modifying handlers.
  • Migration scripts run outside the apps. TUMS exposes migrate:costeasy, migrate:flow, migrate:pop, migrate:eprocurement, and the standalone scripts/ folder layers more migrations on top. The order in which these have been run in production is not recorded in the repo.
  • No top-level test or CI signal in the root. eslint.config.mjs is set up at the root, but tests are invoked per-service via Turbo/Nx. The root Jenkinsfile-deploy only deploys; the build pipelines live in each app's Jenkinsfile.