npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@appaka/redis

v0.1.1

Published

A Redis client with @upstash/redis API compatibility, powered by ioredis

Readme

@appaka/redis

npm version License: MIT

A Redis client with @upstash/redis API compatibility, powered by ioredis.

Why?

If you're using @upstash/redis but want to connect to a standard Redis server (self-hosted, AWS ElastiCache, Redis Cloud, etc.) instead of Upstash's HTTP-based service, this library lets you do that with zero code changes.

| @upstash/redis | @appaka/redis | |----------------|---------------| | HTTP REST API | Native TCP protocol | | Higher latency | Lower latency | | Stateless | Persistent connections | | Upstash only | Any Redis server |

Installation

npm install @appaka/redis
# or
pnpm add @appaka/redis
# or
yarn add @appaka/redis

Quick Start

import { Redis } from '@appaka/redis'

// Connect to Redis
const redis = new Redis({
  host: 'localhost',
  port: 6379,
  password: 'your-password', // optional
})

// Use exactly like @upstash/redis
await redis.set('key', 'value')
const value = await redis.get('key')

// Automatic JSON serialization
await redis.set('user', { name: 'Alice', age: 30 })
const user = await redis.get('user') // { name: 'Alice', age: 30 }

Migration from @upstash/redis

Simply change the import and configuration:

- import { Redis } from '@upstash/redis'
+ import { Redis } from '@appaka/redis'

- const redis = new Redis({
-   url: 'https://your-instance.upstash.io',
-   token: 'your-token',
- })
+ const redis = new Redis({
+   host: 'localhost',
+   port: 6379,
+ })

// All your code works unchanged
await redis.set('key', 'value')
await redis.get('key')

Upstash-style URL (also supported)

const redis = new Redis({
  url: 'redis://localhost:6379',
})

// Or with authentication
const redis = new Redis({
  url: 'redis://username:password@localhost:6379',
})

Environment Variables

// Reads from UPSTASH_REDIS_REST_URL, REDIS_URL, or KV_REST_API_URL
const redis = Redis.fromEnv()

Configuration

ioredis-style (recommended)

const redis = new Redis({
  host: 'localhost',
  port: 6379,
  password: 'secret',
  username: 'default',    // Redis 6+ ACL
  db: 0,                  // Database index
  tls: {},                // TLS options for SSL
})

Options

const redis = new Redis(
  { host: 'localhost', port: 6379 },
  {
    automaticDeserialization: true, // Auto JSON.parse responses (default: true)
  }
)

API Reference

String Commands

// Set a value
await redis.set('key', 'value')
await redis.set('key', 'value', { ex: 60 })      // Expire in 60 seconds
await redis.set('key', 'value', { px: 60000 })   // Expire in 60000 milliseconds
await redis.set('key', 'value', { nx: true })    // Only set if not exists
await redis.set('key', 'value', { xx: true })    // Only set if exists

// Get a value
const value = await redis.get('key')             // Returns string or null
const user = await redis.get<User>('user')       // With TypeScript generics

// Delete keys
const count = await redis.del('key1', 'key2')    // Returns number deleted

// Check if keys exist
const exists = await redis.exists('key1', 'key2') // Returns count of existing

// Expiration
await redis.expire('key', 60)                    // Set TTL in seconds
const ttl = await redis.ttl('key')               // Get TTL in seconds

// Increment/Decrement
await redis.incr('counter')                      // +1
await redis.incrby('counter', 5)                 // +5
await redis.incrbyfloat('counter', 1.5)          // +1.5
await redis.decr('counter')                      // -1
await redis.decrby('counter', 5)                 // -5

// Multiple keys
const values = await redis.mget('key1', 'key2')  // Get multiple values
await redis.mset({ key1: 'v1', key2: 'v2' })     // Set multiple key-values

// String operations
await redis.setex('key', 60, 'value')            // Set with expiration
await redis.setnx('key', 'value')                // Set if not exists
await redis.strlen('key')                         // Get string length
await redis.append('key', 'suffix')               // Append to string
await redis.getrange('key', 0, 5)                 // Get substring
await redis.setrange('key', 0, 'new')             // Replace substring

Hash Commands

