How to Implement Hybrid Microservices in NestJS: HTTP and TCP Together
Use NestFactory.create() to bootstrap your HTTP application, then call app.connectMicroservice() with Transport.TCP to attach a TCP listener, followed by await app.startAllMicroservices() before await app.listen().
The nestjs/nest repository provides native support for hybrid applications that serve HTTP requests and TCP microservice messages from the same process. This pattern allows REST controllers and message handlers to share providers, configuration, and modules while communicating over different transports. In sample/03-microservices, the framework demonstrates how to combine an HTTP server on port 3001 with a TCP microservice listener on port 3000 using a single bootstrap sequence.
What Is a Hybrid NestJS Application?
A hybrid application runs multiple server transports within one NestJS context. The HTTP layer handles traditional REST requests through @Controller decorators, while the microservice layer processes @MessagePattern and @EventPattern calls over TCP sockets. Both sides access the same singleton providers and database connections because they exist within a single NestApplicationContext.
The architecture follows this sequence:
- Create the HTTP application with
NestFactory.create(AppModule) - Register one or more microservices via
app.connectMicroservice<MicroserviceOptions>() - Initialize microservice listeners with
app.startAllMicroservices() - Start the HTTP server with
app.listen(port)
Bootstrap a Hybrid HTTP and TCP Microservice
The entry point in sample/03-microservices/src/main.ts demonstrates the complete bootstrap pattern for a hybrid deployment.
Create the HTTP Application Foundation
Begin by creating the standard NestJS HTTP application. This establishes the root dependency injection container that the microservice will also use.
// sample/03-microservices/src/main.ts
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// HTTP application is now ready for middleware and controllers
}
Connect the TCP Microservice Transport
Attach a TCP microservice to the existing HTTP instance using connectMicroservice(). Pass Transport.TCP in the options object to specify the transport protocol.
app.connectMicroservice<MicroserviceOptions>({
transport: Transport.TCP,
options: {
retryAttempts: 5,
retryDelay: 3000,
},
});
The options object configures the TCP listener behavior. By default, the TCP transport binds to localhost:3000, while the HTTP server binds to the port specified in app.listen().
Start All Microservices Before the HTTP Server
You must start the microservice listeners before the HTTP server to ensure all transport layers are ready to accept connections.
await app.startAllMicroservices();
await app.listen(3001);
console.log(`Application is running on: ${await app.getUrl()}`);
}
bootstrap();
The startAllMicroservices() method iterates over every microservice registered with connectMicroservice() and invokes their individual listen() methods. This guarantees that TCP sockets are bound before the HTTP server begins accepting requests.
Handle Messages and Proxy HTTP Requests
The sample/03-microservices/src/app.controller.ts file demonstrates how to implement both sides of the hybrid architecture: message handlers for TCP clients and HTTP endpoints that delegate to those handlers.
Define TCP Message Pattern Handlers
Use the @MessagePattern() decorator to define methods that respond to specific command patterns sent over TCP.
// sample/03-microservices/src/app.controller.ts
import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
@Controller()
export class AppController {
@MessagePattern({ cmd: 'sum' })
sum(data: number[]): number {
return (data || []).reduce((a, b) => a + b);
}
@MessagePattern({ cmd: 'streaming' })
streaming(data: number[]) {
return from(data); // Returns an Observable stream
}
}
These handlers execute whenever a TCP client sends a message matching the pattern object. The method receives the payload directly and can return either a value or an Observable for streaming responses.
Create HTTP Endpoints That Delegate to Microservices
Inject a ClientProxy using the @Client() decorator to communicate from the HTTP controller back to the TCP microservice running in the same process.
import { Body, Controller, HttpCode, Post, Query } from '@nestjs/common';
import { Client, ClientProxy, Transport } from '@nestjs/microservices';
import { Observable } from 'rxjs';
import * as fs from 'fs';
import * as path from 'path';
@Controller()
export class AppController {
@Client({
transport: Transport.TCP,
options: {
tlsOptions: {
ca: [
fs.readFileSync(
path.join(__dirname, 'ca.cert.pem'),
'utf-8',
).toString(),
],
},
},
})
client: ClientProxy;
@Post()
@HttpCode(200)
call(
@Query('command') cmd: string,
@Body() data: number[],
): Observable<number> {
return this.client.send<number>({ cmd }, data);
}
}
The client.send() method returns an Observable that resolves when the TCP message handler completes. NestJS automatically subscribes to this observable and sends the result as the HTTP response body.
Register Dynamic Client Proxies for Dependency Injection
For complex dependency injection scenarios, use ClientsModule.registerAsync() in your module configuration. The integration test file integration/microservices/src/tcp-tls/app.module.ts shows three registration patterns: factory-based, class-based, and custom proxy classes.
// integration/microservices/src/tcp-tls/app.module.ts
import { Module, Injectable } from '@nestjs/common';
import {
ClientsModule,
ClientsModuleOptionsFactory,
ClientOptions,
Transport,
ClientTCP,
} from '@nestjs/microservices';
import { ConfigService } from './config.service';
@Injectable()
class ClientOptionService implements ClientsModuleOptionsFactory {
constructor(private readonly configService: ConfigService) {}
createClientOptions(): ClientOptions {
return {
transport: this.configService.get('transport'),
options: { tlsOptions: { ca: caCert } },
};
}
}
@Module({
imports: [
ClientsModule.registerAsync([
{
name: 'USE_FACTORY_CLIENT',
useFactory: (cfg: ConfigService) => ({
transport: cfg.get('transport'),
options: { tlsOptions: { ca: caCert } },
}),
inject: [ConfigService],
imports: [ConfigModule],
},
{
name: 'USE_CLASS_CLIENT',
useClass: ClientOptionService,
imports: [ConfigModule],
},
{
name: 'CUSTOM_PROXY_CLIENT',
useFactory: () => ({
customClass: class extends ClientTCP {
serializeError(err) {
return new RpcException(err);
}
},
}),
},
]),
],
})
export class ApplicationModule {}
Inject these dynamic clients into services using the @Inject() decorator with the registered name: @Inject('USE_FACTORY_CLIENT') private client: ClientProxy.
Summary
- Hybrid applications combine HTTP servers and TCP microservices in a single process using
NestFactory.create()followed byapp.connectMicroservice(). - Bootstrap sequence matters: always call
await app.startAllMicroservices()beforeawait app.listen()to ensure transport layers initialize correctly. - Shared context allows both HTTP controllers and TCP message handlers to inject the same providers and database connections.
- ClientProxy enables HTTP endpoints to delegate work to TCP microservices via
client.send()orclient.emit(). - Advanced registration via
ClientsModule.registerAsync()supports factory patterns, configuration services, and custom client classes for TLS or error handling customization.
Frequently Asked Questions
What port does the TCP microservice listen on by default?
By default, the TCP transport binds to localhost:3000. You can override this by specifying the host and port properties in the options object passed to connectMicroservice(). The HTTP server port is configured separately in app.listen().
Can I connect multiple microservices to one HTTP application?
Yes. You can call app.connectMicroservice() multiple times with different transports or port configurations. Invoke app.startAllMicroservices() once to initialize all registered microservices simultaneously. Each microservice runs its own listener while sharing the same dependency injection container.
How do I handle errors when proxying from HTTP to TCP?
Errors thrown in TCP message handlers propagate through the ClientProxy observable. Subscribe to the observable in your HTTP controller or use an exception filter. Alternatively, provide a custom client class via ClientsModule.registerAsync() that overrides serializeError() to transform errors into RpcException instances before they reach the HTTP layer.
Is it possible to use TLS with TCP microservices in NestJS?
Yes. Pass tlsOptions within the transport options object when configuring either the server side (connectMicroservice) or the client side (@Client decorator or ClientsModule). Provide the certificate authority, key, and certificate files as shown in integration/microservices/src/tcp-tls/app.module.ts to enable encrypted TCP communication.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →