RedisClientをDIする方法と呼び出し方を紹介します。
dependency injection containerはTSyringeを使います。
Dependency Injection
import 'reflect-metadata';
import { container } from 'tsyringe';
import { RedisClientType, createClient } from 'redis';
import dotenv from 'dotenv';
dotenv.config();
const redisClient: RedisClientType = createClient({
url: process.env.REDIS_URL!
});
redisClient.on('error', err => { throw err });
(async () => {
await redisClient.connect();
})();
// add singleton
container.registerInstance<RedisClientType>("RedisClient", redisClient);
Resolving Dependencies
import { SchemaFieldTypes, RedisClientType } from 'redis';
import { inject, singleton } from "tsyringe";
@singleton()
export class FreeeUserRepository {
constructor(
@inject("RedisClient") private readonly redisClient: RedisClientType
) {
(async () => {
await this.createIndex();
})();
}
public isReady(): boolean {
return this.redisClient.isReady;
}
public async save(userId: string, user: User): Promise<void> {
const userKey = `freeeuser:${userId}`;
let userJsonString = JSON.stringify(user);
const result = await this.redisClient.set(userKey, userJsonString);
}
public async get(userId: string): Promise<User | null> {
const userKey = `freeeuser:${userId}`;
const userJsonString = await this.redisClient.get(userKey);
if (!userJsonString) {
return null;
}
return JSON.parse(userJsonString) as User;
}
private async createIndex(): Promise<void> {
try {
await this.redisClient.ft.create('idx:freeeusers', {
'$.id': {
type: SchemaFieldTypes.NUMERIC,
SORTABLE: true
},
'$.updated_at': {
type: SchemaFieldTypes.NUMERIC,
SORTABLE: true
},
'$.companies[*].id': {
type: SchemaFieldTypes.NUMERIC
},
'$.companies[*].name': {
type: SchemaFieldTypes.TEXT
},
'$.companies[*].role': {
type: SchemaFieldTypes.TEXT
},
'$.companies[*].external_cid': {
type: SchemaFieldTypes.NUMERIC
},
'$.companies[*].employee_id': {
type: SchemaFieldTypes.NUMERIC
},
'$.companies[*].display_name': {
type: SchemaFieldTypes.TEXT
},
'$.oauth.access_token': {
type: SchemaFieldTypes.TEXT
},
'$.oauth.token_type': {
type: SchemaFieldTypes.TEXT
},
'$.oauth.expires_in': {
type: SchemaFieldTypes.NUMERIC,
SORTABLE: true
},
'$.oauth.refresh_token': {
type: SchemaFieldTypes.TEXT
},
'$.oauth.scope': {
type: SchemaFieldTypes.TEXT
},
'$.oauth.created_at': {
type: SchemaFieldTypes.NUMERIC,
SORTABLE: true
},
'$.oauth.company_id': {
type: SchemaFieldTypes.NUMERIC
}
}, {
ON: 'JSON',
PREFIX: 'freeeuser:'
});
} catch (e: any) {
if (e.message === 'Index already exists') {
//console.log('Index exists already, skipped creation.');
} else {
throw e;
}
}
}
}
export interface OAuth {
access_token: string;
token_type: string;
expires_in: number;
refresh_token: string;
scope: string;
created_at: number;
company_id: number;
}
export interface Company {
id: number;
name: string;
role: string;
external_cid: number;
employee_id?: number | null;
display_name?: string | null;
}
export interface User {
id: number;
companies: Company[];
oauth: OAuth;
updated_at: number;
}
環境情報
// package.json
{
"name": "workday",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "ts-node ./src/app.ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@types/express": "^4.17.20",
"@types/node": "^20.8.9",
"axios": "^1.6.0",
"dotenv": "^16.3.1",
"ejs": "^3.1.9",
"express": "^4.18.2",
"express-validator": "^7.0.1",
"method-override": "^3.0.0",
"polly-js": "^1.8.3",
"redis": "^4.6.10",
"reflect-metadata": "^0.1.13",
"sequelize": "^6.33.0",
"sqlite3": "^5.1.6",
"ts-node": "^10.9.1",
"tsyringe": "^4.8.0",
"typescript": "^5.2.2"
},
"devDependencies": {
"@types/ejs": "^3.1.4",
"@types/method-override": "^0.0.34"
}
}
// tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"noImplicitAny" : true,
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
}
}