// Set fields
await redis.hset('user:1', 'name', 'Alice')
await redis.hset('user:1', { name: 'Alice', age: 30 })
await redis.hmset('user:1', { name: 'Alice', age: 30 }) // Set multiple
await redis.hsetnx('user:1', 'email', '[email protected]')   // Set if not exists

// Get fields
const name = await redis.hget('user:1', 'name')
const user = await redis.hgetall('user:1')        // { name: 'Alice', age: 30 }
const vals = await redis.hmget('user:1', 'name', 'age') // Get multiple
const keys = await redis.hkeys('user:1')          // Get all field names
const values = await redis.hvals('user:1')        // Get all values

// Field operations
await redis.hexists('user:1', 'name')             // Check field exists
await redis.hlen('user:1')                        // Number of fields
await redis.hstrlen('user:1', 'name')             // Length of field value
await redis.hincrby('user:1', 'age', 1)           // Increment integer
await redis.hincrbyfloat('user:1', 'score', 1.5)  // Increment float

// Delete fields
await redis.hdel('user:1', 'age', 'email')

// Scan fields
const [cursor, fields] = await redis.hscan('user:1', 0, { match: 'n*' })

List Commands

// Push values
await redis.lpush('list', 'a', 'b', 'c')         // Push to left
await redis.rpush('list', 'x', 'y', 'z')         // Push to right
await redis.lpushx('list', 'a')                  // Push only if list exists
await redis.rpushx('list', 'z')                  // Push only if list exists

// Pop values
const left = await redis.lpop('list')            // Pop from left
const right = await redis.rpop('list')           // Pop from right

// Get range
const items = await redis.lrange('list', 0, -1)  // Get all items
const first3 = await redis.lrange('list', 0, 2)  // Get first 3 items

// Element operations
const elem = await redis.lindex('list', 0)       // Get by index
await redis.lset('list', 0, 'new')               // Set by index
await redis.linsert('list', 'BEFORE', 'b', 'a')  // Insert before element
await redis.linsert('list', 'AFTER', 'b', 'c')   // Insert after element
const pos = await redis.lpos('list', 'b')        // Find element position

// Remove/trim
await redis.lrem('list', 1, 'a')                 // Remove count occurrences
await redis.ltrim('list', 0, 99)                 // Trim to range

// Get length
const len = await redis.llen('list')

Set Commands

// Add members
await redis.sadd('tags', 'redis', 'database', 'cache')

// Remove members
await redis.srem('tags', 'cache')

// Get all members
const tags = await redis.smembers('tags')        // ['redis', 'database']

// Check membership
const isMember = await redis.sismember('tags', 'redis')  // 1 or 0
const results = await redis.smismember('tags', 'redis', 'cache') // [1, 0]

// Random members
const random = await redis.srandmember('tags')    // Get random member
const popped = await redis.spop('tags')           // Remove and return random

// Set operations
const diff = await redis.sdiff('set1', 'set2')    // Difference
const inter = await redis.sinter('set1', 'set2')  // Intersection
const union = await redis.sunion('set1', 'set2')  // Union
await redis.sdiffstore('result', 'set1', 'set2')  // Store difference
await redis.sinterstore('result', 'set1', 'set2') // Store intersection
await redis.sunionstore('result', 'set1', 'set2') // Store union
await redis.smove('set1', 'set2', 'member')       // Move member

// Get size
const size = await redis.scard('tags')

// Scan members
const [cursor, members] = await redis.sscan('tags', 0, { match: 'r*' })

Sorted Set Commands

// Add members with scores
await redis.zadd('leaderboard',
  { score: 100, member: 'alice' },
  { score: 200, member: 'bob' },
)

// Get range by index
const top3 = await redis.zrange('leaderboard', 0, 2)
const withScores = await redis.zrange('leaderboard', 0, 2, { withScores: true })
const reversed = await redis.zrange('leaderboard', 0, 2, { rev: true })

// Get range by score
const byScore = await redis.zrange('leaderboard', 0, 100, { byScore: true })

// Get range by lex
const byLex = await redis.zrange('leaderboard', '[a', '[z', { byLex: true })

// Score operations
const score = await redis.zscore('leaderboard', 'alice')   // 100
const scores = await redis.zmscore('leaderboard', 'alice', 'bob') // [100, 200]
await redis.zincrby('leaderboard', 10, 'alice')            // Increment score

