nestjs-temporal-core
    Preparing search index...

    nestjs-temporal-core

    NestJS Temporal Core

    A comprehensive NestJS integration framework for Temporal.io that provides enterprise-ready workflow orchestration with automatic discovery, declarative decorators, and robust monitoring capabilities.

    Statements Branches Functions Lines

    DocumentationNPMGitHubExample Project


    NestJS Temporal Core bridges NestJS's powerful dependency injection system with Temporal.io's robust workflow orchestration engine. It provides a declarative approach to building distributed, fault-tolerant applications with automatic service discovery, enterprise-grade monitoring, and seamless integration.

    Feature Description
    Seamless Integration Native NestJS decorators and dependency injection support
    Auto-Discovery Automatic registration of activities and workflows via decorators
    Type Safety Full TypeScript support with comprehensive type definitions
    Enterprise Ready Built-in health checks, monitoring, and error handling
    Zero Configuration Smart defaults with extensive customization options
    Modular Architecture Use client-only, worker-only, or full-stack configurations
    Production Grade Connection pooling, graceful shutdown, and fault tolerance
    • Declarative Decorators - Use @Activity() and @ActivityMethod() for clean, intuitive activity definitions
    • Automatic Discovery - Runtime discovery and registration of activities with zero configuration
    • Schedule Management - Programmatic schedule creation, updates, and monitoring
    • Health Monitoring - Built-in health checks and comprehensive status reporting
    • Connection Management - Automatic connection pooling and lifecycle management
    • Error Handling - Structured error handling with detailed logging and retry policies
    • Performance Monitoring - Built-in metrics, statistics, and performance tracking
    • Graceful Shutdown - Clean resource cleanup and connection termination
    • Modular Design - Use only what you need (client-only, worker-only, or combined)
    • Multiple Workers - Support for multiple workers with different task queues
    • Advanced Configuration - Extensive customization for production environments
    • TLS Support - Secure connections for Temporal Cloud deployments

    🔝 Back to top

    npm install nestjs-temporal-core @temporalio/client @temporalio/worker @temporalio/workflow @temporalio/common
    
    npm install @nestjs/common @nestjs/core reflect-metadata rxjs
    

    🔝 Back to top

    Enable shutdown hooks in your main.ts for proper Temporal resource cleanup:

    // main.ts
    import { NestFactory } from '@nestjs/core';
    import { AppModule } from './app.module';

    async function bootstrap() {
    const app = await NestFactory.create(AppModule);

    // Required for graceful Temporal connection cleanup
    app.enableShutdownHooks();

    await app.listen(3000);
    }
    bootstrap();

    Import and configure TemporalModule in your app module:

    // app.module.ts
    import { Module } from '@nestjs/common';
    import { TemporalModule } from 'nestjs-temporal-core';
    import { PaymentActivity } from './activities/payment.activity';
    import { EmailActivity } from './activities/email.activity';

    @Module({
    imports: [
    TemporalModule.register({
    connection: {
    address: 'localhost:7233',
    namespace: 'default',
    },
    taskQueue: 'my-task-queue',
    worker: {
    workflowsPath: require.resolve('./workflows'),
    activityClasses: [PaymentActivity, EmailActivity],
    autoStart: true,
    },
    }),
    ],
    providers: [PaymentActivity, EmailActivity],
    })
    export class AppModule {}

    Create activities using @Activity() and @ActivityMethod() decorators:

    // payment.activity.ts
    import { Injectable } from '@nestjs/common';
    import { Activity, ActivityMethod } from 'nestjs-temporal-core';

    export interface PaymentData {
    amount: number;
    currency: string;
    customerId: string;
    }

    @Injectable()
    @Activity({ name: 'payment-activities' })
    export class PaymentActivity {

    @ActivityMethod('processPayment')
    async processPayment(data: PaymentData): Promise<{ transactionId: string }> {
    // Payment processing logic with full NestJS DI support
    console.log(`Processing payment: $${data.amount} ${data.currency}`);

    // Simulate payment processing
    await new Promise(resolve => setTimeout(resolve, 1000));

    return { transactionId: `txn_${Date.now()}` };
    }

    @ActivityMethod('refundPayment')
    async refundPayment(transactionId: string): Promise<{ refundId: string }> {
    // Refund logic
    console.log(`Refunding transaction: ${transactionId}`);
    return { refundId: `ref_${Date.now()}` };
    }
    }

    Create workflows as pure Temporal functions (NOT NestJS services):

    // payment.workflow.ts
    import { proxyActivities, defineSignal, defineQuery, setHandler } from '@temporalio/workflow';
    import type { PaymentActivity } from './payment.activity';

    // Create activity proxies
    const { processPayment, refundPayment } = proxyActivities<typeof PaymentActivity.prototype>({
    startToCloseTimeout: '5m',
    retry: {
    maximumAttempts: 3,
    initialInterval: '1s',
    },
    });

    // Define signals and queries
    export const cancelPaymentSignal = defineSignal<[string]>('cancelPayment');
    export const getPaymentStatusQuery = defineQuery<string>('getPaymentStatus');

    export async function processPaymentWorkflow(data: PaymentData): Promise<any> {
    let status = 'processing';
    let transactionId: string | undefined;

    // Set up signal and query handlers
    setHandler(cancelPaymentSignal, (reason: string) => {
    status = 'cancelled';
    });

    setHandler(getPaymentStatusQuery, () => status);

    try {
    // Execute payment activity
    const result = await processPayment(data);
    transactionId = result.transactionId;
    status = 'completed';

    return {
    success: true,
    transactionId,
    status,
    };
    } catch (error) {
    status = 'failed';

    // Compensate if needed
    if (transactionId) {
    await refundPayment(transactionId);
    }

    throw error;
    }
    }

    Inject TemporalService to start and manage workflows:

    // payment.service.ts
    import { Injectable } from '@nestjs/common';
    import { TemporalService } from 'nestjs-temporal-core';

    @Injectable()
    export class PaymentService {
    constructor(private readonly temporal: TemporalService) {}

    async processPayment(paymentData: any) {
    // Start workflow
    const result = await this.temporal.startWorkflow(
    'processPaymentWorkflow',
    [paymentData],
    {
    workflowId: `payment-${Date.now()}`,
    taskQueue: 'my-task-queue',
    }
    );

    return {
    workflowId: result.result.workflowId,
    runId: result.result.runId,
    };
    }

    async checkPaymentStatus(workflowId: string) {
    // Query workflow
    const statusResult = await this.temporal.queryWorkflow(
    workflowId,
    'getPaymentStatus'
    );

    return { status: statusResult.result };
    }

    async cancelPayment(workflowId: string, reason: string) {
    // Send signal
    await this.temporal.signalWorkflow(
    workflowId,
    'cancelPayment',
    [reason]
    );
    }
    }

    🔝 Back to top

    The package provides modular architecture with separate modules for different use cases:

    Complete integration with both client and worker capabilities:

    import { TemporalModule } from 'nestjs-temporal-core';

    TemporalModule.register({
    connection: { address: 'localhost:7233' },
    taskQueue: 'my-queue',
    worker: {
    workflowsPath: require.resolve('./workflows'),
    activityClasses: [PaymentActivity, EmailActivity],
    },
    })

    For services that only need to start/query workflows:

    import { TemporalClientModule } from 'nestjs-temporal-core/client';

    TemporalClientModule.register({
    connection: { address: 'localhost:7233' },
    namespace: 'default',
    })

    For dedicated worker processes:

    import { TemporalWorkerModule } from 'nestjs-temporal-core/worker';

    TemporalWorkerModule.register({
    connection: { address: 'localhost:7233' },
    taskQueue: 'worker-queue',
    worker: {
    workflowsPath: require.resolve('./workflows'),
    activityClasses: [BackgroundActivity],
    },
    })

    For standalone activity management:

    import { TemporalActivityModule } from 'nestjs-temporal-core/activity';

    TemporalActivityModule.register({
    activityClasses: [DataProcessingActivity],
    })

    For managing Temporal schedules:

    import { TemporalSchedulesModule } from 'nestjs-temporal-core/schedules';

    TemporalSchedulesModule.register({
    connection: { address: 'localhost:7233' },
    })

    🔝 Back to top

    TemporalModule.register({
    connection: {
    address: 'localhost:7233',
    namespace: 'default',
    },
    taskQueue: 'my-task-queue',
    worker: {
    workflowsPath: require.resolve('./workflows'),
    activityClasses: [PaymentActivity, EmailActivity],
    autoStart: true,
    maxConcurrentActivityExecutions: 100,
    },
    logLevel: 'info',
    enableLogger: true,
    })

    New in 3.0.12: Support for multiple workers with different task queues in the same process.

    TemporalModule.register({
    connection: {
    address: 'localhost:7233',
    namespace: 'default',
    },
    autoRestart: true, // Global default for all workers
    maxRestarts: 3, // Global default for all workers
    workers: [
    {
    taskQueue: 'payments-queue',
    workflowsPath: require.resolve('./workflows/payments'),
    activityClasses: [PaymentActivity, RefundActivity],
    autoStart: true,
    maxRestarts: 5, // Override for this critical worker
    workerOptions: {
    maxConcurrentActivityTaskExecutions: 100,
    },
    },
    {
    taskQueue: 'notifications-queue',
    workflowsPath: require.resolve('./workflows/notifications'),
    activityClasses: [EmailActivity, SmsActivity],
    autoStart: true,
    workerOptions: {
    maxConcurrentActivityTaskExecutions: 50,
    },
    },
    {
    taskQueue: 'background-jobs',
    workflowsPath: require.resolve('./workflows/jobs'),
    activityClasses: [DataProcessingActivity],
    autoStart: false, // Start manually later
    autoRestart: false, // Disable auto-restart for this worker
    },
    ],
    logLevel: 'info',
    enableLogger: true,
    })
    import { Injectable } from '@nestjs/common';
    import { TemporalService } from 'nestjs-temporal-core';

    @Injectable()
    export class WorkerManagementService {
    constructor(private readonly temporal: TemporalService) {}

    async checkWorkerStatus() {
    // Get all workers info
    const workersInfo = this.temporal.getAllWorkers();
    console.log(`Total workers: ${workersInfo.totalWorkers}`);
    console.log(`Running workers: ${workersInfo.runningWorkers}`);

    // Get specific worker status
    const paymentWorkerStatus = this.temporal.getWorkerStatusByTaskQueue('payments-queue');
    if (paymentWorkerStatus?.isHealthy) {
    console.log('Payment worker is healthy');
    }
    }

    async controlWorkers() {
    // Start a specific worker
    await this.temporal.startWorkerByTaskQueue('background-jobs');

    // Stop a specific worker
    await this.temporal.stopWorkerByTaskQueue('notifications-queue');
    }

    async registerNewWorker() {
    // Dynamically register a new worker at runtime
    const result = await this.temporal.registerWorker({
    taskQueue: 'new-queue',
    workflowsPath: require.resolve('./workflows/new'),
    activityClasses: [NewActivity],
    autoStart: true,
    });

    if (result.success) {
    console.log(`Worker registered for queue: ${result.taskQueue}`);
    }
    }
    }

    For users who need full control, you can access the native Temporal connection to create custom workers:

    import { Injectable, OnModuleInit } from '@nestjs/common';
    import { TemporalService } from 'nestjs-temporal-core';
    import { Worker } from '@temporalio/worker';

    @Injectable()
    export class CustomWorkerService implements OnModuleInit {
    private customWorker: Worker;

    constructor(private readonly temporal: TemporalService) {}

    async onModuleInit() {
    const workerManager = this.temporal.getWorkerManager();
    const connection = workerManager.getConnection();

    if (!connection) {
    throw new Error('No connection available');
    }

    // Create your custom worker using the native Temporal SDK
    this.customWorker = await Worker.create({
    connection,
    taskQueue: 'custom-task-queue',
    namespace: 'default',
    workflowsPath: require.resolve('./workflows/custom'),
    activities: {
    myCustomActivity: async (data: string) => {
    return `Processed: ${data}`;
    },
    },
    });

    // Start the worker
    await this.customWorker.run();
    }
    }

    For dynamic configuration using environment variables or config services:

    // config/temporal.config.ts
    import { Injectable } from '@nestjs/common';
    import { ConfigService } from '@nestjs/config';
    import { TemporalOptionsFactory, TemporalOptions } from 'nestjs-temporal-core';

    @Injectable()
    export class TemporalConfigService implements TemporalOptionsFactory {
    constructor(private configService: ConfigService) {}

    createTemporalOptions(): TemporalOptions {
    return {
    connection: {
    address: this.configService.get('TEMPORAL_ADDRESS', 'localhost:7233'),
    namespace: this.configService.get('TEMPORAL_NAMESPACE', 'default'),
    },
    taskQueue: this.configService.get('TEMPORAL_TASK_QUEUE', 'default'),
    worker: {
    workflowsPath: require.resolve('../workflows'),
    activityClasses: [], // Populated by module
    maxConcurrentActivityExecutions: 100,
    },
    };
    }
    }

    // app.module.ts
    import { ConfigModule } from '@nestjs/config';

    @Module({
    imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    TemporalModule.registerAsync({
    imports: [ConfigModule],
    useClass: TemporalConfigService,
    }),
    ],
    })
    export class AppModule {}
    TemporalModule.registerAsync({
    imports: [ConfigModule],
    useFactory: (configService: ConfigService) => ({
    connection: {
    address: configService.get('TEMPORAL_ADDRESS', 'localhost:7233'),
    namespace: configService.get('TEMPORAL_NAMESPACE', 'default'),
    },
    taskQueue: configService.get('TEMPORAL_TASK_QUEUE', 'default'),
    worker: {
    workflowsPath: require.resolve('./workflows'),
    activityClasses: [PaymentActivity, EmailActivity],
    },
    }),
    inject: [ConfigService],
    })

    For secure connections to Temporal Cloud:

    import * as fs from 'fs';

    TemporalModule.register({
    connection: {
    address: 'your-namespace.your-account.tmprl.cloud:7233',
    namespace: 'your-namespace.your-account',
    tls: {
    clientCertPair: {
    crt: fs.readFileSync('/path/to/client.crt'),
    key: fs.readFileSync('/path/to/client.key'),
    },
    },
    },
    taskQueue: 'my-task-queue',
    worker: {
    workflowsPath: require.resolve('./workflows'),
    activityClasses: [PaymentActivity],
    },
    })
    interface TemporalOptions {
    // Connection settings
    connection: {
    address: string; // Temporal server address (default: 'localhost:7233')
    namespace?: string; // Temporal namespace (default: 'default')
    tls?: TLSConfig; // TLS configuration for secure connections
    };

    // Task queue name
    taskQueue?: string; // Default task queue (default: 'default')

    // Worker configuration
    worker?: {
    workflowsPath?: string; // Path to workflow definitions (use require.resolve)
    activityClasses?: any[]; // Array of activity classes to register
    autoStart?: boolean; // Auto-start worker on module init (default: true)
    autoRestart?: boolean; // Auto-restart on failure (inherits from global)
    maxRestarts?: number; // Max restart attempts (inherits from global)
    maxConcurrentActivityExecutions?: number; // Max concurrent activities (default: 100)
    maxActivitiesPerSecond?: number; // Rate limit for activities
    };

    // Logging
    logLevel?: 'trace' | 'debug' | 'info' | 'warn' | 'error'; // Log level (default: 'info')
    enableLogger?: boolean; // Enable logging (default: true)

    // Auto-restart configuration
    autoRestart?: boolean; // Auto-restart worker on failure (default: true)
    maxRestarts?: number; // Max restart attempts before giving up (default: 3)

    // Advanced
    isGlobal?: boolean; // Make module global (default: false)
    }

    🔝 Back to top

    Activities are NestJS services decorated with @Activity() that perform actual work. They have full access to NestJS dependency injection and can interact with external systems.

    Key Points:

    • Activities are NestJS services (@Injectable())
    • Use @Activity() decorator at class level
    • Use @ActivityMethod() decorator for methods to be registered
    • Activities should be idempotent and handle retries gracefully
    • Full access to NestJS DI (inject services, repositories, etc.)
    @Injectable()
    @Activity({ name: 'order-activities' })
    export class OrderActivity {
    constructor(
    private readonly orderRepository: OrderRepository,
    private readonly emailService: EmailService,
    ) {}

    @ActivityMethod('createOrder')
    async createOrder(orderData: CreateOrderData): Promise<Order> {
    // Database operations with full DI support
    const order = await this.orderRepository.create(orderData);
    await this.emailService.sendConfirmation(order);
    return order;
    }

    @ActivityMethod('validateInventory')
    async validateInventory(items: OrderItem[]): Promise<boolean> {
    // Business logic with injected services
    return await this.orderRepository.checkInventory(items);
    }
    }

    Workflows are pure Temporal functions (NOT NestJS services) that orchestrate activities. They must be deterministic and use Temporal's workflow APIs.

    Important: Workflows are NOT decorated with @Injectable() and should NOT use NestJS dependency injection.

    // order.workflow.ts
    import { proxyActivities, defineSignal, defineQuery, setHandler } from '@temporalio/workflow';
    import type { OrderActivity } from './order.activity';

    // Create activity proxies with proper typing
    const { createOrder, validateInventory } = proxyActivities<typeof OrderActivity.prototype>({
    startToCloseTimeout: '5m',
    retry: {
    maximumAttempts: 3,
    initialInterval: '1s',
    maximumInterval: '30s',
    },
    });

    // Define signals and queries at module level
    export const cancelOrderSignal = defineSignal<[string]>('cancelOrder');
    export const getOrderStatusQuery = defineQuery<string>('getOrderStatus');

    // Workflow function (exported, not a class)
    export async function processOrderWorkflow(orderData: CreateOrderData): Promise<OrderResult> {
    let status = 'pending';

    // Set up signal handler
    setHandler(cancelOrderSignal, (reason: string) => {
    status = 'cancelled';
    });

    // Set up query handler
    setHandler(getOrderStatusQuery, () => status);

    try {
    // Validate inventory
    const isValid = await validateInventory(orderData.items);
    if (!isValid) {
    throw new Error('Insufficient inventory');
    }

    // Create order
    status = 'processing';
    const order = await createOrder(orderData);
    status = 'completed';

    return {
    orderId: order.id,
    status,
    };
    } catch (error) {
    status = 'failed';
    throw error;
    }
    }

    Signals allow external systems to send events to workflows, while queries provide read-only access to workflow state.

    import { defineSignal, defineQuery, setHandler, condition } from '@temporalio/workflow';

    // Define at module level
    export const updateStatusSignal = defineSignal<[string]>('updateStatus');
    export const addItemSignal = defineSignal<[Item]>('addItem');
    export const getItemsQuery = defineQuery<Item[]>('getItems');
    export const getStatusQuery = defineQuery<string>('getStatus');

    export async function myWorkflow(): Promise<void> {
    let status = 'pending';
    const items: Item[] = [];

    // Set up handlers
    setHandler(updateStatusSignal, (newStatus: string) => {
    status = newStatus;
    });

    setHandler(addItemSignal, (item: Item) => {
    items.push(item);
    });

    setHandler(getItemsQuery, () => items);
    setHandler(getStatusQuery, () => status);

    // Wait for completion signal
    await condition(() => status === 'completed');
    }

    Inject TemporalService in your NestJS services to interact with workflows:

    @Injectable()
    export class OrderService {
    constructor(private readonly temporal: TemporalService) {}

    async createOrder(orderData: CreateOrderData) {
    // Start workflow - note the method signature
    const result = await this.temporal.startWorkflow(
    'processOrderWorkflow', // Workflow function name
    [orderData], // Arguments array
    { // Options
    workflowId: `order-${Date.now()}`,
    taskQueue: 'order-queue',
    }
    );

    return {
    workflowId: result.result.workflowId,
    runId: result.result.runId,
    };
    }

    async queryOrderStatus(workflowId: string) {
    const result = await this.temporal.queryWorkflow(
    workflowId,
    'getOrderStatus'
    );

    return result.result;
    }

    async cancelOrder(workflowId: string, reason: string) {
    await this.temporal.signalWorkflow(
    workflowId,
    'cancelOrder',
    [reason]
    );
    }
    }

    🔝 Back to top

    For detailed API documentation, visit the Full API Documentation.

    The main unified service providing access to all Temporal functionality. See the API Documentation for complete method signatures and examples.

    Key methods:

    • startWorkflow() - Start a workflow execution
    • signalWorkflow() - Send a signal to a running workflow
    • queryWorkflow() - Query a running workflow
    • getWorkflowHandle() - Get a workflow handle to interact with it
    • terminateWorkflow() - Terminate a workflow execution
    • cancelWorkflow() - Cancel a workflow execution
    • getHealth() - Get service health status
    • createSchedule() - Create a schedule
    • listSchedules() - List all schedules
    • deleteSchedule() - Delete a schedule

    🔝 Back to top

    Check out our complete example repository featuring:

    • Real-world implementations - Production-ready examples
    • Multiple use cases - E-commerce, notifications, reports, and more
    • Best practices - Following all recommended patterns
    • Docker setup - Ready-to-run with docker-compose
    • Test coverage - Comprehensive test examples

    For more examples, visit our documentation. Key example scenarios include:

    1. E-commerce Order Processing - Complete example with compensation logic
    2. Scheduled Reports - Creating and managing scheduled workflows
    3. Activity Retry Configuration - Custom retry policies
    4. Child Workflows - Organizing complex workflows
    5. Continue-As-New - For long-running workflows
    6. Custom Error Handling - Implementing custom error types

    🔝 Back to top

    // workflow.ts
    const paymentActivities = proxyActivities<typeof PaymentActivity.prototype>({
    startToCloseTimeout: '5m',
    retry: {
    maximumAttempts: 5,
    initialInterval: '1s',
    maximumInterval: '1m',
    backoffCoefficient: 2,
    nonRetryableErrorTypes: ['InvalidPaymentMethod', 'InsufficientFunds'],
    },
    });
    import { TestWorkflowEnvironment } from '@temporalio/testing';
    import { Worker } from '@temporalio/worker';
    import { processOrderWorkflow } from './order.workflow';

    describe('Order Workflow', () => {
    let testEnv: TestWorkflowEnvironment;

    beforeAll(async () => {
    testEnv = await TestWorkflowEnvironment.createTimeSkipping();
    });

    afterAll(async () => {
    await testEnv?.teardown();
    });

    it('should process order successfully', async () => {
    const { client, nativeConnection } = testEnv;

    // Mock activities
    const mockOrderActivity = {
    validatePayment: async () => ({ valid: true }),
    reserveInventory: async () => ({ reservationId: 'res-123' }),
    chargePayment: async () => ({ transactionId: 'txn-123' }),
    sendConfirmationEmail: async () => {},
    };

    const worker = await Worker.create({
    connection: nativeConnection,
    taskQueue: 'test',
    workflowsPath: require.resolve('./order.workflow'),
    activities: mockOrderActivity,
    });

    await worker.runUntil(async () => {
    const result = await client.workflow.execute(processOrderWorkflow, {
    workflowId: 'test-order-1',
    taskQueue: 'test',
    args: [{
    orderId: 'order-123',
    payment: { amount: 100, currency: 'USD' },
    items: [{ id: '1', quantity: 1 }],
    }],
    });

    expect(result.status).toBe('completed');
    expect(result.transactionId).toBe('txn-123');
    });
    });
    });

    🔝 Back to top

    ✅ DO:

    • Keep workflows deterministic (no random numbers, current time, network calls)
    • Use activities for any non-deterministic operations
    • Keep workflow history size manageable (use continue-as-new for long-running workflows)
    • Export workflow functions (not classes)
    • Use defineSignal and defineQuery at module level

    ❌ DON'T:

    • Don't use @Injectable() on workflow functions
    • Don't inject NestJS services in workflows
    • Don't use Math.random() or Date.now() directly in workflows
    • Don't make HTTP calls or database queries directly in workflows

    ✅ DO:

    • Make activities idempotent (safe to retry)
    • Use @Injectable() and leverage NestJS DI
    • Use @Activity() and @ActivityMethod() decorators
    • Handle errors appropriately
    • Log activity execution for debugging

    ❌ DON'T:

    • Don't make activities too granular (network overhead)
    • Don't rely on activity execution order guarantees
    • Don't share mutable state between activity invocations

    ✅ DO:

    • Use async configuration for environment-based setup
    • Configure appropriate timeouts for your use case
    • Set up proper retry policies
    • Enable graceful shutdown hooks
    • Use task queues to organize work

    ❌ DON'T:

    • Don't hardcode connection strings
    • Don't use the same task queue for all workflows
    • Don't ignore timeout configurations

    ✅ DO:

    • Implement compensation logic in workflows
    • Use appropriate retry policies
    • Log errors with context
    • Define non-retryable error types
    • Handle activity failures gracefully

    ❌ DON'T:

    • Don't swallow errors silently
    • Don't retry indefinitely
    • Don't ignore business-level failures

    ✅ DO:

    • Write unit tests for activities
    • Use TestWorkflowEnvironment for integration tests
    • Mock external dependencies
    • Test failure scenarios
    • Test signal and query handlers

    ❌ DON'T:

    • Don't skip workflow testing
    • Don't test against production Temporal server
    • Don't assume workflows are correct without testing

    🔝 Back to top

    The package includes comprehensive health monitoring capabilities for production deployments.

    // app.module.ts
    import { Module } from '@nestjs/common';
    import { TemporalModule } from 'nestjs-temporal-core';
    import { TemporalHealthModule } from 'nestjs-temporal-core/health';

    @Module({
    imports: [
    TemporalModule.register({
    connection: { address: 'localhost:7233' },
    taskQueue: 'my-queue',
    worker: {
    workflowsPath: require.resolve('./workflows'),
    activityClasses: [MyActivity],
    },
    }),
    TemporalHealthModule, // Adds /health/temporal endpoint
    ],
    })
    export class AppModule {}
    @Controller('health')
    export class HealthController {
    constructor(private readonly temporal: TemporalService) {}

    @Get('/status')
    async getHealthStatus() {
    const health = this.temporal.getHealth();

    return {
    status: health.overallHealth,
    timestamp: new Date(),
    services: {
    client: {
    healthy: health.client.status === 'healthy',
    connection: health.client.connectionStatus,
    },
    worker: {
    healthy: health.worker.status === 'healthy',
    state: health.worker.state,
    activitiesRegistered: health.worker.activitiesCount,
    },
    discovery: {
    healthy: health.discovery.status === 'healthy',
    activitiesDiscovered: health.discovery.activitiesDiscovered,
    },
    },
    uptime: health.uptime,
    };
    }
    }

    🔝 Back to top

    Problem: Cannot connect to Temporal server

    Solutions:

    // Check connection configuration
    const health = temporalService.getHealth();
    console.log('Connection status:', health.client.connectionStatus);

    // Verify Temporal server is running
    // docker ps | grep temporal

    // Check connection settings
    TemporalModule.register({
    connection: {
    address: process.env.TEMPORAL_ADDRESS || 'localhost:7233',
    namespace: 'default',
    },
    })

    Problem: Workflow cannot find registered activities

    Solutions:

    // 1. Ensure activity is in activityClasses array
    TemporalModule.register({
    worker: {
    activityClasses: [MyActivity], // Must include the activity class
    },
    })

    // 2. Verify activity is registered as provider
    @Module({
    providers: [MyActivity], // Must be in providers array
    })

    // 3. Check activity decorator
    @Activity({ name: 'my-activities' })
    export class MyActivity {
    @ActivityMethod('myActivity')
    async myActivity() { }
    }

    // 4. Check discovery status
    const health = temporalService.getHealth();
    console.log('Activities discovered:', health.discovery.activitiesDiscovered);

    Problem: Workflow not found or not executing

    Solutions:

    // 1. Ensure workflowsPath is correct
    TemporalModule.register({
    worker: {
    workflowsPath: require.resolve('./workflows'), // Must resolve to workflows file/directory
    },
    })

    // 2. Export workflow function properly
    // workflows/index.ts
    export { processOrderWorkflow } from './order.workflow';
    export { reportWorkflow } from './report.workflow';

    // 3. Use correct workflow name when starting
    await temporal.startWorkflow(
    'processOrderWorkflow', // Must match exported function name
    [args],
    options
    );

    Problem: Activities or workflows timing out

    Solutions:

    // Configure appropriate timeouts
    const activities = proxyActivities<typeof MyActivity.prototype>({
    startToCloseTimeout: '10m', // Increase for long-running activities
    scheduleToCloseTimeout: '15m', // Total time including queuing
    scheduleToStartTimeout: '5m', // Time waiting in queue
    });

    // For workflows
    await temporal.startWorkflow('myWorkflow', [args], {
    workflowExecutionTimeout: '24h', // Max total execution time
    workflowRunTimeout: '12h', // Max single run time
    workflowTaskTimeout: '10s', // Decision task timeout
    });

    Enable comprehensive debugging:

    TemporalModule.register({
    logLevel: 'debug',
    enableLogger: true,
    connection: {
    address: 'localhost:7233',
    },
    worker: {
    debugMode: true, // If available
    },
    })

    // Check detailed health and statistics
    const health = temporalService.getHealth();
    const stats = temporalService.getStatistics();
    console.log('Health:', JSON.stringify(health, null, 2));
    console.log('Stats:', JSON.stringify(stats, null, 2));

    If you're still experiencing issues:

    1. Check the logs - Enable debug logging to see detailed information
    2. Verify configuration - Double-check all connection and worker settings
    3. Test connectivity - Ensure Temporal server is accessible
    4. Review health status - Use getHealth() to identify failing components
    5. Check GitHub Issues - Search existing issues
    6. Create an issue - Provide logs, configuration, and minimal reproduction

    🔝 Back to top

    Version 3.0.12 introduces support for multiple workers without breaking existing single-worker configurations.

    Your existing configuration continues to work:

    // ✅ This still works exactly as before
    TemporalModule.register({
    connection: { address: 'localhost:7233' },
    taskQueue: 'my-queue',
    worker: {
    workflowsPath: require.resolve('./workflows'),
    activityClasses: [MyActivity],
    },
    })

    After (v3.0.12):

    // Option 1: Configure multiple workers in module
    TemporalModule.register({
    connection: { address: 'localhost:7233' },
    workers: [
    {
    taskQueue: 'main-queue',
    workflowsPath: require.resolve('./workflows/main'),
    activityClasses: [MainActivity],
    },
    {
    taskQueue: 'schedule-queue',
    workflowsPath: require.resolve('./workflows/schedule'),
    activityClasses: [ScheduleActivity],
    },
    ],
    })
    // Get native connection for custom worker creation
    const workerManager = temporal.getWorkerManager();
    const connection: NativeConnection | null = workerManager.getConnection();

    // Get specific worker by task queue
    const worker: Worker | null = temporal.getWorker('payments-queue');

    // Get all workers information
    const workersInfo: MultipleWorkersInfo = temporal.getAllWorkers();
    console.log(`${workersInfo.runningWorkers}/${workersInfo.totalWorkers} workers running`);

    // Control specific workers
    await temporal.startWorkerByTaskQueue('payments-queue');
    await temporal.stopWorkerByTaskQueue('notifications-queue');

    // Register new worker dynamically
    const result = await temporal.registerWorker({
    taskQueue: 'new-queue',
    workflowsPath: require.resolve('./workflows/new'),
    activityClasses: [NewActivity],
    autoStart: true,
    });

    🔝 Back to top

    • Node.js: >= 16.0.0
    • NestJS: >= 9.0.0
    • Temporal Server: >= 1.20.0

    We welcome contributions! To contribute:

    1. Fork the repository
    2. Create a feature branch (git checkout -b feature/amazing-feature)
    3. Make your changes
    4. Run tests (npm test)
    5. Commit your changes (git commit -m 'Add amazing feature')
    6. Push to the branch (git push origin feature/amazing-feature)
    7. Open a Pull Request
    # Clone the repository
    git clone https://github.com/harsh-simform/nestjs-temporal-core.git
    cd nestjs-temporal-core

    # Install dependencies
    npm install

    # Run tests
    npm test

    # Run tests with coverage
    npm run test:cov

    # Build the package
    npm run build

    # Generate documentation
    npm run docs:generate

    🔝 Back to top

    This project is licensed under the MIT License - see the LICENSE file for details.


    ⭐ Star us on GitHub if you find this project helpful!

    Made with ❤️ by the Harsh Simform