https://github.com/oslabs-beta/denostore
GraphQL caching solution for a Deno/Oak runtime environment that is modular, efficient and lightweight
https://github.com/oslabs-beta/denostore
cache caching deno graphql oak redis
Last synced: about 1 month ago
JSON representation
GraphQL caching solution for a Deno/Oak runtime environment that is modular, efficient and lightweight
- Host: GitHub
- URL: https://github.com/oslabs-beta/denostore
- Owner: oslabs-beta
- License: mit
- Created: 2022-02-09T02:48:45.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2024-12-10T16:14:31.000Z (5 months ago)
- Last Synced: 2025-03-30T11:35:22.177Z (about 2 months ago)
- Topics: cache, caching, deno, graphql, oak, redis
- Language: TypeScript
- Homepage:
- Size: 12.8 MB
- Stars: 87
- Watchers: 7
- Forks: 3
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# **DenoStore**
DenoStore brings modular and low latency caching of GraphQL queries to a Deno/Oak server.
[](https://github.com/oslabs-beta/DenoStore)
[](https://deno.land/x/denostore)
[](https://github.com/oslabs-beta/DenoStore/blob/main/LICENSE.md)
[]()**DenoStore Query Demo**

## Table of Contents
- [Description](#description)
- [Features](#features)
- [Installation](#installation)
- [Getting Started](#getting-started)
- [Server Setup](#server-setup)
- [Caching](#caching)
- [Expiration](#expiration)
- [Further Documentation](#documentation)
- [Contributions](#contributions)
- [Developers](#developers)
- [License](#license)When implementing caching of GraphQL queries there are a few main issues to consider:
- Cache becoming stale/cache invalidation
- More unique queries and results compared to REST due to granularity of GraphQL
- Lack of built-in caching support (especially for Deno)DenoStore was built to address the above challenges and empowers users with a caching tool that is modular, efficient and quick to implement.
- Seamlessly embeds caching functionality at query resolver level, giving implementing user modular decision making power to cache specific queries and not others
- Caches resolver results rather than query results - so subsequent queries with different fields and formats can still receive existing cached values
- Leverages _[Redis](https://redis.io/)_ as an in-memory low latency server-side cache
- Integrates with _[Oak](https://oakserver.github.io/oak/)_ middleware framework to handle GraphQL queries with error handling
- Provides global and resolver level expiration controls
- Makes _GraphQL Playground IDE_ available for constructing and sending queries during development
- Supports all GraphQL query options (e.g. arguments, directives, variables, fragments)### Redis
DenoStore uses Redis data store for caching
- If you do not yet have Redis installed, please follow the instructions for your operation system here: https://redis.io/docs/getting-started/installation/
- After installing, start the Redis server by running `redis-server`
- You can test that your Redis server is running by connecting with the Redis CLI:```sh
redis-cli
127.0.0.1:6379> ping
PONG
```- To stop your Redis server:
`redis-cli shutdown`- To restart your Redis server:
`redis-server restart`- Redis uses port `6379` by default
### DenoStore
DenoStore 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.
```ts
import { DenoStore } from 'https://deno.land/x/denostore@/mod.ts';
```### Oak
DenoStore 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.
**Using v10.2.0 is highly recommended**
```ts
import { Application } from 'https://deno.land/x/[email protected]/mod.ts';
```Implementing DenoStore takes only a few steps and since it is modular you can implement caching to your query resolvers incrementally if desired.
To set up your server:
- Import _Oak_, _DenoStore_ class and your _schema_
- Create a new instance of DenoStore with your desired configuration
- Add the route to handle GraphQL queries ('/graphql' by default)Below 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.
```ts
// imports
import { Application } from 'https://deno.land/x/[email protected]/mod.ts';
import { DenoStore } from 'https://deno.land/x/denostore@/mod.ts';
import { typeDefs, resolvers } from './yourSchema.ts';const PORT = 3000;
const app = new Application();
// configure DenoStore instance
const ds = new DenoStore({
route: '/graphql',
usePlayground: true,
schema: { typeDefs, resolvers },
redisPort: 6379,
});// add dedicated route
app.use(ds.routes(), ds.allowedMethods());
```**How do I set up caching?**
After 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.
**Accessing DenoStore methods using `ds` from `context`**
```ts
oneRocket: async (
_parent: any,
args: any,
// destructuring ds off context
{ ds }: any,
info: any
)
```Alternatively, you can access ds from context without destructuring (e.g. `context.ds.cache`)
#### Cache Implementation Example
Here 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.
**No DenoStore**
```ts
Query: {
oneRocket: async (
_parent: any,
args: any,
context: any,
info: any
) => {
const results = await fetch(
`https://api.spacexdata.com/v3/rockets/${args.id}`
)
.then(res => res.json())
.catch(err => console.log(err))return results;
},
```**DenoStore Caching**
```ts
Query: {
oneRocket: async (
_parent: any,
args: any,
{ ds }: any,
info: any
) => {
return await ds.cache({ info }, async () => {
const results = await fetch(
`https://api.spacexdata.com/v3/rockets/${args.id}`
)
.then(res => res.json())
.catch(err => console.log(err))return results;
});
},
```As you can see, it only takes a few lines of code to add modular caching exactly how and where you need it.
**Cache Method**
```ts
ds.cache({ info }, callback);
````cache` is an asynchronous method that takes two arguments:
- 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
- A callback function with your data store call to execute if the results are not in the cacheExpiration time for cached results can be set for each resolver and/or as a global default.
#### Setting expiration in the cache method
You can easily pass in cache expiration time in seconds as a value to the `ex` property to the cache method's first argument object:
```ts
// cached value will expire in 5 seconds
ds.cache({ info, ex: 5 }, callback);
```#### Setting global expiration in DenoStore config
You can also add the `defaultEx` property with value expiration time in seconds when configuring the `ds` instance on your server.
```ts
// configure DenoStore instance
const ds = new DenoStore({
route: '/graphql',
usePlayground: true,
schema: { typeDefs, resolvers },
redisPort: 6379,
// default expiration set globally to 5 seconds
defaultEx: 5,
});
```When determining expiration for a cached value, DenoStore will always prioritize expiration time in the following order:
1. `ex` property in resolver `cache` method
2. `defaultEx` property in DenoStore configuration
3. 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### Clearing Cache
#### DenoStore Clear Method
There 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.
```ts
Mutation: {
cancelTrip: async (
_parent: any,
args: launchId,
{ ds }: any
) => {
const result = await dataSources.userAPI.cancelTrip({ launchId });
if (!result)
return {
success: false,
message: 'failed to cancel trip',
};// clear/invalidate cache after successful mutation
await ds.clear();return result;
},
```#### Clearing with redis-cli
You can also clear the Redis cache at any time using the redis command line interface.
Clear keys from all databases on Redis instance
```sh
redis-cli flushall
```Clear keys from all databases without blocking your server
```sh
redis-cli flushall async
```Clear keys from currently selected database (if using same Redis client for other purposes aside from DenoStore)
```sh
redis-cli flushdb
```We welcome contributions to DenoStore as they are key to growing the Deno ecosystem and community.
### Start Contributing
1. Fork and clone the repository
2. Ensure [Deno](https://deno.land/manual/getting_started/installation) and [Redis](https://redis.io/docs/getting-started/) are installed on your machine
3. Redis server must be [running](#installation) to use DenoStore
4. Checkout feature/issue branch off of _main_ branch### Running Testing
1. Make sure Redis server is [running](#installation) on port _6379_ when testing
2. To run all tests run `deno test tests/ --allow-net`
3. If tests pass you can submit a PR to the DenoStore _main_ branch- [Jake Van Vorhis](https://github.com/jakedoublev)
- [James Kim](https://github.com/Jamesmjkim)
- [Jessica Wachtel](https://github.com/JessicaWachtel)
- [Scott Tatsuno](https://github.com/sktatsuno)
- [TX Ho](https://github.com/lawauditswe)This product is licensed under the MIT License - see the LICENSE.md file for details.
This is an open source product.
This product is accelerated by [OS Labs](https://opensourcelabs.io/).