nestjs/nest · error

CONN_ERR

CONN_ERR

Error message

CONN_ERR

What it means

NestJS microservices error surfaced by DisconnectedClientController (integration/microservices/src/disconnected.controller.ts). ClientProxyFactory.create(options) builds a transport client (TCP/Redis/RabbitMQ/NATS/MQTT) from request-body options, then client.send({cmd:'none'}, [1,2,3]) emits a message. When the transport cannot reach its broker/service, NestJS's ClientProxy emits an error whose code is CONN_ERR (its own 'connection not available' code) or one of ECONNREFUSED/ENOTFOUND/CONNECTION_REFUSED. The catchError at line 22-33 destructures `error?.err ?? error ?? { code: 'CONN_ERR' }` and normalises connection-class codes into RequestTimeoutException('ECONNREFUSED'); the bare `{ code: 'CONN_ERR' }` fallback covers errors with no structured code. This controller exists specifically to demonstrate that disconnection path.

Source

Thrown at integration/microservices/src/disconnected.controller.ts:23

  Post,
  RequestTimeoutException,
} from '@nestjs/common';
import { ClientProxyFactory } from '@nestjs/microservices';
import { Observable, throwError } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';

@Controller()
export class DisconnectedClientController {
  @Post()
  call(@Body() options): Observable<number> {
    const client = ClientProxyFactory.create(options);
    return client.send<number, number[]>({ cmd: 'none' }, [1, 2, 3]).pipe(
      // tap(
      //   console.log.bind(console, 'data'),
      //   console.error.bind(console, 'error'),
      // ),
      catchError(error => {
        const { code } = error?.err ?? error ?? { code: 'CONN_ERR' };
        return throwError(() =>
          code === 'ECONNREFUSED' ||
          code === 'CONN_ERR' ||
          code === 'ENOTFOUND' ||
          code === 'CONNECTION_REFUSED' ||
          error.message.includes('Connection is closed.')
            ? new RequestTimeoutException('ECONNREFUSED')
            : new InternalServerErrorException(),
        );
      }),
      tap({
        error: () => client.close(),
      }),
    );
  }
}

View on GitHub (pinned to 6ec0e2783d)

Solutions

  1. Start the transport the options point at (e.g., docker run redis, or the partner microservice) and re-issue the request.
  2. Verify host/port in the request body actually have a listener before sending.
  3. Call client.connect() (or rely on NestJS eager connect) and await it so connection failures surface before the first send rather than as CONN_ERR mid-stream.
  4. Do not accept raw transport options from the request body in production — validate/whitelist them, or build the client from trusted config.

Example fix

// before
const client = ClientProxyFactory.create(options);
return client.send<number, number[]>({ cmd: 'none' }, [1, 2, 3]).pipe(...)

// after
if (!options || !options.transport) {
  throw new BadRequestException('transport options required');
}
const client = ClientProxyFactory.create(options);
await client.connect(); // surface connection failure explicitly
return client.send<number, number[]>({ cmd: 'none' }, [1, 2, 3]).pipe(...)
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate transport options before creating the client
const ALLOWED_TRANSPORTS = new Set(['TCP', 'REDIS', 'RMQ', 'NATS', 'MQTT', 'KAFKA']);

function isValidTransportOptions(v: unknown): v is { transport: string; options: Record<string, unknown> } {
  if (typeof v !== 'object' || v === null) return false;
  const t = (v as any).transport;
  return typeof t === 'string' && ALLOWED_TRANSPORTS.has(t)
    && typeof (v as any).options === 'object';
}

// Optionally probe reachability before sending
import { createConnection } from 'node:net';
async function canReach(host: string, port: number, ms = 1000) {
  return new Promise<boolean>(resolve => {
    const s = createConnection({ host, port }, () => { s.destroy(); resolve(true); });
    s.setTimeout(ms, () => { s.destroy(); resolve(false); });
    s.on('error', () => resolve(false));
  });
}

Type guard

function isConnectionError(e: unknown): boolean {
  const code = (e as any)?.err?.code ?? (e as any)?.code ?? (e as NodeJS.ErrnoException)?.code;
  return ['CONN_ERR', 'ECONNREFUSED', 'ENOTFOUND', 'CONNECTION_REFUSED', 'ECONNRESET'].includes(code);
}

Try / catch

client.send<number, number[]>({ cmd: 'none' }, [1, 2, 3]).pipe(
  catchError(error => {
    const code = error?.err?.code ?? error?.code ?? 'CONN_ERR';
    if (['CONN_ERR', 'ECONNREFUSED', 'ENOTFOUND', 'CONNECTION_REFUSED'].includes(code)) {
      return throwError(() => new RequestTimeoutException('broker unreachable'));
    }
    return throwError(() => new InternalServerErrorException(error?.message));
  }),
  tap({ error: () => client.close() }),
)

Prevention

When it happens

Trigger: POST to the controller with transport options pointing at a broker/service that isn't running — e.g., body { transport: 'REDIS', options: { host: 'localhost', port: 6379 } } with no Redis up, or a TCP microservice on a dead port. The first emitted message hits a closed/unreachable socket and the stream errors with code CONN_ERR.

Common situations: Broker (Redis/RabbitMQ/NATS/MQTT) never started locally; wrong host/port in options; service registry/DNS resolves but nothing listens on that port; firewall/network policy blocks the port; the receiving microservice crashed between connection setup and the send; accepting arbitrary options from the request body (as this controller does) lets clients target unreachable hosts.

Related errors


AI-assisted analysis of nestjs/nest@6ec0e2783d (2026-08-03). Data as JSON: /data/errors/8989a89e81468368.json. Report an issue: GitHub.