Introduction
NestJS isn't tied to any specific HTTP framework. It's an abstraction layer that works with different HTTP frameworks and transports. Understanding this architecture helps you write portable code and choose the right platform for your needs.
Key Concepts
- HTTP Adapter: Abstraction over Express/Fastify
- Platform Package: @nestjs/platform-express or @nestjs/platform-fastify
- Transport Layer: HTTP, WebSocket, microservices, GraphQL
Real World Context
Teams often start with Express for its massive ecosystem and later need to switch to Fastify for performance, or support both HTTP and WebSocket transports. NestJS's platform abstraction makes these transitions possible without rewriting business logic — a migration that would take weeks in a raw Express app takes hours with NestJS.
Deep Dive
Express (Default)
typescriptimport { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); }
Fastify
typescriptimport { NestFactory } from '@nestjs/core'; import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify'; async function bootstrap() { const app = await NestFactory.create<NestFastifyApplication>( AppModule, new FastifyAdapter(), ); await app.listen(3000, '0.0.0.0'); }
Accessing Underlying Instance
typescript// Express const expressApp = app.getHttpAdapter().getInstance(); expressApp.use(someExpressMiddleware); // Fastify const fastifyInstance = app.getHttpAdapter().getInstance(); await fastifyInstance.register(someFastifyPlugin);
Writing Portable Code
typescript// Avoid this (Express-specific) @Get() findAll(@Req() req: Request) { return req.headers['x-custom']; } // Prefer this (portable) @Get() findAll(@Headers('x-custom') customHeader: string) { return customHeader; }
Hybrid Applications
typescriptconst app = await NestFactory.create(AppModule); // Add microservice transport app.connectMicroservice({ transport: Transport.REDIS, options: { host: 'localhost', port: 6379 }, }); // Add WebSocket gateway // (WebSocket module handles this automatically) await app.startAllMicroservices(); await app.listen(3000);
NestJS 11.1: Express v5 & Fastify v5
NestJS 11 upgraded to Express v5 and Fastify v5 by default. Key changes:
Express v5 breaking changes:
- Wildcard routes require named parameters:
@Get('files/*splat')instead of@Get('files/*') - Query parser defaults to
simple— useapp.set('query parser', 'extended')for nested objects
Fastify v5 is mostly backward-compatible. Path matching for routes remains unchanged. When using NestJS's middleware abstraction layer (MiddlewareConsumer.apply()), path matching follows the updated path-to-regexp conventions regardless of the underlying platform. Note that Fastify uses its own native hook system, not Express-style middleware.
Node.js v20+ is required — support for v16 and v18 has been dropped.
The portable coding patterns shown above remain the best approach regardless of platform version.
Common Pitfalls
- Platform-specific middleware: Express middleware doesn't work with Fastify.
- @Req() and @Res() dependency: These bind you to a specific platform.
- Assuming Express: Always check documentation for Fastify differences.
Best Practices
- Use NestJS decorators (@Body, @Query, @Headers) over @Req/@Res
- Test platform changes thoroughly
- Abstract platform-specific code into services
- Document platform requirements
Summary
NestJS abstracts the HTTP layer through adapters. Express is the default; Fastify offers better performance. Write portable code using NestJS decorators instead of accessing raw request/response objects. Hybrid applications can combine HTTP with WebSockets and microservices.
Code Examples
// Portable code using NestJS decorators
@Get(':id')
findOne(
@Param('id') id: string,
@Headers('x-tenant-id') tenantId: string,
@Query('include') include?: string,
) {
return this.service.findOne(id, tenantId, include);
}
// Avoid: @Req() req — ties you to Express/Fastify