Library / SDK
microsoft/etcd3 avatar
microsoft/etcd3

etcd3 gives Node a chainable etcd v3 client with leases, elections and mutexes

🔖 Node.js client for etcd3

550 stars76 forksTypeScriptNOASSERTION

At a glance

What is it?
etcd3 is Microsoft's TypeScript client for the gRPC-based etcd v3 API, with a query builder that chains get, put and delete calls, plus leases, watchers, distributed mutexes, elections and a mocking layer. The package version in the manifest is 2.0.0 while the newest tagged release is v1.1.2, so the published package and the tags have drifted apart.
Who is it for?
Adopt etcd3 if you are writing a Node or TypeScript service that needs etcd and want a chainable query builder rather than raw gRPC calls, because the client ships leases, watchers, elections and mutexes as first-class features rather than leaving you to build them on the raw API.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the client covers, and the query builder shape

etcd3 is described as a high-quality, production-ready client for the Protocol Buffer-based etcd v3 API, and the feature list is long enough to be worth reading in full because it shows how much of the coordination work the library takes off your hands.

Load balancing and fault handling with reconnections are both in the list, and they are linked to the same IOptions page in the API documentation, which is where the configuration for both lives. Then there are transactions and software transactional memory, the latter as a separate SoftwareTransaction class. High-level query builders come next, then lease management, fair distributed mutexes, watchers, user and role mocking, and elections.

The query builder is the part every user touches first. The quickstart shows the shape:

js
import { Etcd3 } from 'etcd3';

const client = new Etcd3();

(async () => {
  await client.put('foo').value('bar');

  const fooValue = await client.get('foo').string();
  console.log('foo was:', fooValue);

  const allFValues = await client.getAll().prefix('f').keys();
  console.log('all our keys starting with "f":', allFValues);

  await client.delete().all();
})();

The chaining is the design. client.get('foo').string() reads one key and coerces the value to a string. client.getAll().prefix('f').keys() reads a range by prefix and returns just the keys. client.delete().all() deletes everything. Every one of those is a builder that accumulates options and then executes.

That matters because etcd's raw API is range-based. A prefix read is a range request with a range end computed from the prefix, and the details of that computation are exactly the sort of thing a builder should handle. The fluent style also means the terminal operation is explicit: .string(), .keys(), .exec() or .execJS(), so you can tell from the call site whether you wanted values, keys or raw responses.

The constructor takes no arguments in the quickstart, which means it connects to the default etcd endpoint. The options for hosts, credentials, timeouts and the load balancing and fault handling behaviour are all in the IOptions interface that the feature list links to.

The coordination primitives, which is the real value of the library

The feature list is dominated by things that are hard to build on a raw key-value API, and each one maps onto a real distributed systems problem.

Lease management is the foundation. An etcd lease attaches a time-to-live to keys, and the server removes them when the holder stops renewing. A client that dies without releasing its lease leaves garbage keys behind, so a lease-aware client is what stops a crashed instance from leaving permanent state in the keyspace.

Watchers are the change notification mechanism, and the WatchBuilder class gives you a stream of events on a key or a range rather than a poll loop.

Fair distributed mutexes are next, and the word fair is doing specific work. A mutex over a key-value store has to be granted in order, otherwise a client that keeps requesting the lock can starve the others. The Mutex class is described as fair, which means the client implements the ordering rather than hoping the store does.

Elections build on that. The Election class is the standard pattern where several instances of a service contend for a role and one holds it at a time, which is how you build leader election on top of a key-value store. etcd's own transaction support is the natural mechanism, and the transactions and software transactional memory entries in the feature list are the pieces that make it usable.

User and role mocking is the last entry and it is the most unusual. The Etcd3 documentation links to a user section and a role section, and describes it as mocking management. In other words, you can create a user and a role in etcd and then mock them, which is a test-time facility: a test can assert on the identity and permissions that would be granted, without needing a real authentication setup and without granting real permissions to a real principal.

For a service that coordinates other services, this is the difference between a thin etcd wrapper and a coordination toolkit. The primitives are the same ones you would otherwise write, and writing them means getting the ordering, the lease renewal and the failure handling right under partition.

The proto directory and how the generated methods are built

The repository carries a proto/ directory and a build:proto script, which is the part that tells you how this client stays in step with etcd's own API.

