├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── README.md ├── index.js ├── package.json └── test ├── failed_connection.test.js ├── heroku_rediscloud.test.js ├── kill_connection_manually.test.js ├── local_redis.test.js └── local_redis_reuse.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | *.env 30 | dump.rdb 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 4.2.6 4 | services: 5 | - redis-server 6 | before_install: 7 | - pip install --user codecov 8 | after_success: 9 | - codecov 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | _**Please read** our_ [**contribution guide**](https://github.com/dwyl/contributing) (_thank you_!) 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Redis Connection 2 | 3 | A ***Global Redis Connection*** that can be used anywhere in your app 4 | and closed ***once*** at the end of tests. 5 | 6 | [![Build Status](https://travis-ci.org/dwyl/redis-connection.svg)](https://travis-ci.org/dwyl/redis-connection) 7 | [![Code Climate](https://codeclimate.com/github/dwyl/redis-connection/badges/gpa.svg)](https://codeclimate.com/github/dwyl/redis-connection) 8 | [![codecov.io](http://codecov.io/github/dwyl/redis-connection/coverage.svg?branch=master)](http://codecov.io/github/dwyl/redis-connection?branch=master) 9 | [![Dependency Status](https://david-dm.org/dwyl/redis-connection.svg)](https://david-dm.org/dwyl/redis-connection) 10 | [![devDependency Status](https://david-dm.org/dwyl/redis-connection/dev-status.svg)](https://david-dm.org/dwyl/redis-connection#info=devDependencies) 11 | 12 | [![npm](https://img.shields.io/npm/v/redis-connection.svg)](https://www.npmjs.com/package/redis-connection) 13 | 14 | ## Why? 15 | 16 | At ***dwyl*** *we* ***use Redis everywhere*** *because its* ***fast***! 17 | 18 | > If you're *new* to Redis, checkout our *beginners tutorial*: 19 | https://github.com/dwyl/learn-redis 20 | 21 | Given that Redis can handle ***millions of operations per second***, 22 | it is *unlikely* to be the *bottleneck* in your application/stack. 23 | 24 | Where you *can* (*unintentionally*) *create* an issue is by having 25 | ***too many*** **connections** to your Redis Datastore. 26 | *Don't laugh*, we've seen this happen, 27 | where people open a ***new connection*** to Redis 28 | for ***each*** **incoming http request** 29 | (*and forget to close them!*) and thus quickly run out 30 | of available connections to Redis! 31 | 32 | Most apps *really* only need ***one*** connection to Redis (*per node.js instance*) 33 | Or, if you are using Redis' **Publish/Subscribe** feature, you will need ***two*** connections per node.js server; one for a "*standard*" connection (*the* "***Publisher***"") and another as a "***Subscriber***". 34 | 35 | 36 | Given that we *modularise* our apps and we 37 | don't want each *file* opening multiple connections to the Redis datastore 38 | (*because* ***Redis connections*** *are a* ***scarce resource*** - e.g: [RedisCloud](https://addons.heroku.com/rediscloud) is *30 connections* - *and 39 | each connection needs to be closed for tape tests to exit*...) 40 | we decided write a *little* script to *instantiate* a *single* connection 41 | to Redis which can be re-used across multiple files. 42 | 43 | 44 | ## What? 45 | 46 | An ***easy*** way to re-use your ***single*** Redis connection 47 | (*or* ***pair*** of connections - when using Redis Publish/Subscribe) 48 | across multiple files/handlers in your application 49 | and *close once* at the end of your tests. 50 | 51 | 52 | ## *How*? 53 | 54 | ### Install from NPM 55 | 56 | ```sh 57 | npm install redis-connection --save 58 | ``` 59 | 60 | ### Use in your code 61 | 62 | ```js 63 | var redisClient = require('redis-connection')(); // require & connect 64 | redisClient.set('hello', 'world'); 65 | redisClient.get('hello', function (err, reply) { 66 | console.log('hello', reply.toString()); // hello world 67 | }); 68 | ``` 69 | 70 | ### Using Redis Publish / Subscriber ? 71 | 72 | You can use the *standard* `redisClient` for *publishing* but 73 | will need to have a *separate* connection to subscribe on. 74 | 75 | Create a *Subscriber* connection by supplying the word subscriber 76 | when starting the `redis-connection`: 77 | 78 | ```js 79 | var redisSub = require('redis-connection')('subscriber'); 80 | redisSub.subscribe("chat:messages:latest", "chat:people:new"); 81 | // see: https://github.com/dwyl/hapi-socketio-redis-chat-example ;-) 82 | ``` 83 | 84 | ### Closing your Connection(s) 85 | 86 | Closing your connections is easy. 87 | 88 | ```js 89 | var redisClient = require('redis-connection')(); // require & connect 90 | redisClient.set('hello', 'world'); 91 | redisClient.get('hello', function (err, reply) { 92 | console.log('hello', reply.toString()); // hello world 93 | redisClient.end(true); // this will "flush" any outstanding requests to redis 94 | }); 95 | ``` 96 | 97 | If you have created *multiple* connections in your app 98 | (*you would do this to use Redis' Publish/Subscribe feature*), 99 | we have a simple method to "*Close All*" the connections 100 | you have opened in a single command: `killall()` 101 | 102 | e.g: 103 | 104 | ```js 105 | var redisClient = require('redis-connection')(); // require & connect 106 | var redisSub = require('redis-connection')('subscriber'); 107 | 108 | // do things with redisClient and redisSub in your app... 109 | // when you want to close both connections simply call: 110 | require('redis-connection').killall(); 111 | ``` 112 | 113 | ### Using `redis-connection` with `env2` 114 | 115 | If you are using [**env2**](https://github.com/dwyl/env2) to load your configuration file, simply require `env2` before requiring `redis-connection`: 116 | 117 | ```js 118 | require('env2')('.env'); // load the redis URL 119 | var redisClient = require('redis-connection')(); 120 | // now use your redis connection 121 | ``` 122 | **Make sure you have defined an environment variable named "REDIS_URL" or "REDISCLOUD_URL"** 123 | For example: 124 | ``` 125 | REDIS_URL=redis://127.0.0.1:6379/0 126 | ``` 127 | If the `REDIS_URL` (or `REDISCLOUD_URL`) environment variable is not defined `redis-connection` will use the Redis localhost url (127.0.0.1) on the port 6379 128 | 129 | ## Need More? 130 | 131 | If you need us to support a different Redis-as-a-service provider 132 | or want to have more configuration options, please let us know! 133 | [![Join the chat at https://gitter.im/dwyl/chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dwyl/chat/?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 134 | 135 | ## *Contributors* 136 | 137 | As with all @dwyl projects 138 | [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/dwyl/redis-connection/issues) 139 | 140 | ### Environment Variables 141 | 142 | If you want to help improve/update/extend this module, 143 | please ask us for access to the ***environment variables*** 144 | (`.env` file) with `REDISCLOUD_URL` so you can test your modifications *locally*. 145 | 146 | 147 | ### Failed Connection ? 148 | 149 | If you are seeing a "_Redis Connection Error_" message in your terminal, e.g: 150 | 151 | ``` 152 | - - - - - - - - Redis Connection Error: - - - - - - - - 153 | { Error: 'Redis connection to 127.0.0.1:6380 failed', 154 | code: 'ECONNREFUSED', 155 | errno: 'ECONNREFUSED', 156 | syscall: 'connect', 157 | address: '127.0.0.1', 158 | port: 4321 } 159 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - 160 | ``` 161 | 162 | Either your local instance of Redis is not running or is running on a 163 | different port from the standard which is **6379**. 164 | Confirm Redis is running then try again! 165 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var redis = require('redis'); 2 | var url = require('url'); 3 | 4 | var rc; // redis config 5 | if (process.env.REDIS_URL || process.env.REDISCLOUD_URL) { 6 | var redisURL = url.parse(process.env.REDIS_URL || process.env.REDISCLOUD_URL); 7 | rc = { 8 | port: redisURL.port, 9 | host: redisURL.hostname, 10 | auth: redisURL.auth.split(":")[1] 11 | } 12 | } 13 | else { 14 | rc = { 15 | port: 6379, 16 | host: '127.0.0.1' 17 | // auth: '' no auth on localhost see: https://git.io/vH3TN 18 | } 19 | } 20 | 21 | var CON = {}; // store redis connections as Object 22 | 23 | function new_connection () { 24 | var redis_con = redis.createClient(rc.port, rc.host); 25 | if (process.env.REDISCLOUD_URL && rc.auth) { // only auth on CI/Stage/Prod 26 | redis_con.auth(rc.auth); // see: https://git.io/vH3TN 27 | } 28 | return redis_con; 29 | } 30 | 31 | function redis_connection (type) { 32 | type = type || 'DEFAULT'; // allow infinite types of connections 33 | 34 | if (!CON[type] || !CON[type].connected) { 35 | CON[type] = new_connection(); 36 | } 37 | return CON[type]; 38 | } 39 | 40 | module.exports = redis_connection; 41 | 42 | module.exports.kill = function(type) { 43 | type = type || 'DEFAULT'; // kill specific connection or default one 44 | CON[type].end(true); 45 | delete CON[type]; 46 | } 47 | 48 | module.exports.killall = function() { 49 | var keys = Object.keys(CON); 50 | keys.forEach(function(k){ 51 | CON[k].end(true); 52 | delete CON[k]; 53 | }) 54 | } 55 | 56 | /** 57 | * In the event of a failed connection we don't want our Node.js App to "Die"! 58 | * rather we want to report that the connection failed but then keep running... 59 | * see: github.com/dwyl/redis-connection/issues/38 60 | * @param {Object} err - the error Object thrown by Redis (standard node error) 61 | * @returns {Object} err - unmodified error 62 | */ 63 | var reported; // bit 64 | function report_error (err) { 65 | if (!reported && err.syscall === 'connect' && err.code === 'ECONNREFUSED') { 66 | reported = true; // only report the error once. 67 | console.log('- - - - - - - - Redis Connection Error: - - - - - - - - ') 68 | console.error(err); 69 | console.log('- - - - - - - - - - - - - - - - - - - - - - - - - - - - ') 70 | } 71 | return err; 72 | } 73 | 74 | process.on('uncaughtException', report_error); 75 | 76 | module.exports.report_error = report_error; 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redis-connection", 3 | "version": "5.4.1", 4 | "description": "Re-use a single or pool of redis connections across several modules/files in your app.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "./node_modules/.bin/istanbul cover ./node_modules/tape/bin/tape ./test/*.test.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/dwyl/redis-connection.git" 12 | }, 13 | "keywords": [ 14 | "redis", 15 | "connection", 16 | "single", 17 | "multiple", 18 | "re-useable", 19 | "global" 20 | ], 21 | "author": "@nelsonic & friends @dwylhq", 22 | "license": "GPL-2.0", 23 | "bugs": { 24 | "url": "https://github.com/dwyl/redis-connection/issues" 25 | }, 26 | "homepage": "https://github.com/dwyl/redis-connection#readme", 27 | "dependencies": { 28 | "redis": "^2.8.0" 29 | }, 30 | "devDependencies": { 31 | "decache": "^4.1.0", 32 | "env2": "^2.2.0", 33 | "istanbul": "^0.4.5", 34 | "tape": "^4.8.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/failed_connection.test.js: -------------------------------------------------------------------------------- 1 | // See: github.com/dwyl/redis-connection/issues/38 2 | var dir = __dirname.split('/')[__dirname.split('/').length-1]; 3 | var file = dir + __filename.replace(__dirname, '') + " > "; 4 | var test = require('tape'); 5 | var decache = require('decache'); // http://goo.gl/JIjK9Y 6 | require('env2')('.env'); // ensure environment variables are loaded 7 | var REDISCLOUD_URL = process.env.REDISCLOUD_URL; // save valid REDISCLOUD_URL 8 | 9 | test(file + 'No (fatal) error is thrown when connection fails', function (t) { 10 | process.env.REDISCLOUD_URL = 'redis://rediscloud:@127.0.0.1:6380'; // bad port 11 | var redisClient = require('../index.js')(); 12 | 13 | t.true(redisClient.connected === false, 14 | '✓ redisClient.connected: ' + redisClient.connected); 15 | 16 | redisClient.end(true); // close the (failed) connection 17 | t.end(); 18 | }); 19 | 20 | test(file + 'functional test of report_error()', function (t) { 21 | var report_error = require('../index.js').report_error; 22 | var error = { 23 | Error: "Redis connection to 127.0.0.1:6380 failed", 24 | code: 'ECONNREFUSED', 25 | errno: 'ECONNREFUSED', 26 | syscall: 'connect', 27 | address: '127.0.0.1', 28 | port: 6380 29 | } 30 | t.true(error === report_error(error), '✓ Error reported: ' + error.code); 31 | 32 | var error2 = { syscall: false }; // don't report for non-connection errors 33 | t.true(error2 === report_error(error2), '✓ Error reported: none'); 34 | t.end(); 35 | }); 36 | 37 | test('restore process.env.REDISCLOUD_URL', function (t) { 38 | decache('../index.js'); 39 | process.env.REDISCLOUD_URL = REDISCLOUD_URL; // restore valid 40 | t.ok(process.env.REDISCLOUD_URL 41 | .indexOf('redis://rediscloud:@127.0.0.1:6380') === -1, '✓ URL Restored') 42 | t.end() 43 | }) 44 | -------------------------------------------------------------------------------- /test/heroku_rediscloud.test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var dir = __dirname.split('/')[__dirname.split('/').length-1]; 3 | var file = dir + __filename.replace(__dirname, '') + " -> "; 4 | 5 | test(file + " Confirm RedisCloud is accessible GET/SET", function(t) { 6 | require('decache')('../index.js'); 7 | require('env2')('.env'); 8 | console.log(' - - - - - - - - - - - - - process.env:'); 9 | // console.log(process.env); 10 | var redisClient = require('../index.js')(); 11 | t.ok(redisClient.address !== '127.0.0.1:6379', 12 | "✓ Redis Client connected to: " + redisClient.address); 13 | 14 | redisClient.set('redis', 'working', function(err, res) { 15 | console.log("✓ Redis Client connected to: " + redisClient.address); 16 | redisClient.get('redis', function (err, reply) { 17 | t.equal(reply.toString(), 'working', '✓ RedisCLOUD is ' + reply.toString()); 18 | redisClient.end(true); // ensure redis con closed! - \\ 19 | t.equal(redisClient.connected, false, "✓ Connection to RedisCloud Closed"); 20 | delete process.env.REDISCLOUD_URL; 21 | require('decache')('../index.js'); 22 | t.end(); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /test/kill_connection_manually.test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var dir = __dirname.split('/')[__dirname.split('/').length-1]; 3 | var file = dir + __filename.replace(__dirname, '') + " -> "; 4 | var decache = require('decache'); // http://goo.gl/JIjK9Y 5 | 6 | test(file +"Kill a Redis Connection", function(t) { 7 | var redisClient = require('../index.js')(); // connect 8 | t.equal(redisClient.address, '127.0.0.1:6379', 9 | "✓ Redis Client connected to: " + redisClient.address) 10 | redisClient.set('redis', 'FAST!', function(err, reply){ 11 | redisClient.get('redis', function (err, reply) { 12 | t.equal(reply.toString(), 'FAST!', '✓ LOCAL Redis is ' +reply.toString()); 13 | require('../index.js').kill(); 14 | decache('../index.js'); 15 | console.log('redisClient.connected: ', redisClient.connected); 16 | t.equal(redisClient.connected, false, "✓ Connection to Local Redis Killed!"); 17 | t.end(); 18 | }); 19 | }); 20 | }); 21 | 22 | test(file +" Connect to LOCAL Redis instance Which was CLOSED in Previous Test", function(t) { 23 | delete process.env.REDISCLOUD_URL; // delete to ensure we use LOCAL Redis! 24 | redisClient = require('../index.js')(); // re-connect 25 | redisSub = require('../index.js')('SUBSCRIBER'); 26 | t.equal(redisClient.address, '127.0.0.1:6379', 27 | "✓ Redis Client RE-connected to: " + redisClient.address) 28 | redisClient.set('redis', 'RE-CONNECTED', function(err, reply){ 29 | redisClient.get('redis', function (err, reply) { 30 | t.equal(reply.toString(), 'RE-CONNECTED', '✓ LOCAL Redis is ' +reply.toString()); 31 | require('../index.js').killall(); 32 | decache('../index.js'); 33 | t.end(); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/local_redis.test.js: -------------------------------------------------------------------------------- 1 | // require('env2')('config.env'); 2 | // var REDISCLOUD_URL = process.env.REDISCLOUD_URL; 3 | // delete process.env.REDISCLOUD_URL; // delete to ensure we use LOCAL Redis! 4 | 5 | var test = require('tape'); 6 | var decache = require('decache'); // http://goo.gl/JIjK9Y 7 | 8 | var dir = __dirname.split('/')[__dirname.split('/').length-1]; 9 | var file = dir + __filename.replace(__dirname, '') + " -> "; 10 | 11 | test(file +" Connect to LOCAL Redis instance as Subscriber", function(t) { 12 | var redisSub = require('../index.js')('subscriber'); 13 | t.equal(redisSub.address, '127.0.0.1:6379', 14 | "✓ Redis Client connected to: " + redisSub.address) 15 | redisSub.set('redis', 'SUBSCRIBER', function(err, reply){ 16 | redisSub.get('redis', function (err, reply) { 17 | t.equal(reply.toString(), 'SUBSCRIBER', '✓ LOCAL Redis is ' +reply.toString()); 18 | t.end(); 19 | }); 20 | }); 21 | }); 22 | 23 | test(file +" Connect to LOCAL Redis instance and GET/SET", function(t) { 24 | var redisClient = require('../index.js')(); 25 | t.equal(redisClient.address, '127.0.0.1:6379', 26 | "✓ Redis Client connected to: " + redisClient.address) 27 | redisClient.set('redis', 'LOCAL', function(err, reply){ 28 | redisClient.get('redis', function (err, reply) { 29 | t.equal(reply.toString(), 'LOCAL', '✓ LOCAL Redis is ' +reply.toString()); 30 | t.end(); 31 | }); 32 | }); 33 | }); 34 | 35 | test('Require an existing Redis connection', function(t){ 36 | var r2 = require('../index.js')(); 37 | r2.get('redis', function(err, reply){ 38 | t.equal(reply.toString(), 'LOCAL', '✓ LOCAL Redis is ' +reply.toString()); 39 | t.end(); 40 | }); 41 | }); 42 | 43 | test('Require an existing Redis SUBSCRIBER connectiong', function(t){ 44 | var rs2 = require('../index.js')('subscriber'); 45 | rs2.get('redis', function(err, reply){ 46 | t.equal(reply.toString(), 'LOCAL', '✓ LOCAL Redis is ' +reply.toString()); 47 | t.end(); 48 | }); 49 | }); 50 | 51 | test('Close Conection & Reset for Heroku Compatibility tests', function(t){ 52 | require('../index.js').killall(); // close all connections 53 | decache('../index.js'); 54 | t.equal(redisClient.connected, false, "✓ Connection to LOCAL Closed"); 55 | t.end(); 56 | }); 57 | -------------------------------------------------------------------------------- /test/local_redis_reuse.test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var dir = __dirname.split('/')[__dirname.split('/').length-1]; 3 | var file = dir + __filename.replace(__dirname, '') + " -> "; 4 | var decache = require('decache'); // http://goo.gl/JIjK9Y 5 | decache('../index.js'); 6 | 7 | test(file +" Connect to LOCAL Redis instance CLOSED in Previous Test", function(t) { 8 | var redisClient = require('../index.js')(); // connect 9 | t.equal(redisClient.address, '127.0.0.1:6379', 10 | "✓ Redis Client connected to: " + redisClient.address) 11 | redisClient.set('redis', 'LOCAL', function(err,reply){ 12 | redisClient.get('redis', function (err, reply) { 13 | t.equal(reply.toString(), 'LOCAL', '✓ LOCAL Redis is ' +reply.toString()); 14 | redisClient.end(true); // ensure redis con closed! - \\ 15 | t.equal(redisClient.connected, false, "✓ Connection to Redis Closed"); 16 | t.end(); 17 | }); 18 | }); 19 | }); 20 | 21 | test(file +" Connect to LOCAL Redis instance Which was CLOSED in Previous Test", function(t) { 22 | delete process.env.REDISCLOUD_URL; // delete to ensure we use LOCAL Redis! 23 | var redisClient = require('../index.js')(); // re-connect 24 | t.equal(redisClient.address, '127.0.0.1:6379', 25 | "✓ Redis Client RE-connected to: " + redisClient.address) 26 | redisClient.set('redis', 'RE-CONNECTED', function(err, reply){ 27 | redisClient.get('redis', function (err, reply) { 28 | t.equal(reply.toString(), 'RE-CONNECTED', '✓ LOCAL Redis is ' +reply.toString()); 29 | redisClient.end(true); // ensure redis con closed! - \\ 30 | decache('../index.js'); 31 | t.end(); 32 | }); 33 | }); 34 | }); 35 | --------------------------------------------------------------------------------