Skip to main content

Quick Start

Install the package and the Vercel AI SDK:

npm i @nest-native/ai-sdk ai

Install the Nest peers:

npm i @nestjs/common @nestjs/core reflect-metadata rxjs

Install the HTTP adapter your application uses:

npm i @nestjs/platform-express
# or @nestjs/platform-fastify

The published package keeps "dependencies": {}. The AI SDK and the NestJS packages are declared as peerDependencies, so your application installs only the ecosystems it actually uses.

Register AiModule​

Register the module once in your root module. It wires the global defaults that @AiStream reads from (default headers, default in-stream error mapping).

import { Module } from '@nestjs/common';
import { AiModule } from '@nest-native/ai-sdk';

@Module({
imports: [
AiModule.forRoot({
defaultHeaders: { 'x-powered-by': 'nest-native-ai-sdk' },
}),
],
})
export class AppModule {}

Stream From A Handler​

Decorate a handler with @AiStream() and return an AI SDK stream result (for example from streamText). The decorator pipes it to the active HTTP adapter — guards, pipes, interceptors, and exception filters all run first.

import { AiStream } from '@nest-native/ai-sdk';
import { Body, Controller, Post, UseGuards } from '@nestjs/common';
import { streamText } from 'ai';

@Controller('chat')
export class ChatController {
@Post()
@AiStream()
@UseGuards(ApiKeyGuard)
chat(@Body() body: ChatDto) {
// The guard runs before the stream opens — a rejection is HTTP 401/403,
// never an SSE error frame.
return streamText({ model, prompt: body.prompt });
}
}

ui-message is the default wire format — the protocol consumed by @ai-sdk/react's useChat. To stream plain text deltas instead, opt in with @AiStream({ format: 'text' }). See Stream Formats.

Cancel On Disconnect​

Inject @AiAbortSignal() and forward it into the AI SDK call so a client disconnect cancels the upstream model request (and stops billing):

import { AiAbortSignal, AiStream } from '@nest-native/ai-sdk';
import { Body, Controller, Post } from '@nestjs/common';
import { streamText } from 'ai';

@Controller('chat')
export class ChatController {
@Post()
@AiStream()
chat(@Body() body: ChatDto, @AiAbortSignal() signal: AbortSignal) {
return streamText({ model, prompt: body.prompt, abortSignal: signal });
}
}

See @AiAbortSignal.

Async Configuration​

Use forRootAsync() when the configuration depends on other providers:

AiModule.forRootAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
defaultHeaders: { 'x-app': config.getOrThrow('APP_NAME') },
}),
});

Both registrations return a global DynamicModule by default. Pass isGlobal: false to scope it to a single module boundary.

Next Steps​