# NestJS (https://docs.getaviato.com/integrations/nestjs)



Nest modules, dependency injection and decorators for the Aviato TypeScript SDK.
Requires NestJS 12, Node.js 22.22.1 or newer, and an existing Aviato agent.
Installing this package does not install the agent.

```sh
npm install @getaviato/nestjs @getaviato/sdk
```

Ships ESM JavaScript and declarations. Node 22.22.1+ also supports loading the
package from CommonJS Nest applications. Enable `experimentalDecorators` and
`emitDecoratorMetadata` in your TypeScript configuration, as in a standard Nest app.

## Configure once [#configure-once]

```ts
import { Injectable, Module } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AviatoModule, AviatoAction, type ActionContext } from '@getaviato/nestjs';

@Injectable()
class CustomerActions {
    @AviatoAction({ collection: 'customers', name: 'Confirm selection', scope: 'single' })
    async confirm(context: ActionContext) {
        const records = await context.getRecords();
        return records.length === 1
            ? context.success('Customer selected')
            : context.error('Select one customer you can read');
    }
}

@Module({
    imports: [AviatoModule.forRoot({ secret: process.env.AVIATO_PLUGIN_SECRET! })],
    providers: [CustomerActions],
})
class AppModule {}

const app = await NestFactory.create(AppModule, { rawBody: true });
await app.listen(3000);
```

`rawBody: true` is required for signature verification. Keep Nest's built-in body
parser enabled. Express and Fastify are supported. Their parser limits apply before
the SDK's 10 MiB limit; configure them separately if your payloads exceed the host's
defaults. Configure `AVIATO_PLUGIN_URL=http://your-app:3000/aviato` and the same
`AVIATO_PLUGIN_SECRET` on the agent. A Nest global prefix is included in this URL:
`app.setGlobalPrefix('api')` means `/api/aviato`.

Set `path: 'internal/aviato'` to change the controller mount. Paths contain literal
segments, not route patterns. Async configuration uses the same static `path` option:

```ts
AviatoModule.forRootAsync({
    imports: [ConfigModule],
    inject: [ConfigService],
    path: 'internal/aviato',
    useFactory: (config: ConfigService) => ({
        secret: config.getOrThrow<string>('AVIATO_PLUGIN_SECRET'),
    }),
});
```

Import the root configuration exactly once. It exports a global `AviatoPlugin`
provider. Register decorated classes in any feature module's `providers`; discovery
runs at application bootstrap. Dependencies are ordinary Nest constructor injections.

## Plugins through Nest providers [#plugins-through-nest-providers]

Use `forFeature` in your domain module to build integrations with injected configuration:

```ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AviatoModule, StripePlugin } from '@getaviato/nestjs';

@Module({
    imports: [AviatoModule.forFeature({
        imports: [ConfigModule],
        providers: [{
            provide: StripePlugin,
            inject: [ConfigService],
            useFactory: (config: ConfigService) =>
                new StripePlugin({
                    credentialEnv: config.get<string>('STRIPE_WIDGET_ENV') ?? 'STRIPE_WIDGET_KEY',
                    readOnly: true,
                }).invoices({
                    collection: 'customers',
                    name: 'billing',
                    customerId: { field: 'stripe_customer_id' },
                }),
        }],
        plugins: [StripePlugin],
    })],
})
export class BillingModule {}
```

`plugins` is an explicit list of provider tokens implementing `IntegrationPlugin`.
Nothing is exposed merely because it is injectable. Each listed integration is
registered through the underlying SDK's `use()` method. `useValue`, `useClass`,
`useFactory` (including async factories), and `useExisting` are standard Nest providers.

For an existing plugin exported by another module, reuse its instance:

```ts
AviatoModule.forFeature({
    imports: [ExistingBillingModule], // exports BillingIntegration
    providers: [{ provide: 'AVIATO_BILLING', useExisting: BillingIntegration }],
    plugins: ['AVIATO_BILLING'],
});
```

You can also list `BillingIntegration` directly in `plugins` when the imported module
exports it. Parent module providers are not implicitly visible inside `forFeature`;
export them from an imported module or declare them in its `providers` option.
To reuse a plugin elsewhere, own and export it from your billing module, then import
that module into `forFeature`.

Construct the complete widget declaration in the constructor or factory, before
registration. Registration snapshots and validates widgets; later builder mutations
are not applied unless you explicitly call `AviatoPlugin.use()` again. Request-scoped
plugin dependencies are rejected at startup. Multiple plugins follow Nest dependency
initialization order, so use unique collection/name pairs across feature modules.
The underlying SDK replaces widgets with the same key; do not rely on import order
for overrides.

For prebuilt integrations, `forRoot({ secret, plugins: [stripe] })` is also available.
An integration implements `widgets()`; it does not install new provider execution
code into the agent. The Stripe credential itself stays in the customer agent's
environment. Configure a restricted key with Read/None permissions. Only its
environment-variable name belongs in Nest configuration.

## Decorators and the full SDK [#decorators-and-the-full-sdk]

| Decorator                       | Method arguments              | Returns                                 |
| ------------------------------- | ----------------------------- | --------------------------------------- |
| `@AviatoAction(options)`        | `ActionContext`               | Action result or undefined              |
| `@AviatoComputedField(options)` | records, caller context       | One JSON value per record               |
| `@AviatoHook(options)`          | hook context                  | Rejection, changed values, or undefined |
| `@AviatoSegment(options)`       | caller context                | Filter or record IDs                    |
| `@AviatoSearch(options)`        | query, caller context         | Filter                                  |
| `@AviatoChart(options)`         | filter, caller context        | Vega-Lite specification                 |
| `@AviatoWriteOverride(options)` | value, record, caller context | Record patch                            |

Options match the SDK definition, excluding the decorated callback. Methods may
return promises. One Aviato decorator is allowed per method. Duplicate decorated
keys fail at startup, except hooks, which may compose. Handlers must be singleton
providers with singleton dependencies; request-scoped and transient handlers are
rejected. Symbol-named methods are not discovered; use ordinary named methods.

Inject `AviatoPlugin` for custom datasources, summaries, relation hints, dynamic forms,
widgets and other SDK features. Register these in `onModuleInit()` before bootstrap.
Use the core SDK's types and `ui` helpers from `@getaviato/sdk`. An instance method
used as a dynamic form callback must be bound to its service when registered.

The HTTP controller participates in Nest's global guards, interceptors and filters.
Allow the signed Aviato endpoint through any browser-session-only guard using your
application's guard configuration. SDK signature verification remains mandatory.
Decorated provider methods are ordinary service calls: controller pipes, method guards
and interceptors are not automatically applied to them. Use `ActionContext` for the
Aviato caller, not Nest's request-scoped `REQUEST` token. Direct database or service
calls do not inherit Aviato record permissions; use `context.getRecords()` or
`context.data` for governed access.

See the [TypeScript SDK](/integrations/typescript) for the underlying APIs and [Stripe widgets](/integrations/widgets) for agent-side credentials and permissions.

For customer support data, [Zendesk widgets](/integrations/zendesk) provide a typed
`ZendeskPlugin` with user profiles, requested-ticket lists, and individual tickets.
Its OAuth scopes are verified by the customer agent before each data request.