The script does two things: it runs node ./bin/update-proto ./proto, and then it runs node bin/generate-methods.js ./proto/rpc.proto. So there are two stages. The first updates the Protocol Buffer definitions from somewhere, and the second generates the method wrappers from etcd's rpc.proto, the service definition that lists the gRPC methods etcd exposes.

That is a reasonable approach for a client that wants to cover the whole surface of the API rather than hand-writing a subset of the calls. It also means the client carries the .proto files in its published package: the files array in package.json lists lib and proto, so the protocol definitions ship with the compiled library.

The top-level entries make the rest of the layout legible. There is src/ for the TypeScript source, a proto/ directory, bin/ for the generation scripts, docs/, a changelog.md, typedoc.json for the API documentation build, tsconfig.json alongside tsconfig.test.json, eslint.config.js, and a vitest.config.mts. The build:doc script runs typedoc with the options file, and the README points at the resulting TypeDoc reference at microsoft.github.io/etcd3/classes/etcd3.html.

The README also points at the test cases as documentation: our test cases are also readable, linking to the src/test/ directory. That is a genuine point in favour of this library, because a distributed client's behaviour is defined by what it does under failure, and the tests are where that behaviour is written down in executable form.

One small sign of care: the typedoc-plugin-no-inherit devDependency. That plugin suppresses inherited members in the generated documentation, so the API reference shows what each class actually declares rather than a wall of inherited gRPC client methods.

Running the tests, which means running an etcd server

The test section is short and points at a compose file in a versioned subdirectory:

sh
$ npm install
$ cd src/test/containers/3.2 && docker compose up # in a separate shell
$ npm test
$ docker compose down

The path is the detail worth noticing. src/test/containers/3.2 suggests there is one compose setup per etcd server version, and that the client is tested against more than one version of the server. For a client library, that is the difference between claiming compatibility and demonstrating it, and it explains why the containers live under a versioned directory rather than at the root.

The npm test script chains three things: test:lint, which runs eslint over src/**/*.ts, test:typecheck, which runs tsc against tsconfig.test.json, and test:unit, which runs vitest run. So a test run is a lint check, a type check and the unit tests, in that order, and a formatting or type error fails the same command as a failing test.

The contributing section adds the runtime requirements. Running tests for this module requires Node.js 24 or later and an etcd3 server locally. The tests try to use the default port initially, and you can configure this by setting the ETCD_ADDR environment variable, like export ETCD_ADDR=localhost:12345.

That last point is a small thing that saves a confusing afternoon. If you already run etcd on the default port for something else, the test suite will connect to it rather than to the one you just started in Docker, and the failures will look like the client is broken rather than like it talked to the wrong server. The environment variable is the escape hatch.

The engines field in package.json requires node >=24, which matches the contributing note and means the package itself refuses to install on an older runtime.

The licence is recorded as a custom one that GitHub cannot classify, while the package.json license field says MIT, and the repository root carries a SECURITY.md alongside the LICENSE file.

The version gap, and what it means for depending on this package

The most important fact about this project is the mismatch between the two version numbers, and it needs to be stated plainly.

The recent releases listed are v1.1.2 from 2023-10-04, v1.1.1 from 2023-10-04, an hour earlier, and v1.1.0 from 2020-11-29. The package.json in the repository says version 2.0.0, and the description is Node client for etcd3. The last push was on 2026-10-14, and the repository is not archived.

So the git tags stop at 1.1.2 in October 2023 while the manifest declares 2.0.0 and the branch continues to receive work. What that means in practice depends on how you install.

If you install from npm, you get whatever version was published under the etcd3 name, and the manifest in this repository says the published version is 2.0.0. If you depend on a git tag, you can only pin to 1.1.2 or older, which is three years behind the current source. The two routes give you different code.

That is not necessarily a mistake. Tagging is often used for release notes and for a stable channel, while npm publishing can be more frequent. But for a library where a consumer's choice of install source determines which API surface they get, the gap is worth checking rather than assuming. The package.json also carries the modern module shape: type module with an exports map that points import at ./lib/index.js and types at ./lib/index.d.ts, which is the ESM-only packaging that TypeScript 6 and Node 24 expect.

The dependency list tells you the rest. The runtime dependencies are @grpc/grpc-js at ^1.14.4 and whatever else follows in the truncated listing, and the dev toolchain is TypeScript ~6.0.3, vitest ^5.0.0, eslint ^10.10.0, prettier ^3.9.6, typedoc ^0.28.20 and protobufjs ^8.8.0. The build:ts script is rimraf lib and tsc, and the prepare hook runs it, so the library is compiled on install.

