Management API ↔ @kamea/api lib
The Management API ships as two pieces in the monorepo:
- A library,
@kamea/api, atcore/libs/api/. Contains the platform code that defines "Kamea the product" — controllers, services, entities, repositories, migrations, bootstrap helpers. - A thin reference app,
management-api, atcore/apps/api/management-api/. Contains the bits that are "this deployment" — the entry point (main.ts), the operational fixtures, the Dockerfile, the OpenAPI yaml, the e2e tests.
This page explains why the split exists, how to think about where new code belongs, and how the app boots through the lib. For the broader picture of @kamea/api next to the other libs, see Kamea Libs.
Why the split exists
Kamea is delivered to customers as source code. Customers extend the platform with their own business logic. When extension was done by editing files inside management-api/src/, every platform release caused merge conflicts with customer modifications.
The split is the structural fix:
- Customers depend on
@kamea/apias an npm package. They do not modify it. - Their own app — shaped like the reference
management-api— adds whatever business code they need on top.
Platform releases ship a new lib version. Customer projects upgrade by bumping the dependency, not by merging source.
What lives where
Conceptually:
In the lib (@kamea/api) |
In the app (management-api) |
|---|---|
| Feature modules (devices, users, …) | The bootstrap entry point (main.ts) |
| Domain entities and their TypeORM mapping | Connection parameters (read from environment) |
| Database migrations that own Kamea's schema | Deployment-side fixtures (demo data, load-test seeds) |
| Required platform seed data | e2e tests |
| HTTP middlewares, guards, exception filters | Container build (Dockerfile) |
The AppModule that assembles the lib's tree |
Anything the customer adds on top |
The lib's src/ keeps a folder-per-feature layout (each feature owns its entity, dto, service, controller, and module co-located). The exact folder list lives in the source — ls core/libs/api/src/ is the source of truth.
How the app boots through the lib
The customer-facing entry points are two:
KameaModule.forRoot(settings)— returned from the lib, imported in the consumer's ownAppModulealongside the consumer's own feature modules.bootstrapKamea(app, settings)— called by the consumer'smain.tsafterNestFactory.create, beforeapp.listen. Performs middleware configuration, registry population, Swagger setup, message-bus exchange assertion, the required-data seed (lib's + customer's), and starts background workers.
The reference main.ts is therefore three steps long: create the Nest app, call bootstrapKamea, listen. The lib's AppModule is not a customer-facing export — KameaModule wraps it.
A customer app shape:
// app.module.ts
@Module({
imports: [KameaModule.forRoot(settings), MyBusinessModule, MyOtherModule],
})
export class AppModule {}
Customer modules sit as siblings of KameaModule under the customer's root and can inject lib services freely.
Read core/apps/api/management-api/src/main.ts and app.module.ts for the current shape — keeping both short is intentional.
Settings
KameaSettings carries the configuration the lib needs from the consumer. The full shape lives in core/libs/api/src/kamea/settings.ts (read the source for the authoritative list). At a glance:
- Required:
database(PostgreSQL connection parameters). - Optional values:
port,corsOrigin,logLevels. - SPI hooks:
extraEntities,extraMigrationsDirs,initialDataProviders.
Anything not in KameaSettings is still configured via process environment variables (auth, MQTT, IoT Hub, storage, Swagger prefix, …).
Customer-side seed data — InitialDataProvider
Customer projects that need to seed their own initial rows (default catalogs, custom roles, reference data, …) implement an InitialDataProvider:
@Injectable()
export class MyInitialData implements InitialDataProvider {
constructor(private readonly someService: SomeService) {}
async ensure(ds: DataSource): Promise<void> {
// upsert rows; must be idempotent — runs every boot
}
}
Register the class reference in settings.initialDataProviders. The lib resolves it via Nest DI (so injection of customer or lib services works), runs the lib's own seed first, then the customer's providers in array order.
Contract: providers must be idempotent. The lib runs them on every boot.
Build chain
Building the management-api app builds its lib dependencies first. The root package.json exposes the lib build scripts and chains them in the app's build script — the actual command sequence is in that file. Always build from the repo root: running npm run build from inside the app without the lib being up to date will fail or silently use stale output.
Database, entities, and migrations
The lib owns the schema and its migrations. The app passes connection parameters (host, port, credentials, database name) via environment variables — the entity list and the migrations folder are baked into the lib.
For the day-to-day TypeORM commands (generate, run, revert, show), see Migrations with ORM. The migration commands live in the app's package.json and point at the lib's TypeORM configuration; the underlying workflow does not change because of the lib split.
For the architectural rationale (single TypeORM DataSource, customer-side entity/migration extension, FK references across the lib/customer boundary), see the project root's API_LIB_EXTRACTION_PLAN.md.
Adding code: lib or app?
Use this checklist when deciding where new code belongs:
| It's… | …goes in |
|---|---|
| Part of "Kamea the product" — every deployment needs it | @kamea/api |
| A new domain entity Kamea owns | @kamea/api |
| A migration touching Kamea's tables | @kamea/api |
| Required platform seed data | @kamea/api |
| Customer-specific business logic | the customer app |
| A customer-specific entity | the customer app |
| A migration creating customer-only tables | the customer app |
| Seed data only this deployment needs (demo fixtures, customer default roles) | the customer app |
The reference management-api app currently uses the lib without adding any business code on top — it is the "vanilla deployment" of Kamea.
Public surface
What @kamea/api exposes is curated. See Kamea Libs for the discipline — the same rules apply to this lib. When you need a new symbol exported (because a customer app or a sibling lib needs it), add it to the relevant public-api.ts, rebuild, and consumers can import it.