// Rank operations
const rank = await redis.zrank('leaderboard', 'alice')     // 0-based rank
const revRank = await redis.zrevrank('leaderboard', 'alice') // Reverse rank

// Count and pop
const count = await redis.zcount('leaderboard', 0, 100)    // Count in range
const lowest = await redis.zpopmin('leaderboard', 2)       // Pop lowest
const highest = await redis.zpopmax('leaderboard', 2)      // Pop highest

// Lex operations
const lexCount = await redis.zlexcount('leaderboard', '[a', '[z')

// Remove operations
await redis.zrem('leaderboard', 'alice')
await redis.zremrangebyrank('leaderboard', 0, 2)           // Remove by rank
await redis.zremrangebyscore('leaderboard', 0, 100)        // Remove by score
await redis.zremrangebylex('leaderboard', '[a', '[m')      // Remove by lex

// Get size
const size = await redis.zcard('leaderboard')

// Scan members
const [cursor, members] = await redis.zscan('leaderboard', 0)

Key Commands

// Key operations
const type = await redis.type('key')              // Get key type
const keys = await redis.keys('user:*')           // Find keys by pattern
await redis.rename('old', 'new')                  // Rename key
await redis.renamenx('old', 'new')                // Rename if new doesn't exist
await redis.copy('source', 'dest')                // Copy key
const random = await redis.randomkey()            // Get random key

// Expiration
await redis.expire('key', 60)                     // Expire in seconds
await redis.expireat('key', 1234567890)           // Expire at Unix timestamp
await redis.pexpire('key', 60000)                 // Expire in milliseconds
await redis.pexpireat('key', 1234567890000)       // Expire at ms timestamp
await redis.persist('key')                        // Remove expiration
const ttl = await redis.ttl('key')                // TTL in seconds
const pttl = await redis.pttl('key')              // TTL in milliseconds

// Delete keys
await redis.del('key1', 'key2')                   // Synchronous delete
await redis.unlink('key1', 'key2')                // Asynchronous delete
await redis.touch('key1', 'key2')                 // Update last access time

// Scan keys
const [cursor, keys] = await redis.scan(0, { match: 'user:*', count: 100 })

Other Commands

await redis.ping()                               // 'PONG'
await redis.echo('hello')                        // 'hello'
await redis.dbsize()                             // Number of keys
await redis.flushdb()                            // Clear current database
await redis.flushall()                           // Clear all databases

Pipeline

Batch multiple commands in a single round-trip:

const results = await redis
  .pipeline()
  .set('key1', 'value1')
  .set('key2', 'value2')
  .get('key1')
  .get('key2')
  .incr('counter')
  .exec()

// results = ['OK', 'OK', 'value1', 'value2', 1]

Transactions

Run commands atomically with MULTI:

const results = await redis
  .multi()
  .set('key', 'value')
  .incr('counter')
  .get('key')
  .exec()

// All commands run atomically

TypeScript Support

Full TypeScript support with generics:

interface User {
  name: string
  email: string
  age: number
}

// Type-safe get
const user = await redis.get<User>('user:1')
if (user) {
  console.log(user.name) // TypeScript knows this is a string
}

// Type-safe lists
const users = await redis.lrange<User>('users', 0, -1)

// Type-safe sets
await redis.sadd<User>('users', { name: 'Alice', email: '[email protected]', age: 30 })

Connection Management

// Access underlying ioredis client if needed
const ioredisClient = redis.ioredis

// Close connection gracefully
await redis.quit()

// Force close connection
redis.disconnect()

Compatibility

This library targets API compatibility with:

  • @upstash/redis: 1.36.1
  • ioredis: 5.9.2

Features Not Supported

These @upstash/redis features are HTTP-specific and not applicable:

  • readYourWrites / sync tokens (TCP connections are inherently consistent)
  • HTTP retry logic (ioredis handles reconnection automatically)
  • Telemetry headers
  • Platform-specific builds (Cloudflare Workers, Fastly)

Additional Features

Thanks to ioredis, you get extra features not available in @upstash/redis:

  • Persistent connections (lower latency)
  • Connection pooling
  • Pub/Sub support
  • Streams with blocking operations
  • Cluster support (via ioredis)
  • Sentinel support (via ioredis)

License

MIT

Author

Javier Perez

Contributing

Issues and pull requests are welcome at github.com/appaka/redis.