Where etcd3 is the wrong choice

Three limits, and the first is about the runtime floor.

The first is Node.js 24 or later, which the engines field enforces at install time and the contributing guide repeats. That is a recent floor, and it is fine for a new service but it is a hard stop for a platform team still on Node 20 LTS who wants this client in an existing service. There is no option to relax it, since engines is a declaration rather than a suggestion.

The second is that the client is written in TypeScript and the build is a compiled TypeScript package with generated declarations. That is not a drawback by itself, and the README says it is type-safe for TypeScript consumers, which is the main reason to choose this client over the official Go or Java ones. But it does mean the package pulls a toolchain requirement on the consumer side if you want to consume the types directly, and it means the library is JavaScript-first, so a team whose other services are in Go, Rust or Python has one more language in the dependency tree.

The third is that the feature set is the etcd v3 API surface, and etcd v3 has been the current version for a long time. The library is not version-agnostic in the way a client that also speaks v2 would be, and there is nothing here for the newer APIs etcd has been adding beyond the gRPC service the client generates from rpc.proto. If your etcd deployment uses something outside the v3 gRPC surface, you are outside what this client covers.

The alternative approach is the official etcd client for your language, or a small hand-written wrapper over @grpc/grpc-js and the etcd protos. The difference in approach is that etcd3 gives you the coordination primitives, fair mutexes, elections, leases and watchers, already implemented and tested against multiple server versions, and that work is genuinely fiddly and easy to get subtly wrong. A hand-written wrapper gives you a dependency-light client that talks to exactly the endpoints you need, at the cost of implementing the lease renewal loop and the mutex ordering yourself. For a service that needs a leader election, this client is clearly the better deal. For a service that only reads a config key occasionally, the official client or thirty lines of gRPC is less to carry.

Editorial conclusion

Adopt etcd3 if you are writing a Node or TypeScript service that needs etcd and want a chainable query builder rather than raw gRPC calls, because the client ships leases, watchers, elections and mutexes as first-class features rather than leaving you to build them on the raw API. Do not adopt it expecting a current release, because the newest tagged release is v1.1.2 from 2023 while the package.json version reads 2.0.0, so the npm package and the git tags are not the same version line. Verify first that the etcd server version you run speaks the v3 gRPC API with the authentication your client is configured for, that Node.js 24 or later is available in your build and runtime environment as the manifest requires, and that the ETCD_ADDR environment variable points at the right endpoint, since the tests use the default port until you set it.

Frequently asked questions

What is etcd3 and what does it provide?

It is a TypeScript client for the Protocol Buffer-based etcd v3 API, described as production-ready. It includes load balancing, fault handling and reconnections, transactions and software transactional memory, high-level query builders, lease management, fair distributed mutexes, watchers, user and role mocking, and elections.

How do I install and start using etcd3?

Run npm install --save etcd3, then create a client with new Etcd3() and use the chained query builder: client.put('foo').value('bar'), client.get('foo').string(), client.getAll().prefix('f').keys() and client.delete().all(). Every chain ends in an explicit terminal operation such as string(), keys() or exec().

How do I run the etcd3 tests?

Run npm install, then cd src/test/containers/3.2 and docker compose up in a separate shell, then npm test and docker compose down. The tests need Node.js 24 or later and a local etcd server, and they use the default port unless you set the ETCD_ADDR environment variable, for example export ETCD_ADDR=localhost:12345.

What version of etcd3 is current?

The package.json in the repository declares version 2.0.0 and the last push was on 2026-10-14, while the newest tagged release listed is v1.1.2 from 2023-10-04, with v1.1.0 from 2020-11-29 before that. Installing from npm and pinning a git tag therefore give you different code.

How does etcd3 generate its API methods?

The build:proto script runs node ./bin/update-proto ./proto and then node bin/generate-methods.js ./proto/rpc.proto, updating the Protocol Buffer definitions and generating the method wrappers from etcd's rpc.proto service definition. The proto directory is included in the published package files.

What does the npm test script in etcd3 run?

It chains test:lint, which runs eslint over src/**/*.ts, test:typecheck, which runs tsc against tsconfig.test.json, and test:unit, which runs vitest run. A lint or type error fails the same command as a failing unit test.

Official sources

  1. Issues
  2. microsoft/etcd3 on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/microsoft-etcd3.svg)](https://hysenlabs.com/projects/microsoft-etcd3)

Was this useful?