OrkaJS
Orka.JS
NestJS

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
# or
pnpm add @orka-js/nestjs
 
# Optional — install only what you use:
npm install @nestjs/cqrs # For CQRS features
npm install @nestjs/microservices # For microservice transport

Module Setup

Import OrkaModule in your AppModule. All agents are auto-registered as named DI providers.

app.module.ts
// app.module.ts
import { 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.ts
// app.module.ts
import { 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:

MethodPathDescription
GET/{path}List all registered agents
GET/{path}/:agentGet agent info
POST/{path}/:agentRun agent (non-streaming)
POST/{path}/:agent/streamRun agent with SSE streaming

Decorators

Inject agents anywhere in your application. Use @OrkaAgent to mark classes with metadata.

order.service.ts
// order.service.ts
import { 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.ts
// order-events.handler.ts
import { 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.ts
// admin.controller.ts
import { 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 unavailable

AgentValidationPipe — 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.ts
// products.controller.ts
import { 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.

recommendations.handler.ts
// 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 query
export 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.

microservice-setup.ts
// 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.ts
const 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/cqrs
npm install @nestjs/microservices # @orka-js/nestjs/microservice