express-tenant

Express middleware for multi-tenant configuration and connection management

Usage no npm install needed!

<script type="module">
  import expressTenant from 'https://cdn.skypack.dev/express-tenant';
</script>

README

Express Tenant

Getting started

Looking for the framework agnostic version? Wanna build your own middleware? Try tenant!

Installation

$ npm i --save express-tenant

ES5

var Tenancy = require('express-tenant');

ES6

import { Tenancy, Tenant, Middleware } from 'tenant';

Tenancy configuration options

import Tenancy from 'tenant';
import Bluebird from 'bluebird';

let tenancy = new Tenancy({
  middlewares = {},
  tenantPath = 'tenant',
  requestKey = 'ENV',
  parse = requestParser,
  defaultTenant: process.env.NODE_ENV || 'development',
  tenants: {
    production: convict({}), // use some library
    staging: config, // a custom module
    development: {}, // a plain object!?
  },
  middlewares: {
    auth(config) {
      return (req, res, next) => {
        // Do some tenant specific stuff here
        next();
      };
    }
  },
  connections: {

    // I apologize.
    salesforce(config) {
      let { username, hostname, password, token } = config.salesforce;
      let conn = new jsforce.Connection({
        loginUrl: hostname,
        accessToken: token,
      });

      return Bluebird.fromCallback(cb => {
        conn.login(username, password + token, cb);
      })
    },

    // Less gross.
    couch(config) {
      return nano(config.couch.url);
    },
    // ...other tenanted connections
  ],
});

Functional initialization

Alternatively you can add connections and tenants functionally

Example:

import { Tenancy, Tenant } from 'tenant';

let tenancy = new Tenancy();

let staging = new Tenant('staging', stagingConfig);

tenancy
  .tenant(staging)
  .connection('salesforce', (config) => {
    return Promise.reject(new Error('Really? Still Salesforce?'));
  })
  .middleware('auth', (config) => (req, res, next) => next())
  .middleware('proxy', (config) => (req, res, next) => next())
  .tenant('production', prodConfig);

export default tenancy;

Getting tenant configuration

let secret = tenancy.tenant('production').config.sessionSecret;

Getting a tenant connection

let results = tenancy.tenant('staging').connection('couch')
  .then(CouchDB => {
    let Users = CouchDB.use('users');
    return Users.list();
  });

Using the middleware

import tenancy from './lib/tenant';
import express from 'express';

let app = express();

// Must come before other tenant middlewares
app.use(tenancy.inject());

// Uses tenanted middlewares
app.use(tenancy.middleware('auth'));

// tenant is available on the defined `tenantPath`
app.use((req, res, next) => {
  req.tenant.connection('salesforce')
    .then(SF => {
      SF.sobject('Lead')
        .update({})
        .then(res.send.bind(res))
        .catch(next);
    })
});

API Reference

Tenancy

Methods


constructor(params)

params Object

Example

new Tenancy({

  // Path on the request object that the current tenant will be set.
  tenantPath: 'tenant',

  // Key that the default request parser looks for
  requestKey: 'ENV',

  // Request parser. Determines the tenant for a request
  parse(key, req) {
    if (!key) return;
    return req.get(key)
        || req.get(`X-${key}`)
        || req.query[key]
        || req.query[key.toLowerCase()];
  },

  // Tenant configurations
  tenants: {
    staging: { /* Staging config */ }
  },

  // Tenanted middlewares
  middlewares: {
    auth(config) {
      return (req, res, next) => next();
    }
  },

  // Tenanted connections
  connections: {
    couch: function(config) {
      return Promise.resolve('yay');
    },
  },

  // Default tenant if none is provided
  defaultTenant: process.env.NODE_ENV || 'development',
});

tenant(tenant)

tenant Tenant

Example

tenancy.tenant(new Tenant('staging', {}));

tenant([name])

name String (optional)

Returns a tenant by the name or the default tenant if none is provided

tenant(name, config)

name String

config Object

Example

tenancy.tenant('staging', {});

connection(name, factory)

name String

Key associated with a connection factory.

factory Function

Connection factories are functions with tenant configuration as the last argument. Connection factory function must return a promise, an object, or throw an error.

Example

tenancy.connection('couch', function(config){
  return nano(config.url);
});

middleware(name)

Returns a tenanted middleware by the name.

middleware(name, factory)

name String

Key associated with a middleware factory.

factory Function

Middleware factories are functions with tenant configuration as the only argument. Middleware factory function must return an Express middleware.

Example

tenancy.middleware('auth', function(config){
  return passport.init(config.passport);
});

Tenant

Methods

constructor(name, configuration, connectionsMap)

name String

Key used to retrieve this tenant

configuration Object

Configuration object that gets passed to connection factories.

connectionsMap Object

Key-Value pairs of connections names and factory methods

connection(name)

name String

Returns a promise that will resolve with the tenanted connection

Properties

name

Tenant name

config

Tenant configuration