---
title: "Getting started with NestJS"
description: "Compose Playstack authentication, account scope, and entitlement guards into a NestJS API with explicit dependency order."
tags: ["frameworks","nestjs","getting-started","auth","accounts","entitlements"]
---

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](/docs/packages/identity/auth), [Accounts](/docs/packages/identity/accounts) and [Entitlements](/docs/packages/identity/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](/docs/getting-started#your-first-working-example) first. Confirm [package access](/docs/packages#access-policy); the Accounts pair requires paid access, while a first capability does not have to use Accounts.

```sh
npm install \
  @playstack/auth @playstack/nest-auth \
  @playstack/accounts @playstack/nest-accounts \
  @playstack/entitlements @playstack/nest-entitlements
```

Keep 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:

```ts
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

```ts
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-errors`](/docs/packages/foundation/nest-errors) with an existing reporter and choose where its exception filter applies.
- Register [`@playstack/nest-events`](/docs/packages/events-operations/nest-events) when domain events need outbox pumping or queued observation.
- Add [`@playstack/nest-rate-limit`](/docs/packages/events-operations/nest-rate-limit) only 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.
