Getting started with NestJS
Compose Playstack authentication, account scope, and entitlement guards into a NestJS API with explicit dependency order.
This guide builds a protected account-scoped route. It deliberately keeps the configured domain services outside NestJS so they can also be used by jobs, scripts, tests, or another application edge.
1. Install capability and binding pairs
Before you start: use an existing NestJS app with configured Auth sessions, Accounts and Entitlements. The sessions, accounts and entitlements names below refer to those application-owned instances. These are composition examples, not a complete identity backend or database migration.
New to Playstack? Run the storage quickstart first. Confirm package access; the Accounts pair requires paid access, while a first capability does not have to use Accounts.
npm install \
@playstack/auth @playstack/nest-auth \
@playstack/accounts @playstack/nest-accounts \
@playstack/entitlements @playstack/nest-entitlementsKeep every Playstack dependency on the same fixed version. Install only the pairs used by the first route; events, audit, queues, billing, and analytics can be composed later without changing these boundaries.
2. Create portable services
In an application composition module, configure sessions, accounts, and entitlements with application-owned persistence and infrastructure. These values are normal TypeScript services, not Nest-specific singletons.
Register their bindings at the application root:
import { Module } from '@nestjs/common'
import { PlaystackAuthModule } from '@playstack/nest-auth'
import { PlaystackAccountsModule } from '@playstack/nest-accounts'
import { PlaystackEntitlementsModule } from '@playstack/nest-entitlements'
import type { AccountRequest } from '@playstack/nest-accounts'
@Module({
imports: [
PlaystackAuthModule.forRoot({
sessions,
allowedOrigins: ['https://app.example.com'],
}),
PlaystackAccountsModule.forRoot({ accounts }),
PlaystackEntitlementsModule.forRoot({
entitlements,
resolveSubject: (request) => {
const account = (request as AccountRequest).playstackAccount
if (!account) {
throw new Error('Account context must be resolved first')
}
return { type: 'account', id: account.account.id }
},
}),
],
})
export class ApplicationModule {}Use each package's forRootAsync form when construction depends on other Nest providers. The resulting factory should still return explicit configured services rather than hiding environment reads throughout feature modules.
3. Apply guards in trust order
import { Controller, Get, UseGuards } from '@nestjs/common'
import { PlaystackAuthGuard } from '@playstack/nest-auth'
import { PlaystackAccountGuard } from '@playstack/nest-accounts'
import { PlaystackEntitlementsGuard, RequireEntitlements } from '@playstack/nest-entitlements'
@Controller('/accounts/:accountId/packages')
export class PackagesController {
@Get('/pro')
@UseGuards(
PlaystackAuthGuard,
PlaystackAccountGuard,
PlaystackEntitlementsGuard,
)
@RequireEntitlements('packages.pro')
getProPackage(@CurrentAccount() account: AccountResolution) {
return this.packages.forAccount(account.account.id)
}
}PlaystackAccountGuard revalidates membership rather than trusting the route parameter or stale session metadata. The entitlement subject resolver reads the context established by earlier guards; it does not infer an account from the URL.
For unsafe cookie-authenticated requests, run PlaystackCsrfGuard before PlaystackAuthGuard, as the cookie composition does, so a forged request is refused before any session is read; routes that set or clear session cookies add PlaystackCookieCsrfGuard, which is never bearer-exempt. Bearer-authenticated requests do not use ambient browser credentials and follow the auth adapter's fixed CSRF behavior.
The application owns the composition. Hand-place guards as above, or name a composition once and place PlaystackGuardStack wherever it belongs: guards: 'cookie' or 'bearer' selects a default per transport profile, and an ordered list mixes package guards, other bindings and your own guards, for example [PlaystackOriginGuard, TenantGuard, PlaystackAuthGuard, PlaystackAccountGuard]. globalGuards: true installs the composed stack globally, with @Public() opting routes out of the session check. No package installs a guard on its own.
4. Add operational boundaries explicitly
- Register
@playstack/nest-errorswith an existing reporter and choose where its exception filter applies. - Register
@playstack/nest-eventswhen domain events need outbox pumping or queued observation. - Add
@playstack/nest-rate-limitonly after trusted caller and client-IP resolution are available. - Bind queued consumers in the worker process that owns them rather than starting every consumer inside the HTTP application.
Verify the boundary
- No Playstack module is silently global.
- Authentication precedes account, entitlement, role, and rate-limit checks.
- Every account-scoped request revalidates current membership.
- Database transactions remain application-visible.
- Decorators declare metadata but domain services perform mutations and emit events.
- Worker consumers and shutdown behavior are wired by the application.