{"id":15724,"library":"nice-grpc-client-middleware-retry","title":"nice-grpc Client Retry Middleware","description":"nice-grpc-client-middleware-retry is a client-side middleware for the `nice-grpc` TypeScript gRPC library, enabling automatic retries for unary gRPC calls. It integrates seamlessly with the `nice-grpc` client factory to add robust fault tolerance with features like exponential backoff. The current stable version is 3.1.14, and the `nice-grpc` ecosystem, which this package is part of, maintains an active and frequent release cadence, often with updates every few weeks for core packages. A key differentiator is its strong TypeScript support and modern API leveraging Promises and Async Iterables, providing a more ergonomic experience compared to traditional gRPC implementations. It also emphasizes the importance of idempotency, requiring explicit configuration for retries on non-idempotent operations to prevent unintended side effects.","status":"active","version":"3.1.14","language":"javascript","source_language":"en","source_url":null,"tags":["javascript","typescript"],"install":[{"cmd":"npm install nice-grpc-client-middleware-retry","lang":"bash","label":"npm"},{"cmd":"yarn add nice-grpc-client-middleware-retry","lang":"bash","label":"yarn"},{"cmd":"pnpm add nice-grpc-client-middleware-retry","lang":"bash","label":"pnpm"}],"dependencies":[{"reason":"Provides the core client factory, channel, and types for gRPC communication.","package":"nice-grpc","optional":false},{"reason":"Supplies common data structures and types like ClientError and Status, which are essential for middleware functionality.","package":"nice-grpc-common","optional":false}],"imports":[{"note":"This package is ESM-first. While CommonJS might work with transpilation, direct `require` is not the idiomatic or recommended way. Ensure your project is configured for ESM.","wrong":"const { retryMiddleware } = require('nice-grpc-client-middleware-retry');","symbol":"retryMiddleware","correct":"import { retryMiddleware } from 'nice-grpc-client-middleware-retry';"},{"note":"The client factory is part of the core `nice-grpc` package, not the middleware itself.","wrong":"import { createClientFactory } from 'nice-grpc-client-middleware-retry';","symbol":"createClientFactory","correct":"import { createClientFactory } from 'nice-grpc';"},{"note":"`nice-grpc` re-exports gRPC status codes from its own common package for consistent typing and usage.","wrong":"import { Status } from '@grpc/grpc-js';","symbol":"Status","correct":"import { Status } from 'nice-grpc';"}],"quickstart":{"code":"import { createChannel, createClientFactory, ClientError, Status } from 'nice-grpc';\nimport { retryMiddleware } from 'nice-grpc-client-middleware-retry';\n\n// Imagine you have a generated Protobuf service definition (e.g., from ts-proto)\n// interface ExampleService extends Client<typeof ExampleServiceDefinition> {}\n// const ExampleServiceDefinition = { ... }; // Your service definition\n\n// Mock a service definition for demonstration\nconst ExampleServiceDefinition = {\n  name: 'ExampleService',\n  methods: {\n    exampleMethod: {\n      path: '/ExampleService/ExampleMethod',\n      requestType: {}, // replace with your actual request type\n      responseType: {}, // replace with your actual response type\n      options: {},\n    },\n  },\n};\n\nasync function runClientWithRetries() {\n  const address = process.env.GRPC_SERVER_ADDRESS ?? 'localhost:50051';\n  const channel = createChannel(address);\n\n  const clientFactory = createClientFactory().use(retryMiddleware);\n\n  const client = clientFactory.create(ExampleServiceDefinition, channel);\n\n  let attempt = 0;\n  try {\n    console.log('Attempting to call exampleMethod with retries...');\n    const response = await client.exampleMethod({}, {\n      // Enable retry if method is not marked as idempotent in Protobuf options\n      retry: true, \n      retryMaxAttempts: 3, \n      retryableStatuses: [Status.UNAVAILABLE, Status.INTERNAL, Status.UNKNOWN],\n      onRetry(error: ClientError, attemptNumber: number) {\n        console.warn(`Retry attempt ${attemptNumber} due to ${Status[error.code]}: ${error.details}`);\n      },\n      // Optional: Exponential backoff configuration\n      retryDelay: 100, // initial delay in ms\n      retryDelayMax: 1000, // max delay in ms\n      retryDelayMultiplier: 2, // multiplier for each successive delay\n    });\n    console.log('Successfully received response:', response);\n  } catch (error) {\n    if (error instanceof ClientError) {\n      console.error(`Client error after all retries: ${Status[error.code]} - ${error.details}`);\n    } else {\n      console.error('An unexpected error occurred:', error);\n    }\n  } finally {\n    channel.close();\n  }\n}\n\nrunClientWithRetries();","lang":"typescript","description":"This quickstart demonstrates how to apply the `retryMiddleware` to a `nice-grpc` client, configure retry parameters like max attempts and retryable statuses, and handle potential errors. It also illustrates the `onRetry` callback for logging retry attempts and highlights the configuration options for exponential backoff."},"warnings":[{"fix":"For non-idempotent operations, carefully evaluate the risks before enabling retries. If retries are necessary, ensure your server-side logic can handle duplicate requests safely (e.g., by using idempotency keys). Explicitly set `retry: true` in the call options only when appropriate.","message":"Retrying non-idempotent operations can lead to unintended side effects or data corruption if the original call succeeded but the client didn't receive the response. By default, `nice-grpc-client-middleware-retry` disables retries unless the gRPC method is explicitly marked as `idempotency_level = IDEMPOTENT` or `NO_SIDE_EFFECTS` in the Protobuf definition (requires `ts-proto` for option interpretation), or `retry: true` is set in call options.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Ensure your project is configured for ES Modules (e.g., `\"type\": \"module\"` in `package.json`). Use `import` statements exclusively. If using TypeScript, set `\"module\": \"Node16\"` or `\"ES2022\"` and `\"moduleResolution\": \"Node16\"` or `\"Bundler\"` in `tsconfig.json`.","message":"Older versions of `nice-grpc` and its middlewares might have CJS-only exports. Since `nice-grpc` v3, the library primarily targets ESM, which can lead to import errors if your project is not configured for ESM or if you try to use `require()` statements.","severity":"breaking","affected_versions":">=3.0.0"},{"fix":"Only include truly transient error statuses (e.g., `UNAVAILABLE`, `INTERNAL`, `UNKNOWN` (if appropriate for transient server errors), `DEADLINE_EXCEEDED`) in `retryableStatuses`. Avoid retrying on deterministic application-level errors.","message":"Configuring `retryableStatuses` without careful consideration can lead to retries on errors that are not transient, such as `INVALID_ARGUMENT` or `NOT_FOUND`. This can mask actual application logic errors and exhaust server resources.","severity":"gotcha","affected_versions":">=1.0.0"},{"fix":"Always set a reasonable `retryMaxAttempts` and configure exponential backoff (`retryDelay`, `retryDelayMax`, `retryDelayMultiplier`) with jitter to prevent retries from overwhelming the server. Consider implementing circuit breakers or timeouts for more advanced resilience patterns.","message":"Infinite retries or very long retry configurations can lead to resource exhaustion on the client and put undue pressure on struggling backend services, potentially worsening an outage (the 'thundering herd' problem).","severity":"gotcha","affected_versions":">=1.0.0"}],"env_vars":null,"last_verified":"2026-04-21T00:00:00.000Z","next_check":"2026-07-20T00:00:00.000Z","problems":[{"fix":"Update your import statement to `import { retryMiddleware } from 'nice-grpc-client-middleware-retry';` and ensure your project is configured for ES Modules (e.g., `\"type\": \"module\"` in `package.json`).","cause":"Attempting to import `retryMiddleware` using CommonJS `require` syntax in an ES Module context, or an incorrect import path.","error":"TypeError: __webpack_require__(...).retryMiddleware is not a function"},{"fix":"Increase the `callTimeout` in your gRPC call options or ensure that server processing times are within acceptable limits. Verify that `AbortSignal`s are not prematurely aborting the call. If retries are configured, ensure the total time for retries does not exceed an overarching call timeout.","cause":"The gRPC call was cancelled, often due to a client-side timeout expiring before the server responded, or a manual cancellation signal.","error":"ClientError: status=UNKNOWN, details='Call cancelled'"},{"fix":"Ensure `nice-grpc` (and its types, which it ships) is correctly installed: `npm install nice-grpc`. Check `tsconfig.json` for `\"moduleResolution\": \"Node16\"` or `\"Bundler\"` and `\"module\": \"Node16\"` or `\"ESNext\"` to correctly resolve ES Modules.","cause":"TypeScript compiler cannot resolve the `nice-grpc` package or its types, typically due to missing `@types` packages or incorrect module resolution settings.","error":"Error: TS2307: Cannot find module 'nice-grpc' or its corresponding type declarations."}],"ecosystem":"npm"}