Open Source
valkey-http

Published: October 26, 2026 · by Srinu Desetti (creator & maintainer)

valkey-http: Valkey from Edge, Serverless and Browsers over HTTP

valkey-http is a zero-dependency fetch client for Valkey over HTTP. It runs anywhere fetch runs — Cloudflare Workers, Vercel Edge, Deno, Bun, browsers, and plain Node.js — because it never opens a TCP socket. Every command travels as an HTTP request to an HTTP-to-Valkey gateway, and the whole client ships as a handful of files with no runtime dependencies. I created and maintain this package as the edge-side half of the Valkey HTTP pair.

Packagevalkey-http (opens in a new tab) v0.1.0
LicenseMIT
Runtime dependencies0
Module formatESM, TypeScript declarations included
RuntimeAny fetch runtime — edge, serverless, browsers; Node.js ≥ 18
Sourcegithub.com/webdevelopersrinu/valkey-http (opens in a new tab)

The Problem: No Sockets at the Edge

Every classic Valkey client — iovalkey, node-redis ports, anything built on the RESP protocol — assumes one thing: it can open a raw TCP socket to the server and keep it alive. That assumption quietly fails in exactly the environments modern applications are moving to:

  1. Edge runtimes can't open TCP sockets. Cloudflare Workers, Vercel Edge Functions, and similar V8-isolate platforms expose fetch and little else. There is no net.Socket, so a RESP client cannot even connect.
  2. Serverless penalizes persistent connections. Even where sockets exist (AWS Lambda, traditional serverless Node), a connection pool per short-lived instance means connection storms against the server and cold-start latency paid on every invocation.
  3. Browsers never had sockets at all. Any client-side code that wants to touch a cache must go through HTTP anyway.

In all three, HTTP is not merely an option — it is the only transport available. That is the entire premise of valkey-http: if fetch is the one primitive every runtime shares, the Valkey client should be built on fetch and nothing else.

edge-function.js
// Cloudflare Worker / Vercel Edge — no sockets, no problem.
import { createClient } from 'valkey-http'

const valkey = createClient({
  url: 'https://valkey-gateway.example.com',
  token: env.VALKEY_TOKEN,
})

export default {
  async fetch(request) {
    const cached = await valkey.get('home:payload')
    if (cached) return new Response(cached)

    const payload = await buildPayload()
    await valkey.set('home:payload', payload, { ex: 60 })
    return new Response(payload)
  },
}

The client is stateless by design. There is no connection to establish, keep alive, or pool — each command is one HTTP request, which is precisely the lifecycle model edge and serverless platforms are built around.

How It Works: Commands over HTTP

A classic client speaks RESP over a socket; valkey-http speaks JSON over HTTP. Each command becomes a request to the gateway, the gateway executes it against the real Valkey server over TCP, and the reply comes back as the response body.

Browser / Edge / Serverless          Your infrastructure
┌─────────────────────┐   HTTPS   ┌──────────────────┐   TCP/RESP   ┌────────┐
│ valkey-http (fetch) │ ────────▶ │ HTTP gateway     │ ───────────▶ │ Valkey │
│ zero dependencies   │ ◀──────── │ (valkey-http-    │ ◀─────────── │ server │
└─────────────────────┘           │  server)         │              └────────┘
                                  └──────────────────┘

The server half of that diagram is its own package: valkey-http-server — the HTTP-to-Valkey gateway you deploy next to your Valkey instance. The two packages are designed as a pair: the client assumes the gateway's request/response contract, and the gateway holds the real TCP connections so your edge code never has to.

basic-usage.js
import { createClient } from 'valkey-http'

const valkey = createClient({ url: process.env.VALKEY_HTTP_URL, token: process.env.VALKEY_TOKEN })

// Commands map 1:1 to Valkey commands — same names, same semantics
await valkey.set('user:42:name', 'Srinu')
const name = await valkey.get('user:42:name')   // 'Srinu'

await valkey.incr('pageviews')
await valkey.expire('pageviews', 3600)
await valkey.del('user:42:name')

