Under heavy development
StitchAPI
Integrations

NestJS

Wire stitches into a NestJS app with StitchModule — injectable stitches, a Logger trace bridge, ConfigService-backed secrets, and request-scoped multi-tenancy.

Use @stitchapi/nest when you want your stitches available through NestJS dependency injection — configured once, injected into services as typed dependencies, with the framework's Logger and ConfigService wired in and shutdown handled for you. It is a thin peer-dependency package that adds no capability of its own: it wires StitchAPI's shared-runtime primitive (one store, one trace sink, a trusted principal boundary) into Nest's DI graph, so core stays untouched.

Example

Install the package alongside core:

npm install @stitchapi/nest stitchapi

Configure the shared client once in your root module. forRootAsync resolves its options from an injected factory, which is how ConfigService reaches the base URL, secrets, and store. trace: 'logger' sends the event stream to the Nest Logger:

// app.module.ts
import {  } from '@nestjs/common';
import { ,  } from '@nestjs/config';
import { ,  } from '@stitchapi/nest';
import {  } from 'stitchapi';

@({
    : [
        .({
            : [],
            : [],
            : (: ) => ({
                : .('API_BASE_URL'),
                : (()('API_TOKEN')),
                : 'logger',
            }),
        }),
    ],
})
export class  {}

Declare a stitch with defineStitch, register it per feature with forFeature, and inject it as a typed dependency:

// users.module.ts
import { ,  } from '@nestjs/common';
import {
    ,
    type ,
    ,
    ,
} from '@stitchapi/nest';
import {  } from 'zod';

export const  = ('GET_USER', () =>
    .({
        : '/users/{id}',
        : .({ : .(), : .() }),
    }),
);

@({ : [.({ : [] })] })
export class  {}

@()
export class  {
    constructor(
        @()
        private readonly : <typeof >,
    ) {}

    (: string) {
        return this.({ : {  } });
    }
}

Injected<typeof GetUser> keeps the injection-site type tied to the definition, so the injected getUser is the same typed callable a stitch always is — awaited for the validated result, or .stream()-ed for the event stream.

Call app.enableShutdownHooks() in main.ts. Without it Nest never fires its shutdown hook, so the shared trace sink is not flushed and the store is not closed on exit.

Options

forRoot({ store, trace, ...defaults }) configures the app-wide infrastructure — one store, one trace sink — plus the shared config (baseUrl, auth, retry, throttle, …) every stitch inherits; forRootAsync resolves the same options from an injected factory. forFeature({ stitches, seam }) registers injectable stitches and, optionally, a per-upstream seam built over the shared store and trace — omit seam to attach the stitches to the default one.

Two bridges connect the framework, and neither changes core:

  • trace: 'logger' forwards a stitch's event stream to the Nest Logger. It logs method, URL, and status only, and strips the query string; tracing is otherwise off by default.
  • fromConfig(config)('KEY') is a ConfigService-backed secret resolver — the ConfigService twin of env(). It resolves synchronously at call time, so the credential never lands on the config or in a trace.

For multi-tenancy, bind a principal per request with seam.as(...). The principal scopes sessions and cache over the one shared store, so each tenant gets its own login while the connection pool stays shared:

import {  } from '@nestjs/common';
import {  } from '@nestjs/core';
import {  } from '@stitchapi/nest';
import type { Seam } from 'stitchapi';

const  = {
    : 'TENANT_SEAM',
    : .,
    : (: Seam, : { ?: { ?: string } }) =>
        .(.?. ?? 'anonymous'),
    : [, ],
};

Anti-pattern: don't swap the store per request to isolate tenants — bind the principal with seam.as(tenantId) instead. Multi-tenancy is principal-scoped over one shared store (per-tenant sessions and cache keys, a single connection pool); a per-request store throws all of that away. Outside an HTTP request — a queue worker or a scheduled job — there is no request to read, so bind the principal explicitly from the job's own data.

See also

On this page