{"id":41475845,"url":"https://github.com/alexanderatallah/redbase","last_synced_at":"2026-01-23T17:06:35.375Z","repository":{"id":124977450,"uuid":"594863107","full_name":"alexanderatallah/redbase","owner":"alexanderatallah","description":"A simple, fast, type-safe database on top of Redis","archived":false,"fork":false,"pushed_at":"2023-07-14T15:00:50.000Z","size":2421,"stargazers_count":44,"open_issues_count":4,"forks_count":6,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-10-03T05:53:07.609Z","etag":null,"topics":["caching","redis","redis-database","set-theory","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alexanderatallah.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-01-29T21:22:57.000Z","updated_at":"2025-02-23T09:00:13.000Z","dependencies_parsed_at":"2024-12-18T20:00:47.424Z","dependency_job_id":"8a4da24c-50c3-4426-9738-52aa460701c0","html_url":"https://github.com/alexanderatallah/redbase","commit_stats":{"total_commits":55,"total_committers":1,"mean_commits":55.0,"dds":0.0,"last_synced_commit":"aa2a2bfb7e7b26e9296197700241191c31cf59d8"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":"alexanderatallah/typescript-npm-package-template","purl":"pkg:github/alexanderatallah/redbase","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexanderatallah%2Fredbase","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexanderatallah%2Fredbase/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexanderatallah%2Fredbase/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexanderatallah%2Fredbase/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alexanderatallah","download_url":"https://codeload.github.com/alexanderatallah/redbase/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexanderatallah%2Fredbase/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28696521,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-23T15:57:05.722Z","status":"ssl_error","status_checked_at":"2026-01-23T15:56:27.656Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["caching","redis","redis-database","set-theory","typescript"],"created_at":"2026-01-23T17:06:34.654Z","updated_at":"2026-01-23T17:06:35.363Z","avatar_url":"https://github.com/alexanderatallah.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Redbase\n\nA simple, fast, indexed, and type-safe database on top of Redis.\n\nBuilt to answer the question, \"how can I have a queryable, browsable db that also works well as a cache?\"\n\n## Goals\n\n- **Simple**: less than 500 lines. Only one dependency, `ioredis`. You can copy-paste the code instead if you want.\n- **Serverless-friendly**: no Redis modules, only core Redis.\n- **Fast**: Compared to optimized Postgres, 150% faster at paginating unindexed data. See [all benchmarks](#benchmarks) below.\n- **Indexable**: Supports hierarchical [tags](#tags), a lightweight primitive for indexing your data.\n- **Browsable**: [browser-friendly API](#example-browsing-your-data) included for paginating and browsing by tag.\n\n_Non-goals_\n\n- No dependencies on Redis modules. Useful for deploying on platforms like [Upstash](https://upstash.com/).\n- Never queries \"KEYS\" on your redis instance, which is expensive. Uses simple [set theory](https://github.com/alexanderatallah/redbase/blob/main/src/redbase.ts#L445) and data structures to implement query logic.\n\nIn a few lines:\n```ts\nimport { Redbase } from 'redbase'\nconst db = new Redbase\u003cMyDataType\u003e('my-project')\nconst value = await db.get(id) // type: MyDataType\n```\n\nExploration API:\n`npm run server`\n\n[![npm package][npm-img]][npm-url]\n[![Downloads][downloads-img]][downloads-url]\n[![Issues][issues-img]][issues-url]\n[![Code Coverage][codecov-img]][codecov-url]\n[![Commitizen Friendly][commitizen-img]][commitizen-url]\n[![Semantic Release][semantic-release-img]][semantic-release-url]\n\n- [Redbase](#redbase)\n  - [Goals](#goals)\n  - [Install](#install)\n  - [Usage](#usage)\n  - [Core concepts](#core-concepts)\n    - [Entries](#entries)\n    - [Tags](#tags)\n    - [Example: Prompt Cache](#example-prompt-cache)\n    - [Example: Browsing Your Data](#example-browsing-your-data)\n  - [Benchmarks](#benchmarks)\n    - [For the cache use-case](#for-the-cache-use-case)\n    - [For the database use-case](#for-the-database-use-case)\n    - [Results](#results)\n  - [License](#license)\n\n## Install\n\n```bash\nnpm install redbase\n```\n\n## Usage\n\n```ts\nimport { Redbase } from 'redbase'\n\n// Can use strings, numbers or buffers as well\ntype MyValue = {\n  a: string\n  b?: {\n    c: string\n  }\n}\n\n// Options can also use your own ioredis instance if already defined,\n// as `redisInstance`\nconst db = new Redbase\u003cMyValue\u003e('myProject', { redisUrl: 'redis://...' })\n\nconst key = uuid()\nconst value = { a: 'result' }\n\nawait db.get(key) // undefined\n\nawait db.save(key, value)\n\nawait db.get(key) // value\n\n// Type safety!\nawait db.save(key, { c: 'poorly formed object' }) // Type error on value\n\n// Browsing results\nlet data = await db.filter()\nassertEqual(data, [{ id: key, value }])\nassertEqual(await db.count(), 1)\n\n// Hierarchical indexes, using a customizable tag separator (default: '/')\nawait Promise.all([\n  // Redis auto-pipelines these calls into one fast request!\n  db.save(uuid(), { a: 'hi' }, { tags: ['user1/project1'] }),\n  db.save(uuid(), { a: 'there' }, { tags: ['user1/project2'] }),\n  db.save(uuid(), { a: 'bye' }, { tags: ['user2/project1'] })\n])\n\ndata = await db.filter()\nassertEqual(data.length, 4)\n\n// WHERE queries, using tag names\ndata = await db.filter({ where: 'user1'})\nassertEqual(data.length, 2)\n\n// AND and OR queries:\ndata = await db.filter({ where: {OR: ['user1', 'user2']}})\nassertEqual(data.length, 3)\n\nconst count = await db.count({ where: {OR: ['user1', 'user2']}})\nassertEqual(count, 3)\n\n// See all your indexes:\nconst tags = await db.tags(\"user1\")\nassertEqual(tags.length, 2)\n\n// Clear just parts of the database:\nconst numberDeleted = await db.clear({ where: 'user2' })\nassertEqual(numberDeleted, 1)\n```\n\nFor all functionality, see `test/database.spec.ts`.\n\n## Core concepts\n\nThere are two main concepts in Redbase: entries and tags. This section explains them and then provides example code combining both.\n\n### Entries\n\nAn entry is composed of an `id` and a `value`:\n- Values are type-checked, but schemaless.\n- IDs are strings. You are in charge of creating them, e.g. by hashing your value, making a UUID, using some external id, etc.\n- If you have a non-string key that you want to use with your value (as is the case when you're storing prompts + completions), you can combine them into one value type. See [the prompt cache example](#example-prompt-cache) below.\n\n### Tags\n\nTags are a lightweight primitive for indexing your values. You attach them at insert-time, and they are schemaless. This makes them simple and flexible for many use cases. It also allows you to index values by external attributes (that aren't a part of the value itself: [example](#example-prompt-cache)).\n\nCalling `db.filter({ where: { AND: [...]}})` etc. allows you to compose them together as a list.\n\nTags are sort of self-cleaning: indexes delete themselves during bulk-delete operations, and they shrink when entries are deleted individually. However, when the last entry for an index is deleted, the index is not. This shouldn't cause a significant zombie index issue unless you're creating and wiping out an unbounded number of tags.\n\nTags can get unruly, you you can keep them organized by nesting them:\n`parentindex/childindex`. This effectively allows you to group your indexes, and makes [browsing](#example-browsing-your-data) your data easier and more fun.\n\nCall `db.tags(\"parentindex\")` to get all the children tags.\n\nAs you might expect, when indexing an entry under `parentindex/childindex` the entry is automatically indexed under `parentindex` as well. This makes it easy to build a url-based [cache exploration server](#example-browsing-your-data). Call `db.filter({ where: 'parentindex' })` to get all entries for all children tags.\n\n\n### Example: Prompt Cache\n\nSample code for setting up the prompt cache for a large language model (LLM), like [OpenAI](https://platform.openai.com/docs/introduction):\n\n```ts\nimport { CreateCompletionRequest, CreateCompletionResponse, OpenAIApi } from 'openai'\n\nexport const CACHE_NAME = 'promptCache'\nconst CACHE_EXPIRATION = 60 * 60 * 24 * 7 * 4 * 2 // 2 months\n\nexport type PromptQuery = CreateCompletionRequest\nexport type PromptResult = CreateCompletionResponse\nexport type PromptEntry = { prompt: PromptQuery, completion: PromptResult }\nexport const promptCache = new Redbase\u003cPromptEntry\u003e(CACHE_NAME, { defaultTTL: CACHE_EXPIRATION })\n```\n\nNow, you can cache and index your prompts by doing this:\n\n```ts\n// Tags can include metadata that isn't present in the value itself.\nconst tags = [\n  `user-${user || \"\"}`,\n  `url-${encodeURIComponent(url)}/leaf-${leafNumber}`\n]\nconst payload = { ...requestData, user }\nconst response = await openai.createCompletion(payload)\nawait promptCache.save(id, {\n  prompt: requestData,\n  completion: response.data\n}, { tags })\n```\n\n### Example: Browsing Your Data\n\nTo browse, paginate, filter, and delete your data directly from a browser, just fire up the API:\n\n`npm run server`\n\n![API explorer](files/explorer.png)\n\n## Benchmarks\n\n**Note:** I'm very new to benchmarking open-sourced code, and would appreciate pull requests here! One issue, for example, is that increasing the number of runs can cause the data to scale up (depending on which benchmarks you're running), which seems to e.g. make Redis win on pagination by a larger margin.\n\nThis project uses [hyperfine](https://github.com/sharkdp/hyperfine) to compare Redis in a persistent mode with Postgres in an optimized mode. **Yes, this is comparing apples to oranges.** I decided to do it anyway because:\n\n1. A big question this project answers is \"how can I have a queryable, browsable db that also works well as a cache?\" Redis and Postgres are two backend choices that pop up frequently.\n\n2. Not many people seem to know that Redis has an append-only file (AOF) persistence option. I heard about this and was curious to see how it performed.\n\n3. I wanted to compare with an on-disk database solution to get a sense of where the advantage lies for using a persistent, in-memory database as a primary database.\n\nAll that said, the benchmarks put Redis and Postgres in [roughly equivalent persistence settings](http://oldblog.antirez.com/post/redis-persistence-demystified.html), which you can play with:\n\n- Redis is set up using `SET appendfsync everysec`, which issues `fsync` calls between 0 and 2 seconds.\n\n- Postgres is set up using `SET synchronous_commit=OFF`, which delays writing to WAL by 0.6 seconds.\n\nThese settings were chosen because they somewhat balance the speed needs of a cache with the durability needs of a database. To use one or the other:\n\n### For the cache use-case\nComment-out the Redis config lines in `setupRedis` in `/bench/redis`. Similarly configure your project's Redis setup, of course, though it's the Redis default.\n\n### For the database use-case\nComment-in the Redis config line `redis.config('SET', 'appendfsync', 'always')` in `/bench/redis.ts`. Similarly configure your project's Redis setup, of course.\n\nComment-out the call to `ALTER DATABASE ... SET synchronous_commit=OFF;` in `/bench/postgres.ts`. This reverts to the default (fully-persistent) Postgres setting.\n\n### Results\n- **Inserting data**: Tie\n- **Paginating unindexed data**: Redis is ~150% faster\n- **Single-index pagination**: Postgres is ~50% faster\n- **Joint-index pagination**: Postgres is ~60% faster\n- **Inserting and deleting data**: Redis is ~25% faster\n\nResults on Apple M1 Max, 2021:\n![Insert and scroll](files/insert_and_scroll.png)\n![Scroll along an index](files/index_scrolling.png)\n![Delete data](files/deleting.png)\n\n## License\nMIT\n\n[build-img]: https://github.com/alexanderatallah/redbase/actions/workflows/release.yml/badge.svg\n[build-url]: https://github.com/alexanderatallah/redbase/actions/workflows/release.yml\n[downloads-img]: https://img.shields.io/npm/dt/redbase\n[downloads-url]: https://www.npmtrends.com/redbase\n[npm-img]: https://img.shields.io/npm/v/redbase\n[npm-url]: https://www.npmjs.com/package/redbase\n[issues-img]: https://img.shields.io/github/issues/alexanderatallah/redbase\n[issues-url]: https://github.com/alexanderatallah/redbase/issues\n[codecov-img]: https://codecov.io/gh/alexanderatallah/redbase/branch/main/graph/badge.svg\n[codecov-url]: https://codecov.io/gh/alexanderatallah/redbase\n[semantic-release-img]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg\n[semantic-release-url]: https://github.com/semantic-release/semantic-release\n[commitizen-img]: https://img.shields.io/badge/commitizen-friendly-brightgreen.svg\n[commitizen-url]: http://commitizen.github.io/cz-cli/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexanderatallah%2Fredbase","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexanderatallah%2Fredbase","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexanderatallah%2Fredbase/lists"}