@exium/core
v1.0.0-rc.0
Published
Opinionated NestJS-based framework for building DDD and Hexagonal Architecture microservices
Downloads
21
Maintainers
Readme
Exium
Opinionated NestJS-based framework for building DDD, hexagonal, modular-monolith-first microservices.
Exium is built primarily for Exium-based services. It is public so applications can install it from the npm registry, but it remains an opinionated platform framework rather than a generic NestJS replacement.
Published package:
npm install @exium/coreCLI package:
npm install -g @exium/cli
exium new order-managementExium is designed as an internal platform framework. It intentionally ships with the infrastructure choices this codebase standardizes on:
- NestJS runtime
- Express HTTP adapter
- MikroORM persistence
- PostgreSQL and MongoDB persistence drivers
- Kysely read-side query engine
- RabbitMQ integration messaging
- Redis cache and idempotency adapters
Layers
src/foundation Result and failure primitives
src/domain DDD building blocks
src/application CQRS, middleware, ports, read contracts
src/infrastructure Persistence, messaging, Redis, cache, idempotency adapters
src/http External/internal HTTP contracts, responses, filters, interceptors
src/runtime Outermost Nest runtime and Exium bootstrapHTTP Model
HTTP endpoints can be marked as external or internal contracts.
External APIs are the public contract exposed through a gateway:
@Controller('orders')
@ExternalApi({ name: 'orders', owner: 'order-management' })
export class OrderController {}Internal APIs are module/service-to-module/service contracts. Internal calls can resolve locally in a modular monolith or remotely over HTTP after extraction:
@Controller('orders/internal')
@InternalApi({ name: 'orders-internal', mode: 'auto' })
export class InternalOrderController {}Application Model
Commands and queries return Result.
@Transactional()
export class CreateOrderCommand extends Command<string> {}
@HandleCommand(CreateOrderCommand)
export class CreateOrderCommandHandler extends CommandHandler<CreateOrderCommand> {
async handle(command: CreateOrderCommand): Promise<ResultType<string>> {
return Result.ok('order-id');
}
}Transactional commands commit only on Result.ok(...). Result.fail(...) rolls back.
Exception vs Result
Exium follows a strict policy:
- Thrown exceptions = unexpected failures. Programmer errors, infrastructure faults, broken invariants. The exception filter logs them and surfaces a generic HTTP 500 — internal details never leak to clients.
Result.fail(Failure...)= expected business failures. Validation, not-found, business rule violations, conflicts, authentication/authorization denials. The response interceptor maps eachFailure.kindto the appropriate 4xx status.
// Expected — return as Result
async handle(command: CreateOrderCommand): Promise<ResultType<string>> {
if (await this.repo.exists(command.id)) {
return Result.fail(
Failure.conflict('ORDER_ALREADY_EXISTS', 'Order already exists.', {
orderId: command.id,
}),
);
}
return Result.ok(command.id);
}
// Unexpected — let it throw, filter turns it into 500
if (!this.connection.isOpen) {
throw new Error('Database connection is closed.');
}HttpException is the only exception type that keeps its original status —
preserved for compatibility with NestJS pipes (e.g. ValidationPipe).
Release Gate
Start the test databases first:
npm run test:db:upRun the v1 gate:
npm testThe root test gate includes domain, config, infrastructure, application, core, HTTP, e2e, CLI workspace, and example build checks.
The CLI workspace smoke test also verifies that exium new order-management --skip-install creates a default Order aggregate project that typechecks. The CLI package is kept separate from @exium/core.
ADRs
Architecture decisions live in docs/adr.
Start with:
