Open Source
valkey-om

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

valkey-om: Object Mapping for Valkey and Node.js

valkey-om is an object-mapping library for Valkey (opens in a new tab) and Node.js, written in TypeScript. You define a schema once, get a repository back, and from then on you save, fetch, and search plain typed objects — stored under the hood as Valkey Hashes or JSON documents. It is built on iovalkey (opens in a new tab) and is a Valkey-native port of redis-om-node. I created and maintain this package as the top application-facing layer of the Valkey Node.js client stack.

Packagevalkey-om (opens in a new tab) v0.1.0
LicenseMIT
LanguageTypeScript (declarations included)
Built oniovalkey
Node.js≥ 14
Server requirementValkey with search + JSON modules (valkey-bundle)
Sourcegithub.com/webdevelopersrinu/valkey-om-node (opens in a new tab)

The Problem: Hand-Rolled Mapping Everywhere

Valkey gives you data structures, not objects. The moment your application stores anything richer than a string — a user, an order, an album — you end up writing the same glue in every project:

  1. Manual command calls. HSET, HGETALL, JSON.SET, JSON.GET scattered through application code, each call site repeating the key-naming convention (album:01H...) by hand.
  2. Manual serialization. Hashes store flat strings, so every number, boolean, and date gets stringified on the way in and parsed on the way out — and every developer does it slightly differently.
  3. No typed contract. Nothing stops one code path from writing year as "2014" and another from reading it as a number. TypeScript can't help you across a raw command boundary.
  4. Search is a second, separate chore. Building an index, writing raw search queries, and mapping result documents back into objects is its own pile of boilerplate on top.

An object mapper collapses all four into one declaration. The schema is the single place where field names, types, and the storage structure are defined — and the repository derived from it handles key generation, serialization, index creation, and query building.

schema.ts
import { Client, Schema } from 'valkey-om'

const client = new Client()
await client.open('valkey://localhost:6379')

// One declaration: field names, types, and the storage structure.
const albumSchema = new Schema('album', {
  artist: { type: 'string' },   // tag field — exact matching
  title:  { type: 'text' },     // full-text field
  year:   { type: 'number' },
}, { dataStructure: 'JSON' })

const albumRepository = client.fetchRepository(albumSchema)
await albumRepository.createIndex()

From here on, application code never touches a Valkey command. It works with plain objects.

save-and-fetch.ts
import { EntityId } from 'valkey-om'

// Save a plain object — valkey-om generates the id and writes the JSON document.
const album = await albumRepository.save({
  artist: 'Mushroomhead',
  title: 'The Righteous & The Butterfly',
  year: 2014,
})

// The generated id travels on the entity under the EntityId symbol.
const fetched = await albumRepository.fetch(album[EntityId])

Search: Fluent Queries Over Your Schema

Because the schema declares each field's type, the repository can build typed search queries. string fields match exactly, text fields match full-text, number fields compare — all through one fluent API:

search.ts
// Exact match on a tag field
const byArtist = await albumRepository.search()
  .where('artist').equals('Mushroomhead')
  .return.all()

// Range query on a number field
const recent = await albumRepository.search()
  .where('year').gte(2000)
  .return.all()

// Full-text match on a text field
const byText = await albumRepository.search()
  .where('title').matches('righteous')
  .return.all()

Search requires a Valkey server with the search and JSON modules loaded. The easiest way to get one is the official valkey-bundle image:

terminal
docker run -d -p 6379:6379 valkey/valkey-bundle:latest

npm install valkey-om iovalkey

Bring Your Own Connection

In a real application the Valkey connection is usually owned by something else — a shared module, a DI container, a connection with its own retry and TLS configuration. valkey-om does not insist on opening its own: pass an existing iovalkey client and it mounts on top.

own-connection.ts
import Valkey from 'iovalkey'
import { Client } from 'valkey-om'

const valkey = new Valkey({ host: 'localhost', port: 6379 })
const client = await new Client().use(valkey)

// ...

await client.close()

Field Types

The schema vocabulary covers the common shapes an entity needs, for both HASH and JSON data structures:

TypeIndexed asTypical use
stringTagIds, categories, exact-match fields
textFull-textTitles, descriptions, anything searched by words
numberNumericCounts, years, prices — range queries
booleanTagFlags
dateNumericTimestamps — range queries
pointGeoCoordinates

Array variants of these types are available as well.

Design Notes — Decisions I Kept and Why

Built on iovalkey, not a bundled connection layer

valkey-om does not reimplement connection management. It depends on iovalkey — the Valkey-native client — and exposes client.use(valkey) so an application's existing connection, with its own pooling, TLS, and retry configuration, carries the mapper. One connection stack for the ecosystem, not two.

TypeScript first

The package is written in TypeScript and ships declarations from dist/index.d.ts. The schema definition is where typing starts, so the fluent search API and repository methods stay checked end to end in a TypeScript application.

Two data structures, one API

A schema chooses HASH or JSON as its dataStructure, and nothing else in the application changes — save, fetch, and search read identically. Hashes keep entities flat and compact; JSON documents allow richer shapes. The decision lives in one line of the schema, not in every call site.

Provenance

valkey-om is a Valkey-native port of an upstream Node.js ODM (redis-om-node); the original copyright is preserved in the LICENSE. As with the rest of the Valkey client stack, the point of the port is ownership: the Valkey ecosystem gets its object mapper under its own name, publishing, and maintenance — while migration from the Redis-branded original stays mechanical.

This is a solo project — I designed, built, and maintain valkey-om independently as the object-mapping layer of the Valkey Node.js client stack.

Where It Sits in the Stack

valkey-om is the top of the stack I maintain: valkey-errors defines failure identity at the bottom, valkey-parser decodes the wire protocol, iovalkey manages connections and commands, and valkey-om turns all of it into plain typed objects your application code actually wants to work with.

← Back to Open Source overview


Frequently Asked Questions

Why not just use iovalkey directly?

You can — valkey-om is built on it, and client.use() accepts your existing iovalkey connection. The difference is where the mapping logic lives. With raw iovalkey, every project re-implements key naming, serialization, and search query construction by hand at each call site. With valkey-om, the schema declares all of that once, and the repository enforces it everywhere.

Does valkey-om work with a plain Valkey server?

Saving and fetching entities needs the JSON module for the JSON data structure, and search needs the search module. The simplest setup is the official valkey-bundle Docker image, which ships both modules preloaded: docker run -d -p 6379:6379 valkey/valkey-bundle:latest.

How is valkey-om different from redis-om-node?

It is a Valkey-native port: same object-mapping model — schemas, repositories, fluent search — targeting Valkey and built on iovalkey instead of a Redis client. The upstream copyright is preserved in the LICENSE. The port exists so the Valkey ecosystem owns its object mapper's naming, publishing, and maintenance, while migration from the original stays mechanical.

Should I store entities as HASH or JSON?

Both are supported behind the same API — the choice is one option on the schema (dataStructure). Hashes are flat and compact, a good fit for simple entities. JSON documents allow richer shapes and require the JSON module. Because the repository API is identical for both, switching later is a schema change, not an application rewrite.