Skip to main content

View Watcher

The View Watcher tracks template rendering operations in your NestJS application, monitoring performance, cache hits, and output metrics.

What Gets Captured

  • Template name/path
  • Output format (HTML, JSON, XML, PDF)
  • Rendering duration
  • Rendering status (rendered, error)
  • Template data size
  • Output size
  • Template locals/data (if enabled)
  • Cache hit status
  • Error messages

Configuration

NestLensModule.forRoot({
watchers: {
view: {
enabled: true,
captureData: false, // Set to true to capture template locals
},
},
})

Configuration Options

OptionTypeDefaultDescription
enabledbooleanfalseEnable/disable view tracking
captureDatabooleanfalseCapture template data/locals

Payload Structure

interface ViewEntry {
type: 'view';
payload: {
template: string; // Template name/path
format: 'html' | 'json' | 'xml' | 'pdf';
duration: number; // Render time (ms)
status: 'rendered' | 'error';
dataSize?: number; // Input data size (bytes)
outputSize?: number; // Rendered output size (bytes)
locals?: Record<string, unknown>; // Template data (if captureData: true)
cacheHit?: boolean; // Template cache hit
error?: string; // Error message
};
}

Usage Example

Setup View Engine

// Install: npm install hbs
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'path';

async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);

app.setBaseViewsDir(join(__dirname, '..', 'views'));
app.setViewEngine('hbs');

await app.listen(3000);
}

Provide View Engine to NestLens

import { NESTLENS_VIEW_ENGINE } from 'nestlens';

@Module({
providers: [
{
provide: NESTLENS_VIEW_ENGINE,
useFactory: (app: NestExpressApplication) => app.engine,
inject: [/* app instance */],
},
],
})
export class AppModule {}

Rendering Templates

@Controller()
export class AppController {
@Get()
@Render('index')
getIndex() {
// Automatically tracked
return {
title: 'Welcome',
message: 'Hello World',
};
}

@Get('user/:id')
@Render('user-profile')
async getUserProfile(@Param('id') id: string) {
const user = await this.userService.findOne(id);
return {
user,
timestamp: new Date(),
};
}
}

Manual Rendering

@Controller()
export class ReportController {
@Get('report')
async generateReport(@Res() res: Response) {
const data = await this.reportService.getData();

// Tracked when rendered
res.render('report', {
data,
generatedAt: new Date(),
});
}
}

Dashboard View

In the NestLens dashboard, view entries show:

  • Template rendering timeline
  • Most rendered templates
  • Slow templates
  • Rendering performance trends
  • Output size distribution
  • Cache hit rates
  • Error rates

Performance Analysis

Identify Slow Templates

// Dashboard shows which templates are slow to render
@Get('complex')
@Render('complex-report')
async complexReport() {
// If this takes >500ms, it appears in "Slow Views"
const data = await this.generateComplexData();
return { data };
}

Monitor Output Sizes

// Dashboard tracks output size
@Get('large-list')
@Render('user-list')
async userList() {
const users = await this.userService.findAll();
// Dashboard shows if rendered output is very large
return { users };
}

Template Formats

The watcher auto-detects format from template name:

@Render('report.pdf')     // format: 'pdf'
@Render('data.xml') // format: 'xml'
@Render('api.json') // format: 'json'
@Render('page.html') // format: 'html' (default)

Template Data Capture

When captureData: true, template locals are captured (limited to 4KB):

NestLensModule.forRoot({
watchers: {
view: {
captureData: true,
},
},
})

@Get()
@Render('index')
getIndex() {
return {
user: { id: 123, name: 'John' }, // Captured in dashboard
settings: { theme: 'dark' },
};
}

Cache Tracking

Track template caching effectiveness:

// Some view engines cache compiled templates
// Dashboard shows cache hit rates
@Get('cached')
@Render('frequently-used-template')
getData() {
// Subsequent renders show cacheHit: true
return { data: 'value' };
}

Error Handling

@Get('user/:id')
@Render('user-profile')
async getUserProfile(@Param('id') id: string) {
try {
const user = await this.userService.findOne(id);
if (!user) {
throw new NotFoundException('User not found');
}
return { user };
} catch (error) {
// Rendering error tracked automatically
throw error;
}
}