Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: @adonisjs/cache docs #157

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions content/docs/db.json
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,12 @@
"contentPath": "./digging_deeper/i18n.md",
"category": "Digging deeper"
},
{
"permalink": "digging-deeper/cache",
"title": "Cache",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Digging Deeper section is alphabetically sorted. So we should move up this sub-section.

"contentPath": "./digging_deeper/cache.md",
"category": "Digging deeper"
},
{
"permalink": "digging-deeper/locks",
"title": "Locks",
Expand Down
198 changes: 198 additions & 0 deletions content/docs/digging_deeper/cache.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
---
summary: Cache data to improve the performance of your application
---

# Cache

AdonisJS Cache (`@adonisjs/cache`) is a simple, lightweight wrapper built on top of [bentocache.dev](https://bentocache.dev) to cache data and enhance the performance of your application. It provides a straightforward and unified API to interact with various cache drivers, such as Redis, DynamoDB, PostgreSQL, in-memory caching, and more.

We highly encourage you to read the Bentocache documentation. Bentocache offers some advanced, optional concepts that can be very useful in certain situations, such as [multi-tiering](https://bentocache.dev/docs/multi-tier), [grace periods](https://bentocache.dev/docs/grace-periods), [timeouts](https://bentocache.dev/docs/timeouts), and more.

## Installation

Install and configure the `@adonisjs/cache` package by running the following command:

```sh
node ace add @adonisjs/cache
```

:::disclosure{title="See the steps performed by the add command"}

1. Installs the `@adonisjs/cache` package using the detected package manager.
2. Registers the following service provider inside the `adonisrc.ts` file:

```ts
{
providers: [
// ...other providers
() => import('@adonisjs/cache/cache_provider'),
]
}
```

3. Creates the `config/cache.ts` file.
4. Defines the environment variables for the selected cache drivers inside the `.env` file.

:::

## Configuration

The configuration file for the cache package is located at `config/cache.ts`. You can configure the default cache driver, the list of drivers, and their specific configurations.

See also: [Config stub](https://github.com/adonisjs/cache/blob/1.x/stubs/config.stub)

```ts
import { defineConfig, store, drivers } from '@adonisjs/cache'

const cacheConfig = defineConfig({
default: 'redis',
suppressL2Errors: false,
gracePeriod: {
enabled: true,
duration: '60s',
},
stores: {
/**
* Cache data only on DynamoDB
*/
dynamodb: store().useL2Layer(drivers.dynamodb({})),

/**
* Cache data using your Lucid-configured database
*/
database: store().useL2Layer(drivers.database({ connectionName: 'default' })),

/**
* Cache data in-memory as the primary store and Redis as the secondary store.
* If your application is running on multiple servers, then in-memory caches
* need to be synchronized using a bus.
*/
redis: store()
.useL1Layer(drivers.memory({ maxSize: 10 * 1024 * 1024 }))
.useL2Layer(drivers.redis({ connectionName: 'main' }))
.useBus(drivers.redisBus({ connectionName: 'main' })),
},
})

export default cacheConfig
```

In the code example above, we are setting up multiple layers for each cache store. This is called a [multi-tier caching system](https://bentocache.dev/docs/multi-tier). It lets us first check a fast in-memory cache (the first layer). If we do not find the data there, we will then use the distributed cache (the second layer).

### Redis

To use Redis as your cache system, you must install the `@adonisjs/redis` package and configure it. Refer to the documentation here: [Redis](../database/redis.md).

In `config/cache.ts`, you must specify a `connectionName`. This property should match the Redis configuration key in the `config/redis.ts` file.

### Database

The `database` driver has a peer dependency on `@adonisjs/lucid`. Therefore, you must install and configure this package to use the `database` driver.

In `config/cache.ts`, you must specify a `connectionName`. This property should correspond to the database configuration key in the `config/database.ts` file.

### Other drivers

You can use other drivers such as `memory`, `dynamodb`, `kysely` and `orchid`.

See [Cache Drivers](https://bentocache.dev/docs/cache-drivers) for more information.

## Usage

Once your cache is configured, you can import the `cache` service to interact with it. In the following example, we cache the user details for 5 minutes:

```ts
import cache from '@adonisjs/cache/services/main'
import router from '@adonisjs/core/services/router'

router.get('/user/:id', async ({ params }) => {
return cache.getOrSet({
key: `user:${params.id}`,
duration: '5m',
factory: async () => {
const user = await User.find(params.id)
return user.toJSON()
},
})
})
```

Key points to note here:

- We are using the [`getOrSet`](https://bentocache.dev/docs/methods#getorset) method. The first time the route is accessed, it will fetch the user details from the database and cache them for 5 minutes. Subsequent requests will return the cached value.
- The `factory` method is a callback that executes when the cache key is missing. The return value of the factory method is then cached.
- As you can see, we serialize the user's data using `user.toJSON()`. This is necessary because your data must be serialized to be stored in the cache. Classes such as Lucid models or instances of `Date` cannot be stored directly in caches like Redis or a database.

## Cache Service

The cache service exported from `@adonisjs/cache/services/main` is a singleton instance of the [BentoCache](https://bentocache.dev/docs/named-caches) class created using the configuration defined in `config/cache.ts`.

You can import the cache service into your application and use it to interact with the cache:

```ts
import cache from '@adonisjs/cache/services/main'

/**
* Without calling the `use` method, the methods you call on the cache service
* will use the default store defined in `config/cache.ts`.
*/
cache.put({ key: 'username', value: 'jul', ttl: '1h' })

/**
* Using the `use` method, you can switch to a different store defined in
* `config/cache.ts`.
*/
cache.use('dynamodb').put({ key: 'username', value: 'jul', ttl: '1h' })
```

You can find all available methods here: [BentoCache API](https://bentocache.dev/docs/methods).

```ts
await cache.namespace('users').set({ key: 'username', value: 'jul' })
await cache.namespace('users').get('username')

await cache.get('username')

await cache.set('username', 'jul')
await cache.setForever('username', 'jul')

await cache.getOrSet({
key: 'username',
ttl: '1h',
factory: async () => fetchUserName(),
})

await cache.has('username')
await cache.missing('username')

await cache.pull('username')

await cache.delete('username')
await cache.deleteMany(['products', 'users'])

await cache.clear()
```

## Edge Helper

The `cache` service is available as an edge helper within your views. You can use it to retrieve cached values directly in your templates.

```edge
<p>
Hello {{ await cache.get('username') }}
</p>
```

## Ace Commands

The `@adonisjs/cache` package also provides a set of Ace commands to interact with the cache from the terminal.

### cache:clear

Clears the cache for the specified store. If not specified, it will clear the default one.

```sh
node ace cache:clear
node ace cache:clear redis
node ace cache:clear dynamodb
```
62 changes: 62 additions & 0 deletions content/docs/references/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,3 +308,65 @@ emitter.on('authorization:finished', (event) => {
console.log(event.action)
})
```

## cache:cleared

The event is dispatched by the `@adonisjs/cache` package after the cache has been cleared using the `cache.clear` method.

```ts
import emitter from '@adonisjs/core/services/emitter'

emitter.on('cache:cleared', (event) => {
console.log(event.store)
})
```

## cache:deleted

The event is dispatched by the `@adonisjs/cache` package after a cache key has been deleted using the `cache.delete` method.

```ts
import emitter from '@adonisjs/core/services/emitter'

emitter.on('cache:deleted', (event) => {
console.log(event.key)
})
```

## cache:hit

The event is dispatched by the `@adonisjs/cache` package when a cache key is found in the cache store.

```ts
import emitter from '@adonisjs/core/services/emitter'

emitter.on('cache:hit', (event) => {
console.log(event.key)
console.log(event.value)
})
```

## cache:miss

The event is dispatched by the `@adonisjs/cache` package when a cache key is not found in the cache store.

```ts
import emitter from '@adonisjs/core/services/emitter'

emitter.on('cache:miss', (event) => {
console.log(event.key)
})
```

## cache:written

The event is dispatched by the `@adonisjs/cache` package after a cache key has been written to the cache store.

```ts
import emitter from '@adonisjs/core/services/emitter'

emitter.on('cache:written', (event) => {
console.log(event.key)
console.log(event.value)
})
```