0
# Interfaces and Configuration
1
2
Enhanced interfaces for NestJS applications with Fastify-specific functionality and configuration options for static assets, view engines, and body parsing.
3
4
## Capabilities
5
6
### NestFastifyApplication Interface
7
8
Enhanced NestJS application interface that provides Fastify-specific methods and functionality.
9
10
```typescript { .api }
11
/**
12
* Enhanced NestJS application interface with Fastify-specific methods
13
* Extends the standard INestApplication with Fastify adapter capabilities
14
*/
15
interface NestFastifyApplication<TServer extends RawServerBase = RawServerDefault>
16
extends INestApplication<TServer> {
17
18
/**
19
* Returns the underlying HTTP adapter bound to the Fastify app
20
*/
21
getHttpAdapter(): HttpServer<FastifyRequest, FastifyReply, FastifyInstance>;
22
23
/**
24
* Registers a Fastify plugin with the application
25
* Wrapper around the native fastify.register() method
26
* @param plugin - Fastify plugin (callback, async, or promise-wrapped)
27
* @param opts - Plugin registration options
28
*/
29
register<Options extends FastifyPluginOptions = any>(
30
plugin:
31
| FastifyPluginCallback<Options>
32
| FastifyPluginAsync<Options>
33
| Promise<{ default: FastifyPluginCallback<Options> }>
34
| Promise<{ default: FastifyPluginAsync<Options> }>,
35
opts?: FastifyRegisterOptions<Options>
36
): Promise<FastifyInstance>;
37
38
/**
39
* Registers custom body parsers on the fly
40
* Respects the application's rawBody option
41
* @param type - Content type(s) to parse
42
* @param options - Body parser options
43
* @param parser - Custom parser function
44
*/
45
useBodyParser<TServer extends RawServerBase = RawServerBase>(
46
type: string | string[] | RegExp,
47
options?: NestFastifyBodyParserOptions,
48
parser?: FastifyBodyParser<Buffer, TServer>
49
): this;
50
51
/**
52
* Configures static asset serving
53
* @param options - Static asset configuration options
54
*/
55
useStaticAssets(options: FastifyStaticOptions): this;
56
57
/**
58
* Enables Cross-Origin Resource Sharing (CORS)
59
* @param options - CORS configuration options
60
*/
61
enableCors(options?: FastifyCorsOptions): void;
62
63
/**
64
* Sets up a view engine for template rendering
65
* @param options - View engine configuration (object only, string will cause error)
66
*/
67
setViewEngine(options: FastifyViewOptions | string): this;
68
69
/**
70
* Injects a test request for testing purposes
71
* Returns a chain for building the request
72
*/
73
inject(): LightMyRequestChain;
74
75
/**
76
* Injects a test request with specific options
77
* @param opts - Request options or URL string
78
*/
79
inject(opts: InjectOptions | string): Promise<LightMyRequestResponse>;
80
81
/**
82
* Starts the application server
83
* Multiple overloads for different parameter combinations
84
*/
85
listen(
86
opts: FastifyListenOptions,
87
callback?: (err: Error | null, address: string) => void
88
): Promise<TServer>;
89
listen(opts?: FastifyListenOptions): Promise<TServer>;
90
listen(callback?: (err: Error | null, address: string) => void): Promise<TServer>;
91
listen(
92
port: number | string,
93
callback?: (err: Error | null, address: string) => void
94
): Promise<TServer>;
95
listen(
96
port: number | string,
97
address: string,
98
callback?: (err: Error | null, address: string) => void
99
): Promise<TServer>;
100
listen(
101
port: number | string,
102
address: string,
103
backlog: number,
104
callback?: (err: Error | null, address: string) => void
105
): Promise<TServer>;
106
}
107
```
108
109
**Usage Examples:**
110
111
```typescript
112
import { NestFactory } from '@nestjs/core';
113
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
114
import { AppModule } from './app.module';
115
import * as path from 'path';
116
117
async function bootstrap() {
118
// Create application with proper typing
119
const app = await NestFactory.create<NestFastifyApplication>(
120
AppModule,
121
new FastifyAdapter()
122
);
123
124
// Register Fastify plugins
125
await app.register(require('@fastify/helmet'));
126
await app.register(require('@fastify/rate-limit'), {
127
max: 100,
128
timeWindow: '1 minute'
129
});
130
131
// Configure body parsing
132
app.useBodyParser('application/xml', { bodyLimit: 1024 * 1024 });
133
134
// Setup static assets
135
app.useStaticAssets({
136
root: path.join(__dirname, '..', 'public'),
137
prefix: '/static/',
138
});
139
140
// Enable CORS
141
app.enableCors({
142
origin: ['http://localhost:3000', 'https://example.com'],
143
credentials: true
144
});
145
146
// Setup view engine
147
app.setViewEngine({
148
engine: {
149
handlebars: require('handlebars')
150
},
151
templates: path.join(__dirname, '..', 'views'),
152
options: {
153
partials: {
154
header: 'partials/header.hbs',
155
footer: 'partials/footer.hbs'
156
}
157
}
158
});
159
160
// Start server
161
await app.listen({
162
port: 3000,
163
host: '0.0.0.0'
164
});
165
}
166
```
167
168
### Body Parser Configuration
169
170
Type definition for configuring custom body parsers.
171
172
```typescript { .api }
173
/**
174
* Options for configuring Fastify body parsers
175
* Excludes 'parseAs' property which is handled internally
176
*/
177
type NestFastifyBodyParserOptions = Omit<
178
Parameters<AddContentTypeParser>[1],
179
'parseAs'
180
>;
181
```
182
183
Common body parser options include:
184
185
```typescript
186
interface BodyParserOptions {
187
/** Maximum body size in bytes */
188
bodyLimit?: number;
189
/** Parser-specific configuration */
190
parseAs?: 'string' | 'buffer';
191
/** Content type parser configuration */
192
contentTypeParser?: {
193
// Parser implementation details
194
};
195
}
196
```
197
198
**Usage Examples:**
199
200
```typescript
201
// Register XML body parser
202
app.useBodyParser('application/xml', {
203
bodyLimit: 2 * 1024 * 1024, // 2MB limit
204
}, (req, body, done) => {
205
// Custom XML parsing logic
206
const xmlParser = require('fast-xml-parser');
207
try {
208
const parsed = xmlParser.parse(body.toString());
209
done(null, parsed);
210
} catch (error) {
211
done(error);
212
}
213
});
214
215
// Register multiple content types
216
app.useBodyParser(['text/plain', 'text/html'], {
217
bodyLimit: 1024 * 1024, // 1MB limit
218
}, (req, body, done) => {
219
done(null, body.toString());
220
});
221
```
222
223
### Static Assets Configuration
224
225
Configuration interface for serving static files through Fastify.
226
227
```typescript { .api }
228
/**
229
* Configuration options for static asset serving
230
* Based on @fastify/static plugin options
231
*/
232
interface FastifyStaticOptions extends SendOptions {
233
/** Root directory or directories for static files */
234
root: string | string[] | URL | URL[];
235
236
/** URL prefix for serving static files */
237
prefix?: string;
238
239
/** Avoid trailing slash in prefix */
240
prefixAvoidTrailingSlash?: boolean;
241
242
/** Whether to serve files (can be disabled for decorateReply only) */
243
serve?: boolean;
244
245
/** Add reply.sendFile method to reply object */
246
decorateReply?: boolean;
247
248
/** Hide from OpenAPI/JSON schema */
249
schemaHide?: boolean;
250
251
/** Function to set custom headers */
252
setHeaders?: (res: SetHeadersResponse, path: string, stat: Stats) => void;
253
254
/** Enable redirect from folder to folder/ */
255
redirect?: boolean;
256
257
/** Enable wildcard matching */
258
wildcard?: boolean;
259
260
/** Enable/configure directory listing */
261
list?: boolean | ListOptionsJsonFormat | ListOptionsHtmlFormat;
262
263
/** Function to filter allowed paths */
264
allowedPath?: (
265
pathName: string,
266
root: string,
267
request: FastifyRequest
268
) => boolean;
269
270
/** Look for pre-compressed files (.gz, .br) */
271
preCompressed?: boolean;
272
273
/** Route constraints for static serving */
274
constraints?: RouteOptions['constraints'];
275
276
// Inherited from SendOptions
277
acceptRanges?: boolean;
278
cacheControl?: boolean;
279
dotfiles?: 'allow' | 'deny' | 'ignore';
280
etag?: boolean;
281
extensions?: string[];
282
immutable?: boolean;
283
index?: string[] | string | false;
284
lastModified?: boolean;
285
maxAge?: string | number;
286
}
287
288
interface SetHeadersResponse {
289
getHeader: FastifyReply['getHeader'];
290
setHeader: FastifyReply['header'];
291
readonly filename: string;
292
statusCode: number;
293
}
294
295
interface ListOptionsJsonFormat {
296
format: 'json';
297
names: string[];
298
extendedFolderInfo?: boolean;
299
jsonFormat?: 'names' | 'extended';
300
render?: ListRender;
301
}
302
303
interface ListOptionsHtmlFormat {
304
format: 'html';
305
names: string[];
306
extendedFolderInfo?: boolean;
307
jsonFormat?: 'names' | 'extended';
308
render: ListRender;
309
}
310
311
interface ListRender {
312
(dirs: ListDir[], files: ListFile[]): string;
313
}
314
```
315
316
**Usage Examples:**
317
318
```typescript
319
// Basic static assets
320
app.useStaticAssets({
321
root: path.join(__dirname, '..', 'public'),
322
prefix: '/static/',
323
});
324
325
// Advanced configuration
326
app.useStaticAssets({
327
root: [
328
path.join(__dirname, '..', 'public'),
329
path.join(__dirname, '..', 'uploads')
330
],
331
prefix: '/assets/',
332
maxAge: '1d',
333
etag: true,
334
lastModified: true,
335
immutable: true,
336
preCompressed: true,
337
setHeaders: (res, path, stat) => {
338
if (path.endsWith('.pdf')) {
339
res.setHeader('Content-Type', 'application/pdf');
340
res.setHeader('Content-Security-Policy', "default-src 'self'");
341
}
342
},
343
allowedPath: (pathName, root, request) => {
344
// Only allow access to certain files based on user permissions
345
return !pathName.includes('private') || request.headers.authorization;
346
},
347
list: {
348
format: 'json',
349
names: ['index.html'],
350
extendedFolderInfo: true
351
}
352
});
353
354
// Serving with constraints
355
app.useStaticAssets({
356
root: path.join(__dirname, '..', 'admin-assets'),
357
prefix: '/admin/',
358
constraints: {
359
host: 'admin.example.com'
360
}
361
});
362
```
363
364
### View Engine Configuration
365
366
Configuration interface for template engines and view rendering.
367
368
```typescript { .api }
369
/**
370
* Configuration options for view engine setup
371
* Based on @fastify/view plugin options
372
*/
373
interface FastifyViewOptions {
374
/** Template engine configuration */
375
engine: {
376
/** EJS template engine */
377
ejs?: any;
378
/** Eta template engine */
379
eta?: any;
380
/** Nunjucks template engine */
381
nunjucks?: any;
382
/** Pug template engine */
383
pug?: any;
384
/** Handlebars template engine */
385
handlebars?: any;
386
/** Mustache template engine */
387
mustache?: any;
388
/** Art Template engine */
389
'art-template'?: any;
390
/** Twig template engine */
391
twig?: any;
392
/** Liquid template engine */
393
liquid?: any;
394
/** doT template engine */
395
dot?: any;
396
};
397
398
/** Template directory path(s) */
399
templates?: string | string[];
400
401
/** Include file extension in template names */
402
includeViewExtension?: boolean;
403
404
/** Engine-specific options */
405
options?: object;
406
407
/** Character encoding for templates */
408
charset?: string;
409
410
/** Maximum number of cached templates */
411
maxCache?: number;
412
413
/** Production mode optimizations */
414
production?: boolean;
415
416
/** Default context for all templates */
417
defaultContext?: object;
418
419
/** Default layout template */
420
layout?: string;
421
422
/** Root directory for templates */
423
root?: string;
424
425
/** Default view file extension */
426
viewExt?: string;
427
428
/** Property name for reply.view method */
429
propertyName?: string;
430
431
/** Property name for async reply method */
432
asyncProperyName?: string;
433
}
434
```
435
436
**Usage Examples:**
437
438
```typescript
439
// Handlebars setup
440
app.setViewEngine({
441
engine: {
442
handlebars: require('handlebars')
443
},
444
templates: path.join(__dirname, '..', 'views'),
445
options: {
446
partials: {
447
header: 'partials/header.hbs',
448
footer: 'partials/footer.hbs',
449
sidebar: 'partials/sidebar.hbs'
450
},
451
helpers: {
452
formatDate: (date) => new Date(date).toLocaleDateString(),
453
uppercase: (str) => str.toUpperCase()
454
}
455
},
456
layout: 'layouts/main.hbs',
457
viewExt: 'hbs',
458
defaultContext: {
459
siteName: 'My Application',
460
year: new Date().getFullYear()
461
}
462
});
463
464
// Pug setup with production optimizations
465
app.setViewEngine({
466
engine: {
467
pug: require('pug')
468
},
469
templates: path.join(__dirname, '..', 'views'),
470
production: process.env.NODE_ENV === 'production',
471
maxCache: 100,
472
options: {
473
pretty: process.env.NODE_ENV !== 'production',
474
cache: process.env.NODE_ENV === 'production',
475
basedir: path.join(__dirname, '..', 'views')
476
}
477
});
478
479
// Multiple template directories
480
app.setViewEngine({
481
engine: {
482
ejs: require('ejs')
483
},
484
templates: [
485
path.join(__dirname, '..', 'views'),
486
path.join(__dirname, '..', 'shared-templates')
487
],
488
options: {
489
delimiter: '?',
490
openDelimiter: '<',
491
closeDelimiter: '>',
492
cache: true
493
}
494
});
495
```
496
497
### Testing Interfaces
498
499
Interfaces for testing with light-my-request integration.
500
501
```typescript { .api }
502
/**
503
* Options for injecting test requests
504
*/
505
interface InjectOptions {
506
method?: string;
507
url?: string;
508
path?: string;
509
query?: string | Record<string, any>;
510
payload?: any;
511
headers?: Record<string, string>;
512
cookies?: Record<string, string>;
513
remoteAddress?: string;
514
server?: any;
515
simulate?: {
516
end?: boolean;
517
split?: boolean;
518
error?: boolean;
519
close?: boolean;
520
};
521
validate?: boolean;
522
authority?: string;
523
}
524
525
/**
526
* Chain interface for building test requests
527
*/
528
interface LightMyRequestChain {
529
get(url: string): LightMyRequestChain;
530
post(url: string): LightMyRequestChain;
531
put(url: string): LightMyRequestChain;
532
patch(url: string): LightMyRequestChain;
533
delete(url: string): LightMyRequestChain;
534
head(url: string): LightMyRequestChain;
535
options(url: string): LightMyRequestChain;
536
headers(headers: Record<string, string>): LightMyRequestChain;
537
payload(payload: any): LightMyRequestChain;
538
query(query: Record<string, any>): LightMyRequestChain;
539
cookies(cookies: Record<string, string>): LightMyRequestChain;
540
end(): Promise<LightMyRequestResponse>;
541
}
542
543
/**
544
* Response from injected test requests
545
*/
546
interface LightMyRequestResponse {
547
statusCode: number;
548
statusMessage: string;
549
headers: Record<string, string>;
550
rawPayload: Buffer;
551
payload: string;
552
body: string;
553
json(): any;
554
cookies: Array<{
555
name: string;
556
value: string;
557
path?: string;
558
domain?: string;
559
expires?: Date;
560
httpOnly?: boolean;
561
secure?: boolean;
562
sameSite?: string;
563
}>;
564
}
565
```
566
567
**Usage Examples:**
568
569
```typescript
570
// In test files
571
describe('ProductsController', () => {
572
let app: NestFastifyApplication;
573
574
beforeEach(async () => {
575
const module = await Test.createTestingModule({
576
controllers: [ProductsController],
577
}).compile();
578
579
app = module.createNestApplication<NestFastifyApplication>(
580
new FastifyAdapter()
581
);
582
await app.init();
583
});
584
585
it('should return products', async () => {
586
const response = await app.inject({
587
method: 'GET',
588
url: '/products',
589
query: { page: '1', limit: '10' }
590
});
591
592
expect(response.statusCode).toBe(200);
593
expect(response.json()).toEqual({
594
products: expect.any(Array),
595
total: expect.any(Number)
596
});
597
});
598
599
it('should create product', async () => {
600
const productData = {
601
name: 'Test Product',
602
price: 29.99,
603
category: 'electronics'
604
};
605
606
const response = await app.inject()
607
.post('/products')
608
.headers({ 'content-type': 'application/json' })
609
.payload(productData)
610
.end();
611
612
expect(response.statusCode).toBe(201);
613
expect(response.json()).toMatchObject(productData);
614
});
615
});
616
```