Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/andreafspeziale/nestjs-memcached
Memcached module for Nest framework (node.js) 🐈
https://github.com/andreafspeziale/nestjs-memcached
cache javascript memcached nestjs nodejs typescript
Last synced: 4 days ago
JSON representation
Memcached module for Nest framework (node.js) 🐈
- Host: GitHub
- URL: https://github.com/andreafspeziale/nestjs-memcached
- Owner: andreafspeziale
- License: mit
- Created: 2022-09-27T20:55:58.000Z (about 2 years ago)
- Default Branch: develop
- Last Pushed: 2024-11-05T14:50:51.000Z (12 days ago)
- Last Synced: 2024-11-05T16:18:49.006Z (12 days ago)
- Topics: cache, javascript, memcached, nestjs, nodejs, typescript
- Language: TypeScript
- Homepage: https://nestjs.com/
- Size: 1.26 MB
- Stars: 17
- Watchers: 3
- Forks: 0
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
Memcached module and service for Nest,
a progressive Node.js framework for building efficient and scalable server-side applications.
## Installation
### npm
```sh
npm install @andreafspeziale/nestjs-memcached
```### yarn
```sh
yarn add @andreafspeziale/nestjs-memcached
```### pnpm
```sh
pnpm add @andreafspeziale/nestjs-memcached
```## How to use?
### Module
The module is Global by default.
#### MemcachedModule.forRoot(options)
`src/core/core.module.ts`
```ts
import { Module } from '@nestjs/common';
import { MemcachedModule } from '@andreafspeziale/nestjs-memcached';@Module({
imports: [
MemcachedModule.forRoot({
connections: [
{
host: 'localhost',
port: '11211',
},
],
ttl: 60,
ttr: 30,
superjson: true,
keyProcessor: (key) => `prefix_${key}`,
wrapperProcessor: ({ value, ttl, ttr }) => ({
content: value,
ttl,
...(ttr ? { ttr } : {}),
createdAt: new Date(),
}),
}),
],
....
})
export class CoreModule {}
```- For signle connection you can omit the `connections` property
- For multiple connections you can omit the `port` property if the server is using the default one
- `ttl` is the global time to live
- `ttr` is the global optional time to refresh
- Typically when caching a JS object like `Date` you will get back a `string` from the cache, [superjson](https://github.com/blitz-js/superjson) will `stringify` on cache `sets` adding metadata in order to later `parse` on cache `gets` and retrieve the initial "raw" data
- `wrapperProcessor` is the global optional wrapper processor function which wraps the value to be cached and adds metadata
- `keyProcessor` is the global optional key processor function which process your cache keys#### MemcachedModule.forRootAsync(options)
`src/core/core.module.ts`
```ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { MemcachedModule } from '@andreafspeziale/nestjs-memcached';
import { Config } from './config';@Module({
imports: [
ConfigModule.forRoot({
....
}),
MemcachedModule.forRootAsync({
useFactory: (cs: ConfigService) => cs.get('memcached'),
inject: [ConfigService],
}),
],
....
})
export class CoreModule {}
```### Decorators
> use the client and create your own service#### InjectMemcachedOptions() and InjectMemcached()
`src/samples/samples.service.ts`
```ts
import { Injectable } from '@nestjs/common';
import {
InjectMemcachedOptions,
InjectMemcached,
MemcachedClient,
MemcachedModuleOptions,
MemcachedClient
} from '@andreafspeziale/nestjs-memcached';@Injectable()
export class SamplesService {
constructor(
@InjectMemcachedOptions()
private readonly memcachedModuleOptions: MemcachedModuleOptions, // Showcase purposes
@InjectMemcached() private readonly memcachedClient: MemcachedClient
) {}....
}
```### Service
> out of the box service with a set of features#### MemcachedService
`src/samples/samples.facade.ts`
```ts
import { MemcachedService } from '@andreafspeziale/nestjs-memcached';
import { SampleReturnType } from './samples.interfaces'@Injectable()
export class SamplesFacade {
constructor(
private readonly memcachedService: MemcachedService,
) {}async sampleMethod(): Promise {
const cachedItem = await this.memcachedService.get('key');if(cachedItem === null) {
....
await this.memcachedService.set('key', 'value');
....
}
}
}
```You can also set all the proper `Processors` and `CachingOptions` inline in order to override the global values specified during the `MemcachedModule` import
```ts
await this.memcachedService.set('key', 'value', { ttl: 100 });
```The provided `MemcachedService` is an opinionated wrapper around [memcached](https://github.com/3rd-Eden/memcached#readme) trying to be unopinionated as much as possibile at the same time.
`setWithMeta` enables `refresh-ahead` cache pattern in order to let you add a logical expiration called `ttr (time to refresh)` to the cached data and more.
So each time you get some cached data it will contain additional properties in order to help you decide whatever business logic needs to be applied.
### Health
I usually expose an `/healthz` controller from my microservices in order to check third parties connection.
#### HealthController
`src/health/health.controller.ts`
```ts
import { Controller, Get } from '@nestjs/common';
import {
HealthCheckService,
HealthCheckResult,
HealthIndicatorResult,
MicroserviceHealthIndicator,
} from '@nestjs/terminus';
import { Transport } from '@nestjs/microservices';
import { Config } from '../config';
import { ConfigService } from '@nestjs/config';@Controller('healthz')
export class HealthController {
constructor(
private readonly health: HealthCheckService,
private readonly microservice: MicroserviceHealthIndicator,
private readonly cs: ConfigService,
) {}@Get()
check(): Promise {
return this.health.check([
(): Promise =>
this.microservice.pingCheck('memcached', {
transport: Transport.TCP,
options: {
host: this.cs.get(
'memcached',
).connections?.[0].host,
port: this.cs.get(
'memcached',
).connections?.[0].port,
},
}),
]);
}
}
```### Validation
As mentioned above I usually init my `DynamicModules` injecting the `ConfigService` exposed by the `ConfigModule` (`@nestjs/config` package). This is where I validate my environment variables using a schema validator of my choice, so far I tried `joi`, `class-validator` and `zod`.
This is an example using `joi` but you should tailor it based on your needs, starting by defining a `Config` interface:
`src/config/config.interfaces.ts`
```ts
import {
BaseWrapper,
MemcachedConfig,
} from '@andreafspeziale/nestjs-memcached';/**
* Cached data shape leveraging metadata feature
* {
* content: T;
* ttl: number;
* ttr?: number;
* version: number;
* created: Date;
* }
*/
export interface CachedMetaConfig {
version: number;
created: Date;
}export type Cached = BaseWrapper & CachedMetaConfig;
export type Config = .... & MemcachedConfig;
````src/config/config.schema.ts`
```ts
import {
MEMCACHED_HOST,
MEMCACHED_PORT,
MEMCACHED_TTL,
MEMCACHED_TTR,
MEMCACHED_VERSION
} from '@andreafspeziale/nestjs-memcached';
import {
MEMCACHED_PREFIX
} from './config.defaults';const BASE_SCHEMA = ....;
const MEMCACHED_SCHEMA = Joi.object({
MEMCACHED_HOST: Joi.string().default(MEMCACHED_HOST),
MEMCACHED_PORT: Joi.number().default(MEMCACHED_PORT),
MEMCACHED_TTL: Joi.number().default(MEMCACHED_TTL),
MEMCACHED_TTR: Joi.number().less(Joi.ref('MEMCACHED_TTL')).default(MEMCACHED_TTR),
MEMCACHED_PREFIX: Joi.string().default(MEMCACHED_PREFIX),
MEMCACHED_VERSION: Joi.number().default(MEMCACHED_VERSION),
});export const envSchema = Joi.object()
.concat(BASE_SCHEMA);
.concat(MEMCACHED_SCHEMA)
````src/config/index.ts`
```ts
import { Config } from './config.interfaces';export * from './config.interfaces';
export * from './config.schema';export default (): Config => ({
....,
memcached: {
connections: [
{
host: process.env.MEMCACHED_HOST,
port: parseInt(process.env.MEMCACHED_PORT, 10),
},
],
ttl: parseInt(process.env.MEMCACHED_TTL, 10),
...(process.env.MEMCACHED_TTR ? { ttr: parseInt(process.env.MEMCACHED_TTR, 10) } : {}),
wrapperProcessor: ({ value, ttl, ttr }) => ({
content: value,
ttl,
...(ttr ? { ttr } : {}),
version: parseInt(process.env.MEMCACHED_VERSION, 10),
created: new Date(),
}),
keyProcessor: (key: string) =>
`${process.env.MEMCACHED_PREFIX}::V${process.env.MEMCACHED_VERSION}::${key}`,
},
});
````src/core/core.module.ts`
```ts
import { ConfigModule, ConfigService } from '@nestjs/config';
import { MemcachedModule } from '@andreafspeziale/nestjs-memcached';
import config, { envSchema, Config } from '../config';@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [config],
validationSchema: envSchema,
}),
MemcachedModule.forRootAsync({
useFactory: (cs: ConfigService) => cs.get('memcached'),
inject: [ConfigService],
}),
....
],
})
export class CoreModule {}
````src/users/users.facade.ts`
```ts
import { MemcachedService } from '@andreafspeziale/nestjs-memcached';
import { Cached } from '../config';
import { User } from './users.interfaces'@Injectable()
export class UsersFacade {
constructor(
private readonly usersService: UsersService,
private readonly memcachedService: MemcachedService
) {}async getUser(id: string): Promise {
const cachedUser = await this.memcachedService.get(
id, { superjson: true }
);/**
* Cached data shape leveraging metadata feature
* {
* content: User;
* ttl: number;
* ttr?: number;
* version: number;
* created: Date;
* }
*/if(cachedItem === null) {
....
await this.memcachedService.setWithMeta>(
id, user, { superjson: true }
);
....return user;
}return cachedUser.content;
}
```## Test
- `docker compose -f docker-compose.test.yml up -d`
- `pnpm test`## Stay in touch
- Author - [Andrea Francesco Speziale](https://twitter.com/andreafspeziale)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)## License
nestjs-memcached [MIT licensed](LICENSE).