nestjs-libp2p
v1.0.1
Published
A custom NestJS microservice transport strategy built on libp2p for peer-to-peer communication
Maintainers
Readme
nestjs-libp2p-transport
A fully-featured NestJS custom microservice transport strategy built on libp2p — bringing peer-to-peer, encrypted, multiplexed communication to your NestJS services.
Features
- 🔒 Encrypted by default — Noise protocol encryption on every connection
- 🔀 Multiplexed streams — Yamux stream multiplexing
- 🔍 Peer discovery — mDNS (LAN), Kademlia DHT, and Bootstrap list
- 📡 PubSub — GossipSub topic-based pub/sub via
@chainsafe/libp2p-gossipsub - 📨 Request/Response — Full
send()/emit()ClientProxy support - 🧩 Drop-in — Works with
@MessagePattern,@EventPatternand standard NestJS DI - 🔌 Bring your own node — Inject a pre-configured libp2p instance
Requirements
| Package | Version |
|---|---|
| Node.js | >= 18 |
| libp2p | ^3.x |
| @nestjs/microservices | ^10 or ^11 |
Installation
npm install nestjs-libp2p-transportQuick Start
1. Bootstrap the microservice (server)
// main.ts
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions } from '@nestjs/microservices';
import { Libp2pServer } from 'nestjs-libp2p-transport';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
strategy: new Libp2pServer({
listenAddresses: ['/ip4/0.0.0.0/tcp/4001'],
enableMdns: true,
}),
});
await app.listen();
console.log('Microservice is listening');
}
bootstrap();2. Register message handlers
// app.controller.ts
import { Controller } from '@nestjs/common';
import { MessagePattern, Payload } from '@nestjs/microservices';
// or use the re-exported aliases:
import { PeerPattern, PeerEvent } from 'nestjs-libp2p-transport';
@Controller()
export class AppController {
@PeerPattern('sum')
handleSum(@Payload() data: { a: number; b: number }): number {
return data.a + data.b;
}
@PeerEvent('user.joined')
onUserJoined(@Payload() data: { userId: string }): void {
console.log('User joined:', data.userId);
}
}3. Send messages from a client
// client.ts
import { Libp2pClient } from 'nestjs-libp2p-transport';
import { firstValueFrom } from 'rxjs';
const client = new Libp2pClient({
listenAddresses: ['/ip4/0.0.0.0/tcp/0'], // ephemeral port
});
await client.connect();
await client.connectToPeer('/ip4/192.168.1.5/tcp/4001/p2p/12D3KooW...');
const result = await firstValueFrom(client.send<number>('sum', { a: 3, b: 7 }));
console.log(result); // 10
await client.close();NestJS Module Integration
Register the client in your module for full DI support:
// app.module.ts
import { Module } from '@nestjs/common';
import { Libp2pModule } from 'nestjs-libp2p-transport';
@Module({
imports: [
Libp2pModule.register({
token: 'MESH_CLIENT',
listenAddresses: ['/ip4/0.0.0.0/tcp/0'],
enableMdns: true,
}),
],
})
export class AppModule {}// some.service.ts
import { Inject, Injectable } from '@nestjs/common';
import { Libp2pClient } from 'nestjs-libp2p-transport';
import { firstValueFrom } from 'rxjs';
@Injectable()
export class SomeService {
constructor(@Inject('MESH_CLIENT') private readonly mesh: Libp2pClient) {}
async callRemote(peerMultiaddr: string) {
await this.mesh.connectToPeer(peerMultiaddr);
return firstValueFrom(this.mesh.send('greet', { name: 'World' }));
}
}Async configuration
Libp2pModule.registerAsync({
token: 'MESH_CLIENT',
useFactory: (config: ConfigService) => ({
listenAddresses: [config.get('P2P_LISTEN')],
bootstrapPeers: config.get<string[]>('BOOTSTRAP_PEERS'),
}),
inject: [ConfigService],
});PubSub
// Enable on both server and client
const server = new Libp2pServer({ enablePubsub: true });
const client = new Libp2pClient({ enablePubsub: true });
// Publisher
await client.publish$('events', { type: 'ping', ts: Date.now() });
// Subscriber
client.subscribe$<{ type: string }>('events').subscribe((msg) => {
console.log('Event:', msg.type);
});Bring Your Own libp2p Node
import { createLibp2p } from 'libp2p';
import { tcp } from '@libp2p/tcp';
import { noise } from '@libp2p/noise';
import { yamux } from '@libp2p/yamux';
import { Libp2pServer } from 'nestjs-libp2p-transport';
const myNode = await createLibp2p({
addresses: { listen: ['/ip4/0.0.0.0/tcp/4001'] },
transports: [tcp()],
connectionEncrypters: [noise()],
streamMuxers: [yamux()],
// ... your full config
});
const server = new Libp2pServer({
libp2p: { node: myNode },
});Configuration Reference
interface Libp2pTransportOptions {
/** Pre-built libp2p node */
libp2p?: { node?: Libp2p };
/** Multiaddrs to listen on. Default: ['/ip4/0.0.0.0/tcp/0'] */
listenAddresses?: string[];
/** Bootstrap peer multiaddrs for initial discovery */
bootstrapPeers?: string[];
/** Protocol identifier. Default: '/nestjs/1.0.0' */
protocol?: string;
/** Enable mDNS discovery. Default: true */
enableMdns?: boolean;
/** Enable Kademlia DHT. Default: false */
enableDht?: boolean;
/** Enable GossipSub pubsub. Default: false */
enablePubsub?: boolean;
/** Request timeout ms. Default: 30000 */
requestTimeout?: number;
/** Max inbound streams per peer. Default: 256 */
maxInboundStreams?: number;
/** Max outbound streams per peer. Default: 256 */
maxOutboundStreams?: number;
/** Custom logger (console by default) */
logger?: TransportLogger;
/** Custom serializer (JSON by default) */
serializer?: MessageSerializer;
}Custom Serializer
Swap JSON for MsgPack or Protobuf:
import { MessageSerializer } from 'nestjs-libp2p-transport';
import { encode, decode } from '@msgpack/msgpack';
class MsgPackSerializer implements MessageSerializer {
serialize(msg: unknown): Uint8Array {
return encode(msg);
}
deserialize<T>(data: Uint8Array): T {
return decode(data) as T;
}
}
const server = new Libp2pServer({ serializer: new MsgPackSerializer() });Custom Logger
import { TransportLogger } from 'nestjs-libp2p-transport';
class PinoLogger implements TransportLogger {
log(msg: string) { pino.info(msg); }
error(msg: string) { pino.error(msg); }
warn(msg: string) { pino.warn(msg); }
debug(msg: string) { pino.debug(msg); }
}
const server = new Libp2pServer({ logger: new PinoLogger() });License
MIT
