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
utilspackage, the migration scripts underscripts/). - 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-validatorfor request validation. - Swagger / OpenAPI documentation per service, plus AsyncAPI specs (
asyncapi.json) for event-driven contracts. Each service has anpm run build:docstarget 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-serviceand 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.jsonto 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/mailin TUMS and notification-service. Configured viaSENDGRID_API_KEY,DEFAULT_EMAIL_SENDER_*. - Termii — SMS provider for the notification service. Configured via
TERMII_API_KEYandTERMII_API_URL. - Google Calendar —
@googleapis/calendarin TUMS, used to integrate calendar entries into tenant workflows. - Nx Cloud —
nx.jsonreferences an Nx Cloud access token andcacheableOperations. The flagneverConnectToCloud: trueis set, indicating remote caching is configured but currently disabled. - AWS ECR (per the
api-gatewayJenkinsfile,607492160452.dkr.ecr.us-east-1.amazonaws.com) andhub.vendease.com(per the rootJenkinsfile-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 graphto 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=buildbuilds the whole workspace.- The root
package.jsonalso 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, plusDATABASE_SYNCHRONIZE_*andDATABASE_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
Dockerfileis a Node 18-slim base used for the workspace as a whole; per-serviceDockerfiles sit inside eachapps/*. TUMS, file-upload-service, and rms additionally shipDockerfile-prodvariants. infra/Jenkinsfileis the shared CI entry-point. The rootJenkinsfile-deployis parameterised byservice,environment, andcommit_id; it pulls a pre-built image fromhub.vendease.com, then runshelm upgrade --installagainstvalues-staging.yamlorvalues-prod.yamlunderinfra/<service>/. Namespaces areerp-staginganderp-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.jsondefinesturbo:*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.jsoncontains an Nx CloudaccessToken(read-write) inline.neverConnectToCloud: trueis 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-serviceis a stub at this checkout. It contains onlydist/andnode_modules/— nosrc/, nopackage.json, no Dockerfile. Treat it as not-yet-implemented or removed.rmslacks documentation. It has Sonar config, a Dockerfile, a Jenkinsfile, and source, but no README and no obvious description inpackage.json. Inspect the source before assuming purpose.- Service surface naming is inconsistent.
tums,rms, andpdf-serviceuse 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 KBpackage-lock.json. - Test coverage signals. Every NestJS app has a
test/folder, a Jest configuration, and asonar-project.propertiesreferencingcoverage/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:docsscript 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 standalonescripts/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.mjsis set up at the root, but tests are invoked per-service via Turbo/Nx. The rootJenkinsfile-deployonly deploys; the build pipelines live in each app'sJenkinsfile.