@andreafspeziale/nestjs-memcached
v3.6.0
Published
A Memcached module and service for Nest - modern, fast, powerful node.js web framework
Downloads
204
Maintainers
Readme
Installation
npm
npm install @andreafspeziale/nestjs-memcached
yarn
yarn add @andreafspeziale/nestjs-memcached
pnpm
pnpm add @andreafspeziale/nestjs-memcached
How to use?
Module
The module is Global by default.
MemcachedModule.forRoot(options)
src/core/core.module.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 astring
from the cache, superjson willstringify
on cachesets
adding metadata in order to laterparse
on cachegets
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
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: (configService: ConfigService<Config, true>) => configService.get('memcached'),
inject: [ConfigService],
}),
],
...
})
export class CoreModule {}
Decorators
use the client and create your own service
InjectMemcachedOptions() and InjectMemcached()
src/samples/samples.service.ts
import { Injectable } from '@nestjs/common';
import { InjectMemcachedOptions, InjectMemcached, 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
import { MemcachedService } from '@andreafspeziale/nestjs-memcached';
import { SampleReturnType, CachedItemType } from './interfaces'
@Injectable()
export class SamplesFacade {
constructor(
private readonly memcachedService: MemcachedService
) {}
async sampleMethod(): Promise<SampleReturn> {
const cachedItem = await this.memcachedService.get<CachedPlainOrWrappedItem>(cachedItemKey);
if(cachedItem === null) {
...
await this.memcachedService.set<string>('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
await this.memcachedService.set<string>('key', 'value', { ttl: 100 });
The provided MemcachedService
is an opinionated wrapper around memcached 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
import { Controller, Get } from '@nestjs/common';
import {
HealthCheckService,
HealthCheckResult,
HealthIndicatorResult,
MicroserviceHealthIndicator,
} from '@nestjs/terminus';
import { Transport } from '@nestjs/microservices';
import { Config, MemcachedConfig } from '../config';
import { ConfigService } from '@nestjs/config';
@Controller('healthz')
export class HealthController {
constructor(
private readonly health: HealthCheckService,
private readonly microservice: MicroserviceHealthIndicator,
private readonly configService: ConfigService<Config, true>,
) {}
@Get()
check(): Promise<HealthCheckResult> {
return this.health.check([
(): Promise<HealthIndicatorResult> =>
this.microservice.pingCheck('memcached', {
transport: Transport.TCP,
options: {
host: this.configService.get<MemcachedConfig['memcached']>(
'memcached',
).connections?.[0].host,
port: this.configService.get<MemcachedConfig['memcached']>(
'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
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<T = unknown> = BaseWrapper<T> & CachedMetaConfig;
export type Config = ... & MemcachedConfig<unknown, Cached>;
src/config/config.schema.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
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
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: (configService: ConfigService<Config>) => configService.get('memcached'),
inject: [ConfigService],
}),
...
],
})
export class CoreModule {}
src/users/users.facade.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<User> {
const cachedUser = await this.memcachedService.get<Cached<User>(
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<User, Cached<User>>(
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
- Website - https://nestjs.com
- Twitter - @nestframework
License
nestjs-memcached MIT licensed.