Field entry, 11 May.

The project began with one of those small local annoyances that is easy to mistake for the natural order of things. A development stack wants Redis semantics. The app expects a cache, a session store, a counter, a little pub/sub, perhaps a stream or two. Nobody is claiming this is the central nervous system of civilization. It is only there so a local service can boot in a shape that resembles production closely enough to be useful.

And yet the usual answer is to start another container, wait for another daemon, add another health check, explain another port collision to yourself in the tone of a parent who has just stepped on a toy in the dark, and then pretend this was all part of the feature work.

valkey-bun is an argument against that little ceremony. It is a Valkey/Redis-compatible server written in TypeScript for the Bun runtime, built for the narrow and therefore honest case where a local stack needs Redis protocol and Redis-shaped behavior, but does not need a production Valkey server. The distinction matters. The project is not pretending that a TypeScript process is a durable datastore. It is trying to make a local process useful enough that the container can stay asleep.

The more precise product boundary is this: speak the protocol well enough for real clients, hold useful application state in memory, and forget everything when the process stops. That last clause is not a defect hiding in small print. It is the point.

Boring enough to be useful

The README has the right kind of warning. There is no RDB. There is no AOF. There is no replication, Sentinel, or cluster sharding. If the thing you are storing would make users angry, auditors interested, or lawyers suddenly attentive, this is not the place to store it. Use the real thing, configured by people who enjoy disaster recovery more than is strictly healthy.

But local development is full of state that deserves no such ceremony. Session hashes. Page-view counters. Cache entries. Small queues. Pub/sub events. Test data whose highest ambition is to vanish cleanly after the test process exits. The usual infrastructure shape treats all of these as if they needed the same operational furniture as production. That is how local stacks become tiny museums of systems nobody meant to operate today.

valkey-bun takes the opposite bet. It starts from Bun’s TCP server, implements RESP2 and RESP3, accepts HELLO 3, and keeps the hot path close to the socket. The code is divided by the concerns one would expect: src/server.ts owns the listen loop and per-connection dispatch, src/resp.ts parses and writes the wire protocol, src/store.ts keeps the in-memory data structures, and src/commands/ groups the command handlers by Redis family.

That organization is not glamorous, which is one of its virtues. A local infrastructure replacement should be inspectable. If the server exists to remove ceremony from development, it should not immediately replace that ceremony with a mysterious box of framework magic and a tutorial whose first step is spiritual surrender.

The protocol is the product

The project is more interesting than a mock because it speaks the protocol. Ordinary clients can talk to it. The tests exercise both ioredis and node-redis, which is the difference between “we implemented a pleasant API that resembles Redis” and “a client that expects Redis can get through a real conversation without noticing too much.”

That detail carries more product weight than it first appears to. For local infrastructure, compatibility is often more important than completeness. A test app does not need every command Redis ever learned during its long and eventful life. It does need the commands that common clients probe for, the response modes they negotiate, the errors they expect, and the data-shape assumptions buried inside their libraries.

The command registry currently exposes a broad surface: strings, keys, hashes, lists, sets, sorted sets, streams, pub/sub, transactions, connection commands, and server/configuration commands. Some of those commands are real implementations. Some are compatibility stubs because clients ask questions that a small local server can answer politely without becoming an operations product. This is a practical compromise, not a secret roadmap toward replacing Valkey.

There is a useful humility in that. Completeness would be the wrong ambition. The better ambition is to make the common local paths feel boring: SET and GET, TTLs, HSET session blobs, INCR under contention, ZADD leaderboards, SUBSCRIBE and PUBLISH, MULTI and EXEC, WATCH for optimistic concurrency, stream entries for little event logs. These are the places where application code leans on Redis as a shape, not as a managed service.

The server also has to preserve the awkward physical truth of the protocol. Redis keys and values are binary-safe, while JavaScript would very much like to turn everything into a string with a reassuring smile. valkey-bun stores values as Uint8Array where it can, and maps arbitrary key bytes through a latin1 bijection so JavaScript’s string-only Map keys do not quietly launder the data. That is the kind of unromantic detail that makes compatibility real.

Pen-and-ink object study of an in-memory ledger, byte slips, a clock, a bell, and an expiry sand timer.
FIG. 02 — IN-MEMORY LEDGER. BYTE SLIPS, WATCH NOTCH, EXPIRY TIMER.

Forgetting as a feature

Expiry is handled in the way one would expect from a small in-memory service: lazy checks on reads, plus a periodic sweep. That is not how one designs a database for heroic uptime, but it is entirely respectable for a workbench process. A key can expire. A test can observe the TTL. A session can behave like a session. Then the whole server can disappear at the end of the run, taking its little pile of temporary facts with it.

This is the part I like most about the project. It does not treat ephemerality as a shameful limitation. It treats it as a product boundary.

Software teams often talk about local development as if the only good local service is a smaller, worse replica of production. Sometimes that is true. If the system depends on persistence semantics, replication lag, failover behavior, cluster slots, operational metrics, or the exact strange behavior of a hosted provider under load, then a toy replacement is not a development convenience. It is a lie with a friendly port number.

But many local services do not need a replica. They need a contract. For a surprising amount of application code, the contract is: here is a Redis URL; you may store this value, read it back, increment it, expire it, publish a message, subscribe to a channel, and run a small transaction. That contract can be satisfied by a process that is deliberately modest, provided everyone remembers where the boundary is.

The problem is not that developers use lightweight local substitutes. The problem is when the substitute forgets to announce what it is substituting. valkey-bun is quite plain about the line. No persistence. No replication. No cluster. No blocking command semantics in the places where parking the connection would turn the project into a different beast. Enough compatibility for local development, tests, demos, and internal tools. Not enough ceremony to require its own operator.

The shape of a smaller stack

There is also a Bun-specific pleasure here. Bun already wants to be the fast local runtime, the thing one reaches for when starting and testing should feel nearly immediate. Putting a Redis-compatible TCP service in the same ecosystem has the satisfying quality of removing one more adapter from the bench. bun install, bun run start, connect a normal Redis client, and the process listens on 0.0.0.0:6379 unless told otherwise.

Configuration follows the same plain mood: VALKEY_HOST, VALKEY_PORT, VALKEY_PASSWORD, VALKEY_DBCOUNT, VALKEY_MAXCLIENTS, and an optional cluster-size setting that forks multiple Bun workers with reusePort. The compatibility commands report the resulting values because clients and tooling often ask about the room before deciding whether to sit down.

None of this is revolutionary, which is why it is useful. The best local infrastructure often feels slightly anticlimactic. It removes a wait, deletes a container, shortens a test setup, and gives the developer one fewer process to think about while preserving enough of the real contract that application code does not drift into fantasy.

That is the lesson I would carry forward from valkey-bun: small replacements are safest when their smallness is explicit. The project does not need to become Valkey. It needs to be a truthful workbench instrument for the parts of Valkey that local software actually touches. Speak the protocol. Store the bytes. Keep the common shapes working. Forget on purpose.

There are worse ambitions for a local service than knowing exactly when its memory should end.