NestJS Integration
@taskora/nestjs is the first-class Nest integration for taskora. It wires the full producer + consumer surface into Nest's DI graph so your task code feels native to the framework — no bullmq-style boilerplate, no factory ceremony, no duplicate type annotations.
Everything taskora exposes (dispatching, handlers, events, middleware, inspector, DLQ, schedules, the admin dashboard, and an end-to-end test harness) is injectable. Constructor DI just works — including for @TaskConsumer classes that run as workers.
30-second tour
// src/tasks/contracts.ts
import { defineTask } from "taskora"
import { z } from "zod"
export const sendEmailTask = defineTask({
name: "send-email",
input: z.object({ to: z.string().email(), subject: z.string() }),
output: z.object({ messageId: z.string() }),
})// src/email/email.consumer.ts
import { TaskConsumer, OnTaskEvent } from "@taskora/nestjs"
import type { InferInput, InferOutput, Taskora } from "taskora"
import { MailerService } from "./mailer.service"
import { sendEmailTask } from "../tasks/contracts"
@TaskConsumer(sendEmailTask, { concurrency: 10 })
export class SendEmailConsumer {
constructor(private readonly mailer: MailerService) {}
async process(
data: InferInput<typeof sendEmailTask>,
_ctx: Taskora.Context,
): Promise<InferOutput<typeof sendEmailTask>> {
return this.mailer.send(data)
}
@OnTaskEvent("completed")
onSent() {
// metrics, logs, whatever — DI dependencies are alive here
}
}// src/email/email.service.ts
import { Injectable } from "@nestjs/common"
import { TaskoraRef } from "@taskora/nestjs"
import { sendEmailTask } from "../tasks/contracts"
@Injectable()
export class EmailService {
constructor(private readonly tasks: TaskoraRef) {}
async notifySignup(user: { email: string; name: string }) {
// Full type safety — no manual BoundTask<I, O> annotation.
await this.tasks.for(sendEmailTask).dispatch({
to: user.email,
subject: `Welcome, ${user.name}`,
})
}
}// src/app.module.ts
import { Module } from "@nestjs/common"
import { TaskoraModule } from "@taskora/nestjs"
import { redisAdapter } from "taskora/redis"
import { Redis } from "ioredis"
import { SendEmailConsumer } from "./email/email.consumer"
import { EmailService } from "./email/email.service"
import { MailerService } from "./email/mailer.service"
@Module({
imports: [
TaskoraModule.forRoot({
adapter: redisAdapter({ client: new Redis(process.env.REDIS_URL!) }),
}),
],
providers: [SendEmailConsumer, EmailService, MailerService],
})
export class AppModule {}That's it. main.ts is standard Nest bootstrap:
import "reflect-metadata"
import { NestFactory } from "@nestjs/core"
import { AppModule } from "./app.module"
async function bootstrap() {
const app = await NestFactory.create(AppModule)
app.enableShutdownHooks() // so @taskora/nestjs can drain jobs on SIGTERM
await app.listen(3000)
}
bootstrap()What you get
TaskoraModule.forRoot/forRootAsync— register taskora in the Nest container exactly likeTypeOrmModuleorBullModule.TaskoraRef.for(contract)— zero-decorator, fully inferred dispatchers. Replaces the duplicatedBoundTask<I, O>annotations you'd get from property-style injection.@TaskConsumer(contract)— mark any provider as a worker handler. Full DI in the constructor,process(data, ctx)method runs inside the real worker loop.@OnTaskEvent('completed' | 'failed' | …)— method-level bindings for per-task events.- Class middleware —
@TaskMiddleware()providers composed viaforRoot({ middleware }), Koa-style onion chain with live DI dependencies. - Observability accessors —
Inspector,DeadLetterManager, and the schedule manager are injectable via class tokens (zero decorator) or@InjectInspector/@InjectDeadLetters/@InjectSchedulesfor named apps. TaskoraBoardModule— optional dynamic import of@taskora/board; mount the admin dashboard frommain.tswith three lines.- Multi-app — every decorator and module method takes an optional
nameso one Nest container can host multiple independent taskora apps (separate Redis clusters, per-tenant isolation, etc.). @taskora/nestjs/testing— opt-in subpath withTaskoraTestHarness, a pre-wired memory-adapter testing module that runs the real producer + consumer pipeline end-to-end in milliseconds, no Redis / Docker required.
Installation
npm install @taskora/nestjs taskora reflect-metadatayarn add @taskora/nestjs taskora reflect-metadatapnpm add @taskora/nestjs taskora reflect-metadatabun add @taskora/nestjs taskora reflect-metadataFor the Redis adapter (production):
npm install taskora ioredisyarn add taskora ioredispnpm add taskora ioredisbun add taskora ioredisFor the admin dashboard (optional):
npm install @taskora/board hono @hono/node-serveryarn add @taskora/board hono @hono/node-serverpnpm add @taskora/board hono @hono/node-serverbun add @taskora/board hono @hono/node-serverreflect-metadata is a peer dependency — Nest needs it loaded before any decorated class is evaluated. Import it once at the top of main.ts:
import "reflect-metadata"TypeScript configuration
Nest's constructor DI relies on emitted decorator metadata. Make sure your tsconfig.json has both flags:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}Without emitDecoratorMetadata, constructor(private tasks: TaskoraRef) silently resolves to undefined and every job throws on the first dispatch. If you're using SWC or esbuild (e.g. vitest default transform), you need the equivalent flag in their configs — see Testing for details.
Next steps
- File layout — recommended project structure for contracts, consumers, and modules.
- Dispatching —
TaskoraRef.for(),@InjectTask, andforFeature. - Consumers —
@TaskConsumer,@OnTaskEvent, lifecycle. - Middleware — class middleware with DI.
- Observability — inspector, DLQ, schedules as injectable services.
- Admin dashboard — mounting
@taskora/boardin a Nest app. - Testing —
TaskoraTestHarnesspatterns. - Deployment — multi-app, producer/worker split, graceful shutdown.