vTilt
By roleFoundersKnow what to fix firstMarketersSee what happens after the clickCMOsSee what your budget really buys
By businessSaaSTurn signups into active usersEcommerceSee why shoppers abandon cartsAgenciesProve your work to clientsEnterpriseOne customer record for every team
MeasureWeb AnalyticsFind where buyers leaveSession ReplaySee why they left, not just that they didAdsKnow what paid visitors do nextIntegrationsOne snippet feeds your stack
ThinkPeopleOne profile per customerCustomer MemoryvTilt remembers every visitorSite KnowledgeAnswers and audits from your own pages
ActAsk AIAnswers from real behaviorAI ChatChat that knows the visitorEmail CampaignsEmail from real visits
PricingWhy vTiltDocs
Docs / NestJS
Getting Started
OverviewInstallInitializeIdentify usersTrack eventsLogout & resetVerify eventsCommon mistakes
Guides
Event forwardingReverse proxyRealtime dashboardSite knowledge
Frontend frameworks
Next.jsNuxt.jsVue.jsReactReact RouterRemixGatsbySvelte / SvelteKitAstroAngularTanStack StartDocusaurus
Backend frameworks
NestJSHonoCloudflare WorkersDjangoFlaskLaravelPhoenixRuby on Rails
Backend languages
PythonPHPRubyElixirGoJava.NET / C#Rust
Stack guides
Vue + PHP
API Reference
Browser SDK
Script bundlesAutocaptureWeb VitalsSession recordingChat widgetFeature readinessRemote configurationDebug logging
Node SDK
Install & setupCapture, identify & aliasContext & shutdownGlobal properties & opt-outError tracking
MCP server
Guides
OverviewAuthenticationOAuthAgent skills (prompts)AI intelligenceSite knowledgeGoogle AdsMeta Ads
Client setup
CursorClaude DesktopVS CodeCodex

Getting Started

OverviewInstallInitializeIdentify usersTrack eventsLogout & resetVerify eventsCommon mistakes

Guides

Event forwardingReverse proxyRealtime dashboardSite knowledge
NestJSHonoCloudflare WorkersDjangoFlaskLaravelPhoenixRuby on Rails

API Reference

MCP server

On this page

On this page

  • 1. Install
  • 2. Create a service
  • 3. Register a global module
  • 4. Capture from a controller
  • 5. Identify users from a guard or interceptor
  • Next steps
DocsGuidesBackend frameworksNestJS

NestJS

Last updated July 22, 2026

Integrate vTilt with NestJS — injectable provider wrapping the Node SDK, request-scoped capture, graceful shutdown.

NestJS uses dependency injection, so the cleanest integration is a global module that exposes a singleton VTiltService. Your controllers and services then inject it like any other provider.

#1. Install

npm install @v-tilt/node
bash

#2. Create a service

// src/vtilt/vtilt.service.ts
import { Injectable, OnModuleDestroy } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { VTiltNode } from '@v-tilt/node'

@Injectable()
export class VTiltService implements OnModuleDestroy {
  private client: VTiltNode

  constructor(private config: ConfigService) {
    this.client = new VTiltNode(this.config.getOrThrow('VTILT_TRACKER_TOKEN'), {
      host: this.config.get('VTILT_HOST'),
    })
  }

  capture(input: Parameters<VTiltNode['capture']>[0]) {
    return this.client.capture(input)
  }

  identify(input: Parameters<VTiltNode['identify']>[0]) {
    return this.client.identify(input)
  }

  alias(input: Parameters<VTiltNode['alias']>[0]) {
    return this.client.alias(input)
  }

  async onModuleDestroy() {
    await this.client.shutdown()
  }
}
typescript

#3. Register a global module

// src/vtilt/vtilt.module.ts
import { Global, Module } from '@nestjs/common'
import { VTiltService } from './vtilt.service'

@Global()
@Module({
  providers: [VTiltService],
  exports: [VTiltService],
})
export class VTiltModule {}
typescript
// src/app.module.ts
import { Module } from '@nestjs/common'
import { ConfigModule } from '@nestjs/config'
import { VTiltModule } from './vtilt/vtilt.module'

@Module({
  imports: [ConfigModule.forRoot({ isGlobal: true }), VTiltModule],
})
export class AppModule {}
typescript
# .env
VTILT_TRACKER_TOKEN=YOUR_PROJECT_TOKEN
VTILT_HOST=https://www.vtilt.com
text

#4. Capture from a controller

// src/checkout/checkout.controller.ts
import { Body, Controller, Post } from '@nestjs/common'
import { VTiltService } from '../vtilt/vtilt.service'

@Controller('checkout')
export class CheckoutController {
  constructor(private vt: VTiltService) {}

  @Post()
  complete(@Body() body: { userId: string; amount: number }) {
    this.vt.capture({
      distinctId: body.userId,
      event: 'purchase_completed',
      properties: { amount: body.amount },
    })
    return { ok: true }
  }
}
typescript

#5. Identify users from a guard or interceptor

If you have an AuthGuard that resolves the current user, identify them once per request so any browser-side anonymous activity merges to the right person record.

// src/vtilt/vtilt.interceptor.ts
import {
  CallHandler,
  ExecutionContext,
  Injectable,
  NestInterceptor,
} from '@nestjs/common'
import { Observable } from 'rxjs'
import { VTiltService } from './vtilt.service'

@Injectable()
export class VTiltIdentifyInterceptor implements NestInterceptor {
  constructor(private vt: VTiltService) {}

  intercept(ctx: ExecutionContext, next: CallHandler): Observable<unknown> {
    const req = ctx.switchToHttp().getRequest()
    const user = req.user
    const anonymousId = req.cookies?.vt_anon

    if (user) {
      this.vt.identify({
        distinctId: user.id,
        anonymousId,
        properties: { email: user.email },
      })
    }
    return next.handle()
  }
}
typescript

Note

OnModuleDestroy runs shutdown() so the last batch flushes before the process exits. On serverless platforms (AWS Lambda, Cloud Run) call shutdown() after each handler too — see Node SDK / Context & shutdown.

#Next steps

  • Node SDK / Capture, identify & alias — server-side event shapes.
  • Identify & alias (browser) — anonymous → known user merge model.
  • Event forwarding — fan events out to GA4, Meta CAPI, PostHog.
  • Common mistakes — integration pitfalls to avoid before you ship.
PreviousDocusaurusIntegration guidesNextHonoIntegration guides