{"id":26267173,"url":"https://github.com/oslabs-beta/denostore","last_synced_at":"2025-06-29T17:04:04.035Z","repository":{"id":40475925,"uuid":"457184275","full_name":"oslabs-beta/DenoStore","owner":"oslabs-beta","description":"GraphQL caching solution for a Deno/Oak runtime environment that is modular, efficient and lightweight","archived":false,"fork":false,"pushed_at":"2024-12-10T16:14:31.000Z","size":13388,"stargazers_count":87,"open_issues_count":0,"forks_count":3,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-05-19T13:09:29.685Z","etag":null,"topics":["cache","caching","deno","graphql","oak","redis"],"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/oslabs-beta.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,"zenodo":null},"funding":{"github":"open-source-labs","custom":["https://donorbox.org/donations-to-oslabs-inc"]}},"created_at":"2022-02-09T02:48:45.000Z","updated_at":"2024-12-10T16:14:36.000Z","dependencies_parsed_at":"2024-12-10T17:34:11.645Z","dependency_job_id":null,"html_url":"https://github.com/oslabs-beta/DenoStore","commit_stats":{"total_commits":49,"total_committers":5,"mean_commits":9.8,"dds":0.4285714285714286,"last_synced_commit":"995d18e407744136031fe560273fc09aa5c0ca4a"},"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/oslabs-beta/DenoStore","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oslabs-beta%2FDenoStore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oslabs-beta%2FDenoStore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oslabs-beta%2FDenoStore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oslabs-beta%2FDenoStore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oslabs-beta","download_url":"https://codeload.github.com/oslabs-beta/DenoStore/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oslabs-beta%2FDenoStore/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260277500,"owners_count":22985102,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["cache","caching","deno","graphql","oak","redis"],"created_at":"2025-03-14T04:15:57.511Z","updated_at":"2025-06-29T17:04:03.998Z","avatar_url":"https://github.com/oslabs-beta.png","language":"TypeScript","funding_links":["https://github.com/sponsors/open-source-labs","https://donorbox.org/donations-to-oslabs-inc"],"categories":[],"sub_categories":[],"readme":"# **DenoStore**\n\nDenoStore brings modular and low latency caching of GraphQL queries to a Deno/Oak server.\n\n[![Tests Passing](https://img.shields.io/badge/tests-passing-green)](https://github.com/oslabs-beta/DenoStore)\n[![Custom badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fdeno-visualizer.danopia.net%2Fshields%2Flatest-version%2Fx%2Fdenostore%2Fmod.ts)](https://deno.land/x/denostore)\n[![License](https://img.shields.io/badge/license-MIT-orange)](https://github.com/oslabs-beta/DenoStore/blob/main/LICENSE.md)\n[![Contributions](https://img.shields.io/badge/contributions-welcome-blue)]()\n\n**DenoStore Query Demo**\n\n![](img/DenoStoreDemo.gif)\n\n## Table of Contents\n\n- [Description](#description)\n- [Features](#features)\n- [Installation](#installation)\n- [Getting Started](#getting-started)\n  - [Server Setup](#server-setup)\n  - [Caching](#caching)\n  - [Expiration](#expiration)\n- [Further Documentation](#documentation)\n- [Contributions](#contributions)\n- [Developers](#developers)\n- [License](#license)\n\n## \u003ca name=\"description\"\u003e\u003c/a\u003e Description\n\nWhen implementing caching of GraphQL queries there are a few main issues to consider:\n\n- Cache becoming stale/cache invalidation\n- More unique queries and results compared to REST due to granularity of GraphQL\n- Lack of built-in caching support (especially for Deno)\n\nDenoStore was built to address the above challenges and empowers users with a caching tool that is modular, efficient and quick to implement.\n\n## \u003ca name=\"features\"\u003e\u003c/a\u003e Features\n\n- Seamlessly embeds caching functionality at query resolver level, giving implementing user modular decision making power to cache specific queries and not others\n- Caches resolver results rather than query results - so subsequent queries with different fields and formats can still receive existing cached values\n- Leverages _[Redis](https://redis.io/)_ as an in-memory low latency server-side cache\n- Integrates with _[Oak](https://oakserver.github.io/oak/)_ middleware framework to handle GraphQL queries with error handling\n- Provides global and resolver level expiration controls\n- Makes _GraphQL Playground IDE_ available for constructing and sending queries during development\n- Supports all GraphQL query options (e.g. arguments, directives, variables, fragments)\n\n## \u003ca name=\"installation\"\u003e\u003c/a\u003e Installation\n\n### Redis\n\nDenoStore uses Redis data store for caching\n\n- If you do not yet have Redis installed, please follow the instructions for your operation system here: https://redis.io/docs/getting-started/installation/\n- After installing, start the Redis server by running `redis-server`\n- You can test that your Redis server is running by connecting with the Redis CLI:\n\n```sh\nredis-cli\n127.0.0.1:6379\u003e ping\nPONG\n```\n\n- To stop your Redis server:\n  `redis-cli shutdown`\n\n- To restart your Redis server:\n  `redis-server restart`\n\n- Redis uses port `6379` by default\n\n### DenoStore\n\nDenoStore is hosted as a third-party module at https://deno.land/x/denostore and will be installed the first time you import it and run your server. It is recommended to specify the latest DenoStore version so Deno does not use a previously cached version.\n\n```ts\nimport { DenoStore } from 'https://deno.land/x/denostore@\u003clatestversion\u003e/mod.ts';\n```\n\n### Oak\n\nDenoStore uses the popular middleware framework Oak https://deno.land/x/oak to set up routes for handling GraphQL queries and optionally using the _GraphQL Playground IDE_. Like DenoStore, Oak will be installed directly from deno.land the first time you run your server unless you already have it cached.\n\n**Using v10.2.0 is highly recommended**\n\n```ts\nimport { Application } from 'https://deno.land/x/oak@v10.2.0/mod.ts';\n```\n\n## \u003ca name=\"getting-started\"\u003e\u003c/a\u003e Getting Started\n\nImplementing DenoStore takes only a few steps and since it is modular you can implement caching to your query resolvers incrementally if desired.\n\n### \u003ca name=\"server-setup\"\u003e\u003c/a\u003e Server Setup\n\nTo set up your server:\n\n- Import _Oak_, _DenoStore_ class and your _schema_\n- Create a new instance of DenoStore with your desired configuration\n- Add the route to handle GraphQL queries ('/graphql' by default)\n\nBelow is a simple example of configuring DenoStore for your server file, but there are several configuration options. Please refer to the [docs](http://denostore.io/docs) for more details.\n\n```ts\n// imports\nimport { Application } from 'https://deno.land/x/oak@v10.2.0/mod.ts';\nimport { DenoStore } from 'https://deno.land/x/denostore@\u003clatestversion\u003e/mod.ts';\nimport { typeDefs, resolvers } from './yourSchema.ts';\n\nconst PORT = 3000;\n\nconst app = new Application();\n\n// configure DenoStore instance\nconst ds = new DenoStore({\n  route: '/graphql',\n  usePlayground: true,\n  schema: { typeDefs, resolvers },\n  redisPort: 6379,\n});\n\n// add dedicated route\napp.use(ds.routes(), ds.allowedMethods());\n```\n\n### \u003ca name=\"caching\"\u003e\u003c/a\u003e Caching\n\n**How do I set up caching?**\n\nAfter your DenoStore instance is configured in your server, all GraphQL resolvers have access to that DenoStore instance and its methods through the `ds` property in each resolver's `context` object argument. Your schemas do not require any DenoStore imports.\n\n**Accessing DenoStore methods using `ds` from `context`**\n```ts\n    oneRocket: async (\n      _parent: any,\n      args: any,\n      // destructuring ds off context\n      { ds }: any,\n      info: any\n    )\n```\n\nAlternatively, you can access ds from context without destructuring (e.g. `context.ds.cache`)\n\n\n#### Cache Implementation Example\n\nHere is an example of a query resolver before and after adding the cache method from DenoStore. This is a simple query to pull information for a particular rocket from the SpaceX API.\n\n**No DenoStore**\n\n```ts\nQuery: {\n    oneRocket: async (\n      _parent: any,\n      args: any,\n\t  context: any,\n      info: any\n    ) =\u003e {\n        const results = await fetch(\n          `https://api.spacexdata.com/v3/rockets/${args.id}`\n        )\n        .then(res =\u003e res.json())\n        .catch(err =\u003e console.log(err))\n\n        return results;\n    },\n```\n\n**DenoStore Caching**\n\n```ts\nQuery: {\n    oneRocket: async (\n      _parent: any,\n      args: any,\n      { ds }: any,\n      info: any\n    ) =\u003e {\n      return await ds.cache({ info }, async () =\u003e {\n        const results = await fetch(\n          `https://api.spacexdata.com/v3/rockets/${args.id}`\n        )\n        .then(res =\u003e res.json())\n        .catch(err =\u003e console.log(err))\n\n        return results;\n      });\n    },\n```\n\nAs you can see, it only takes a few lines of code to add modular caching exactly how and where you need it.\n\n**Cache Method**\n\n```ts\nds.cache({ info }, callback);\n```\n\n`cache` is an asynchronous method that takes two arguments:\n\n- An object where **info** is the only required property. The GraphQL resolver's info argument must be passed as a property in this object as DenoStore parses the info AST for query information\n- A callback function with your data store call to execute if the results are not in the cache\n\n### \u003ca name=\"expiration\"\u003e\u003c/a\u003e Expiration\n\nExpiration time for cached results can be set for each resolver and/or as a global default.\n\n#### Setting expiration in the cache method\n\nYou can easily pass in cache expiration time in seconds as a value to the `ex` property to the cache method's first argument object:\n\n```ts\n// cached value will expire in 5 seconds\nds.cache({ info, ex: 5 }, callback);\n```\n\n#### Setting global expiration in DenoStore config\n\nYou can also add the `defaultEx` property with value expiration time in seconds when configuring the `ds` instance on your server.\n\n```ts\n// configure DenoStore instance\nconst ds = new DenoStore({\n  route: '/graphql',\n  usePlayground: true,\n  schema: { typeDefs, resolvers },\n  redisPort: 6379,\n  // default expiration set globally to 5 seconds\n  defaultEx: 5,\n});\n```\n\nWhen determining expiration for a cached value, DenoStore will always prioritize expiration time in the following order:\n\n1. `ex` property in resolver `cache` method\n2. `defaultEx` property in DenoStore configuration\n3. If no resolver or global expiration is set, cached values will **default to no expiration**. However, in the next section we discuss ways to clear the cache\n\n### Clearing Cache\n\n#### DenoStore Clear Method\n\nThere may be times when you want to clear the cache in resolver logic such as when you perform a mutation. In these cases you can invoke the DenoStore `clear` method.\n\n```ts\nMutation: {\n    cancelTrip: async (\n      _parent: any,\n      args: launchId,\n      { ds }: any\n    ) =\u003e {\n      const result = await dataSources.userAPI.cancelTrip({ launchId });\n        if (!result)\n          return {\n            success: false,\n            message: 'failed to cancel trip',\n          };\n\n        // clear/invalidate cache after successful mutation\n        await ds.clear();\n\n        return result;\n    },\n```\n\n#### Clearing with redis-cli\n\nYou can also clear the Redis cache at any time using the redis command line interface.\n\nClear keys from all databases on Redis instance\n\n```sh\nredis-cli flushall\n```\n\nClear keys from all databases without blocking your server\n\n```sh\nredis-cli flushall async\n```\n\nClear keys from currently selected database (if using same Redis client for other purposes aside from DenoStore)\n\n```sh\nredis-cli flushdb\n```\n\n## \u003ca name=\"contributions\"\u003e\u003c/a\u003e Contributions\n\nWe welcome contributions to DenoStore as they are key to growing the Deno ecosystem and community.\n\n### Start Contributing\n\n1. Fork and clone the repository\n2. Ensure [Deno](https://deno.land/manual/getting_started/installation) and [Redis](https://redis.io/docs/getting-started/) are installed on your machine\n3. Redis server must be [running](#installation) to use DenoStore\n4. Checkout feature/issue branch off of _main_ branch\n\n### Running Testing\n\n1. Make sure Redis server is [running](#installation) on port _6379_ when testing\n2. To run all tests run `deno test tests/ --allow-net`\n3. If tests pass you can submit a PR to the DenoStore _main_ branch\n\n## \u003ca name=\"developers\"\u003e\u003c/a\u003e Developers\n\n- [Jake Van Vorhis](https://github.com/jakedoublev)\n- [James Kim](https://github.com/Jamesmjkim)\n- [Jessica Wachtel](https://github.com/JessicaWachtel)\n- [Scott Tatsuno](https://github.com/sktatsuno)\n- [TX Ho](https://github.com/lawauditswe)\n\n## \u003ca name=\"license\"\u003e\u003c/a\u003e License\n\nThis product is licensed under the MIT License - see the LICENSE.md file for details.\n\nThis is an open source product.\n\nThis product is accelerated by [OS Labs](https://opensourcelabs.io/).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foslabs-beta%2Fdenostore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foslabs-beta%2Fdenostore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foslabs-beta%2Fdenostore/lists"}