CLOVER🍀

That was when it all began.

Node.jsからRedisにアクセスしてみる

これは、なにをしたくて書いたもの?

  • Node.jsを使ったプログラミングの練習がてら、データストアにアクセスでも
  • Redisあたりにアクセスしてみて、試してみようかと
  • Promise使おう

A Node.js for Redis Client

Node.jsからRedisにアクセスするには、こちらのライブラリを使うようです。

Node Redis

GitHub - NodeRedis/node_redis: redis client for node

このライブラリは、コールバック形式が標準のようですが、Promiseを使いたければ別のライブラリの手を借りましょうと。

ドキュメントにも記載のあるとおり、今回はBluebirdを使うことにします。

Bluebird Promises

Getting Started | bluebird

環境

今回の環境は、こちら。

$ node -v
v8.11.4


$ npm -v
5.6.0

Redisは、4.0.11を使います。リモートアクセスにするので、パスワード指定でアクセスする感じで。

準備

ライブラリのインストール。

$ npm i --save redis bluebird
  "dependencies": {
    "bluebird": "^3.5.2",
    "redis": "^2.8.0"
  }

動作確認は、テストコードで行うことにしましょう。

Jestをインストール。

$ npm i --save-dev jest
  "devDependencies": {
    "jest": "^23.5.0"
  },
  "jest": {
    "testEnvironment": "node"
  }

scriptの指定は、こんな感じで。

  "scripts": {
    "test": "jest"
  },

テストコードの雛形

テストコードの雛形は、こんな感じで。 test/redis.test.js

const Promise = require('bluebird');
const redis = Promise.promisifyAll(require('redis'));

// ここに、テストを書く!!

Node.jsのRedisクライアントに対して、Bluebirdでラップします。

set/get

まずは単純に、set/getから。

test('simple put and get', async () => {
    const client = await redis.createClient(6379, '172.17.0.2', {
        password: 'redispass'
    });

    await client.setAsync('key1', 'value1');
    expect(await client.getAsync('key1')).toEqual('value1');

    await client.delAsync('key1');
    expect(await client.getAsync('key1')).toBeNull();

    await client.setAsync('key2', 'value2');
    expect(await client.getAsync('key2')).toEqual('value2');

    await client.delAsync('key2', redis.print);
    expect(await client.getAsync('key2')).toBeNull();
    
    await client.quitAsync();
});

Redisへの接続は、こんな感じで。

    const client = await redis.createClient(6379, '172.17.0.2', {
        password: 'redispass'
    });

Redis#createClientのオプションについては、こちらを参照しましょう。

redis.createClient

Redisの各コマンドは、Clientのfunctionとして表現されます。

Sending Commands

で、今回はBluebirdを使ってPromiseとして扱っているので、こういう感じです、と。

    await client.setAsync('key1', 'value1');
    expect(await client.getAsync('key1')).toEqual('value1');

    await client.delAsync('key1');
    expect(await client.getAsync('key1')).toBeNull();

切断は、Client#quitです。

    await client.quitAsync();

client.quit(callback)

hmset/hgetall

ハッシュ的な。

test('hmset and get', async() => {
    const client = await redis.createClient(6379, '172.17.0.2', {
        password: 'redispass'
    });

    await client.hmsetAsync('hkey1', { member1: 'value1-1', member2: 'value1-2' });
    expect(await client.hgetallAsync('hkey1')).toEqual({ member1: 'value1-1', member2: 'value1-2' });

    await client.delAsync('hkey1');
    expect(await client.hgetallAsync('hkey1')).toBeNull();

    await client.quitAsync();
});

オブジェクトを扱えるので、楽ですね。

Friendlier hash commands

Batch(Pipeline)

Pipelineはどうするのかな?と思っていたら、client#batchでやるみたいです。

test('use batch as pipeline', async () => {
    const client = await redis.createClient(6379, '172.17.0.2', {
        password: 'redispass'
    });

    await client
        .batch()
        .set( 'key1', 'value1')
        .hmset('hkey1', { member1: 'value1-1', member2: 'value1-2' })
        .execAsync();

    const response = await client
          .batch()
          .get('key1')
          .hgetall('hkey1')
          .execAsync();

    expect(response[0]).toEqual('value1');
    expect(response[1]).toEqual({ member1: 'value1-1', member2: 'value1-2' });

    await client
        .batch()
        .del('key1')
        .del('hkey1')
        .execAsync();

    const deletedResponse = await client
          .batch()
          .get('key1')
          .hgetall('hkey1')
          .execAsync();

    expect(deletedResponse[0]).toBeNull();
    expect(deletedResponse[1]).toBeNull();

    await client.quitAsync();
});

ただ、説明がほとんどありませんが…。

client.batch([commands])

client#batchのあとに、各種コマンドを続けていき、最後にexecすればよい、と。

    await client
        .batch()
        .set( 'key1', 'value1')
        .hmset('hkey1', { member1: 'value1-1', member2: 'value1-2' })
        .execAsync();

multi

最後、multiです。

client.multi([commands])

batchとほとんど一緒。

test('multi', async() => {
    const client = await redis.createClient(6379, '172.17.0.2', {
        password: 'redispass'
    });

    await client
        .multi()
        .set( 'key1', 'value1')
        .hmset('hkey1', { member1: 'value1-1', member2: 'value1-2' })
        .execAsync();

    const response = await client
          .multi()
          .get('key1')
          .hgetall('hkey1')
          .execAsync();

    expect(response[0]).toEqual('value1');
    expect(response[1]).toEqual({ member1: 'value1-1', member2: 'value1-2' });

    await client
        .multi()
        .del('key1')
        .del('hkey1')
        .execAsync();

    const deletedResponse = await client
          .multi()
          .get('key1')
          .hgetall('hkey1')
          .execAsync();

    expect(deletedResponse[0]).toBeNull();
    expect(deletedResponse[1]).toBeNull();

    client
        .multi()
        .set( 'key2', 'value2')
        .hmset('hkey2', { member1: 'value2-1', member2: 'value2-2' })
        .discard();  // no async

    const discardedResponse = await client
          .multi()
          .get('key2')
          .hgetall('hkey2')
          .execAsync();

    expect(discardedResponse[0]).toBeNull();
    expect(discardedResponse[1]).toBeNull();

    await client.quitAsync();
});

違いは、Client#multiで始めることくらいですね。

    await client
        .multi()
        .set( 'key1', 'value1')
        .hmset('hkey1', { member1: 'value1-1', member2: 'value1-2' })
        .execAsync();

discardもできるのですが、batchも同じことができそうな様子なのですが…。

    client
        .multi()
        .set( 'key2', 'value2')
        .hmset('hkey2', { member1: 'value2-1', member2: 'value2-2' })
        .discard();  // no async

とりあえず、基礎的なことや試せたのではないかなぁと思います。