Because authentication is a bearer token on each request rather than an AUTH handshake on a socket, the same client works unchanged in a browser tab, a Worker, and a Node process — the only per-environment difference is where the token comes from.

serverless-counter.js
// AWS Lambda / Vercel Function — no pool to warm, no connection to leak
import { createClient } from 'valkey-http'

const valkey = createClient({ url: process.env.VALKEY_HTTP_URL, token: process.env.VALKEY_TOKEN })

export async function handler(event) {
  // One HTTP round trip; nothing to close afterwards
  const count = await valkey.incr(`ratelimit:${event.ip}`)
  if (count === 1) await valkey.expire(`ratelimit:${event.ip}`, 60)

  if (count > 100) return { statusCode: 429, body: 'Too many requests' }
  return { statusCode: 200, body: 'ok' }
}

Design Notes — Decisions I Kept and Why

Zero dependencies, by construction

The entire client is built on the fetch global — no HTTP library, no protocol parser, no polyfills. The published package is four files and about 5 KB unpacked. In edge environments, where every kilobyte of bundle counts against cold-start time and platform size limits, a dependency tree is a liability; here there is none to audit, none to tree-shake, none to break.

ESM-only, Node ≥ 18

fetch became a stable global in Node.js 18, which makes 18 the natural floor: below it the package's one platform assumption does not hold. Shipping ESM-only keeps the module graph identical across Node, Deno, Bun, and bundlers targeting edge runtimes — one build, no dual-package hazard.

Stateless client, stateful gateway

Connection management is deliberately not the client's job. The gateway (valkey-http-server) owns the persistent TCP connections to Valkey, sitting in infrastructure where sockets are cheap and long-lived. The client stays a thin, stateless command mapper — which is why it can run in a browser tab and a Worker with the same code path.

HTTP semantics over protocol cleverness

Each command is an ordinary HTTP request, which means every piece of standard HTTP infrastructure works for free: CDN-level caching rules, request logging, per-route authentication, rate limiting at the gateway, and observability through tools that already understand HTTP. A latency cost per command is the honest trade — the FAQ below covers when that trade is right.

This is a solo project — I designed, built, and maintain valkey-http independently as the edge-side half of the Valkey HTTP pair.

What's Next?

valkey-http is only half the story. The other half is the gateway it talks to — the HTTP server that translates requests into real RESP commands against your Valkey instance.

← Back to Open Source overview

Continue to valkey-http-server — the HTTP-to-Valkey gateway →


Frequently Asked Questions

Why not just use a regular Valkey client like iovalkey?

If you run on a platform with TCP sockets and long-lived processes — a VM, a container, a classic Node server — you should: a socket client is faster per command. valkey-http exists for the platforms where that is impossible: Cloudflare Workers, Vercel Edge, and browsers expose fetch and no socket API at all. There, HTTP is not a preference but the only transport, and valkey-http is a client built honestly on that constraint.

Doesn't HTTP add latency compared to RESP over TCP?

Per command, yes — an HTTP request carries more overhead than a pipelined RESP frame on a warm socket. But the comparison at the edge is not HTTP versus a socket; it is HTTP versus nothing, because the socket option does not exist. And in serverless, the socket client pays its own hidden costs: connection setup on cold starts and connection storms under scale-out, which a stateless HTTP request avoids entirely.

Do I need valkey-http-server, or can I point this at Valkey directly?

You need the gateway. Valkey itself speaks RESP over TCP and has no HTTP listener, so something must translate — that is valkey-http-server, deployed next to your Valkey instance inside your infrastructure. The pair is split deliberately: the gateway holds the stateful TCP connections where sockets are cheap, and the client stays a zero-dependency fetch wrapper that runs anywhere.

Is it safe to call this from a browser?

The transport is fine — it is ordinary HTTPS — but treat the token like any client-exposed credential. A token shipped to a browser can issue whatever commands the gateway allows it, so scope it: expose only the commands and key patterns that are safe for untrusted callers, or keep browser traffic behind your own API route that holds the token server-side. For edge and serverless code the token stays in environment secrets, which is the common case.