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 stitchapiConfigure 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 NestLogger. It logs method, URL, and status only, and strips the query string; tracing is otherwise off by default.fromConfig(config)('KEY')is aConfigService-backed secret resolver — theConfigServicetwin ofenv(). 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.