NestJS Integration
@orka-js/nestjs — Enterprise AI agents with NestJS IoC
The first truly idiomatic NestJS integration for AI agents. Expose agents via DI, decorators, guards, pipes, CQRS, and microservice transports — using patterns you already know.
What's included
OrkaModule DI
forRoot / forRootAsync / forMicroservice — standard NestJS DynamicModule pattern with ConfigService support.
@OrkaAgent & @InjectAgent
Decorators to mark and inject agents anywhere in your DI graph — services, controllers, guards.
@AgentReact (Event-Driven)
Method decorator that makes any @OnEvent() handler delegate to an agent. Zero boilerplate.
OrkaSemanticGuard
LLM-powered CanActivate guard. Describe your policy in plain English — the LLM decides ALLOW or DENY.
AgentValidationPipe
PipeTransform that accepts natural language and returns a typed DTO via llm.generateObject().
CQRS + Microservices
OrkaQueryHandler, @AgentQueryHandler, OrkaMessageHandler, AgentClient — full enterprise patterns.
Installation
npm install @orka-js/nestjs# orpnpm add @orka-js/nestjs # Optional — install only what you use:npm install @nestjs/cqrs # For CQRS featuresnpm install @nestjs/microservices # For microservice transportModule Setup
Import OrkaModule in your AppModule. All agents are auto-registered as named DI providers.
// app.module.tsimport { Module } from '@nestjs/common';import { OrkaModule } from '@orka-js/nestjs';import { Agent } from '@orka-js/agent';import { AnthropicAdapter } from '@orka-js/anthropic'; const llm = new AnthropicAdapter({ apiKey: process.env.ANTHROPIC_API_KEY! }); const salesAgent = new Agent({ goal: 'Sales assistant', tools: [] }, llm);const supportAgent = new Agent({ goal: 'Support assistant', tools: [] }, llm); @Module({ imports: [ OrkaModule.forRoot({ agents: { sales: salesAgent, support: supportAgent, }, path: 'ai', // Routes at: /ai, /ai/:agent, /ai/:agent/stream }), ],})export class AppModule {}Async Config (with ConfigService)
Use forRootAsync when your LLM keys come from environment variables via ConfigService.
// app.module.tsimport { Module } from '@nestjs/common';import { ConfigModule, ConfigService } from '@nestjs/config';import { OrkaModule } from '@orka-js/nestjs';import { Agent } from '@orka-js/agent';import { AnthropicAdapter } from '@orka-js/anthropic'; @Module({ imports: [ ConfigModule.forRoot(), OrkaModule.forRootAsync({ imports: [ConfigModule], path: 'ai', useFactory: (config: ConfigService) => ({ agents: { assistant: new Agent( { goal: 'Helpful assistant', tools: [] }, new AnthropicAdapter({ apiKey: config.get('ANTHROPIC_API_KEY')! }) ), }, }), inject: [ConfigService], }), ],})export class AppModule {}Auto-generated HTTP Routes
When path is set in OrkaModule.forRoot(), OrkaJS automatically mounts these routes:
| Method | Path | Description |
|---|---|---|
| GET | /{path} | List all registered agents |
| GET | /{path}/:agent | Get agent info |
| POST | /{path}/:agent | Run agent (non-streaming) |
| POST | /{path}/:agent/stream | Run agent with SSE streaming |
Decorators
Inject agents anywhere in your application. Use @OrkaAgent to mark classes with metadata.
// order.service.tsimport { Injectable } from '@nestjs/common';import { InjectAgent } from '@orka-js/nestjs';import type { BaseAgent } from '@orka-js/agent'; @Injectable()export class OrderService { constructor( @InjectAgent('sales') private salesAgent: BaseAgent, @InjectAgent('support') private supportAgent: BaseAgent, ) {} async processOrder(description: string) { return this.salesAgent.run(description); } async handleComplaint(issue: string) { return this.supportAgent.run(issue); }}@AgentReact — Event-Driven Agents
Decorate any @OnEvent() method to automatically delegate the event payload to an agent.
// order-events.handler.tsimport { Injectable } from '@nestjs/common';import { OnEvent } from '@nestjs/event-emitter';import { InjectAgent, AgentReact } from '@orka-js/nestjs';import type { BaseAgent } from '@orka-js/agent'; @Injectable()export class OrderEventsHandler { constructor( @InjectAgent('fulfillment') private agent: BaseAgent, @InjectAgent('retention') private retentionAgent: BaseAgent, ) {} // Awaits the agent result @OnEvent('order.created') @AgentReact() async onOrderCreated(payload: OrderCreatedEvent) {} // Fire-and-forget — doesn't block the event loop @OnEvent('customer.churned') @AgentReact({ agent: 'retentionAgent', async: true }) onCustomerChurned(payload: ChurnEvent): void {}}OrkaSemanticGuard
Protect routes with LLM-powered semantic authorization. Fails closed if the LLM is unavailable.
// admin.controller.tsimport { Controller, Get, UseGuards } from '@nestjs/common';import { OrkaSemanticGuard } from '@orka-js/nestjs';import { AnthropicAdapter } from '@orka-js/anthropic'; const llm = new AnthropicAdapter({ apiKey: process.env.ANTHROPIC_API_KEY! }); @Controller('admin')@UseGuards( new OrkaSemanticGuard( llm, 'Only allow requests that include a valid Authorization header with an admin Bearer token' ))export class AdminController { @Get('dashboard') getDashboard() { return { stats: '...' }; }} // The guard asks the LLM: "Based on the policy, should this request be ALLOWED or DENIED?"// → Fails closed (returns false) if the LLM is unavailableAgentValidationPipe — NLP → DTO
Accept natural language from clients and extract structured data. Falls back to direct schema validation for structured inputs (no LLM cost).
// products.controller.tsimport { Controller, Post, Body } from '@nestjs/common';import { AgentValidationPipe } from '@orka-js/nestjs';import { AnthropicAdapter } from '@orka-js/anthropic';import { z } from 'zod'; const llm = new AnthropicAdapter({ apiKey: process.env.ANTHROPIC_API_KEY! }); const SearchSchema = z.object({ category: z.string(), color: z.string().optional(), maxPrice: z.number().optional(), size: z.string().optional(),}); @Controller('products')export class ProductsController { @Post('search') async search( @Body(new AgentValidationPipe(SearchSchema, llm, { description: 'product search filters' })) query: z.infer<typeof SearchSchema> ) { return this.productService.search(query); }} // Client sends: { "input": "red shoes under $50 in size 42" }// Pipe returns: { category: "shoes", color: "red", maxPrice: 50, size: "42" } // Client sends: { "category": "shoes", "color": "red" }// Pipe validates directly — no LLM call (zero cost)CQRS Integration
Wire agents directly to CQRS queries and commands. Requires @nestjs/cqrs.
// Import from the /cqrs sub-path (requires @nestjs/cqrs)import { OrkaQueryHandler, AgentQueryHandler } from '@orka-js/nestjs/cqrs';import { InjectAgent } from '@orka-js/nestjs';import type { BaseAgent } from '@orka-js/agent'; // 1. Define the queryexport class GetProductRecommendationsQuery { constructor(public readonly userId: string, public readonly category: string) {}} // 2. Create the handler — extends OrkaQueryHandler, zero boilerplate@AgentQueryHandler(GetProductRecommendationsQuery)export class RecommendationsHandler extends OrkaQueryHandler<GetProductRecommendationsQuery> { constructor(@InjectAgent('recommendations') protected agent: BaseAgent) { super(); } // execute() is provided by OrkaQueryHandler: // → calls agent.run(JSON.stringify(query)) automatically} // 3. Register in module@Module({ imports: [CqrsModule, OrkaModule.forRoot({ agents: { recommendations: recoAgent } })], providers: [RecommendationsHandler],})export class ProductsModule {}Agent as Microservice
Deploy agents as independent NestJS microservices — scalable, transport-agnostic (Redis, NATS, Kafka, TCP). Requires @nestjs/microservices.
// Import from the /microservice sub-path (requires @nestjs/microservices)import { OrkaClientModule } from '@orka-js/nestjs/microservice';import { InjectAgentClient, AgentClient } from '@orka-js/nestjs';import { Transport } from '@nestjs/microservices'; // ── SERVER APP (agent microservice) ───────────────────────────────────────── // main.tsconst app = await NestFactory.createMicroservice(AppModule, { transport: Transport.REDIS, options: { host: 'localhost', port: 6379 },});await app.listen(); // app.module.ts@Module({ imports: [await OrkaModule.forMicroservice({ agents: { sales: salesAgent, support: supportAgent } })]})export class AppModule {} // ── CONSUMER APP ───────────────────────────────────────────────────────────── @Module({ imports: [ OrkaClientModule.forRoot({ clients: [{ name: 'agents', options: { transport: Transport.REDIS, options: { host: 'localhost', port: 6379 } }, }], }), ],})export class ConsumerModule {} @Injectable()export class OrderService { constructor(@InjectAgentClient('agents') private client: AgentClient) {} async processOrder(order: Order) { return this.client.run('sales', JSON.stringify(order)); }}📦 Optional peer dependencies
CQRS and microservice features require optional packages. Install only what you need:
npm install @nestjs/cqrs # @orka-js/nestjs/cqrsnpm install @nestjs/microservices # @orka-js/nestjs/microservice