Skip to content

configly/js

Repository files navigation

Configly JavaScript library

The Node.JS and JavaScript library for Configly: the modern config/static data key/value store that's updatable through a fancy web UI.

npm Coverage Status Try on RunKit GitHub

Table of Contents

What is Configly?

Configly is the place software developers put their static / config data—like copy, styling, and minor configuration values. They can then update that data directly from https://www.config.ly/config without having to wait for a deploy process app store review. Their app or webapp receives the data near instantly. Non-technical folks themselves can publish changes freeing developers to focus on hard software problems and not copy tweaks.

On the backend, Configly provides a read-optimized static-data key/value store built with the aim of being low-latency, and high-availability. The client libraries are made to be dead-simple, lean, and efficient (via enhancements like caching). There is a fancy web UI called the Configulator for setting and updating the configs as well as seeing things like change history.

There are a host of other benefits to using Configly (such as ensuring you do not have data duplicated across clients, reducing load on your primary DB, and providing better tolerance for traffic spikes), read more about the benefits at Configly.

Core Features

  • API to fetch Strings, JSON Blobs (arrays and objects), Booleans, and Numbers from the Configly backend
  • Web interface for modifying these values without having to deploy code (we call our beloved web interface the Configulator).
  • High availability, high-throughput, low-latency backend.
  • Smart caching on the client libraries to minimize server requests.
  • Client libraries available in an expanding amount of languages.

Concepts / Data Model

  • A Configly account contains a set of Configs.
  • A Config is a key-value pair along with associated metadata (like TTL).
  • The keys are strings.
  • The values are one of the following types:

Types

Type notes Example(s)
string "I <3 Configly!"
number Can be integers or decimal; be aware some clients require you to specify which when fetching 31337, 1.618
boolean only true or false true, false
jsonBlob A JSON5 (more relaxed JSON) array or object. ["one", 5, true], {"text": "Buy now!", color: "#0F0"}
More jsonBlob examples

You can make arbitrarily complex JSON structures -- as long as the top level is an object or array. This is incredibly powerful as you can send a host of data with a single config:

A more complex array for a store inventory. Note that because we're using JSON5, quotes are optional for single words.

[
  "Simple T-shirt",
  "Basic hoodie",
  {
    item: "Complex T-shirt",
    sizes: ['S', 'M', 'L'],
    price_us_cents: [1099, 1499, 1599],
  }
]

And a more complex object showing how you can internationalize and set style:

{
  "welcome_message": {
    copy: {
      'en': 'Welcome!',
      'es': "¡Bienvenidos!",
    }, style: {
      color: '#0F0',
      fontWeight: '700',
    }
  },
  "buy_button" : {
    copy: {
      'en': 'Buy',
      'es': "Comprar",
    }, style: {
      backgroundColor: "#F00",
      border: "border-radius 10px",
    }
  }
}

Getting Started

In four easy steps!

1. Get your API Key

You'll need a Configly account. Registration is lightning quick—you can register via visiting https://www.config.ly/signup.

After signing up, you can grab your API Key from https://www.config.ly/config. You'll need your API Key below to integrate the library into your app.

2. Create your first Config

From https://www.config.ly/config, create a new Config via the "Add" button: image

Consider creating a simple JSON Object or Array Config called greetings and give it the value of: ['hello', 'hola', '你好', 'नमस्ते']:

https://www.config.ly/config should look like this:

image

Be sure to save via clicking 'Send to Clients'. Now, we'll write client code to fetch this key.

3. Install the client library

In a new folder, run:

npm install configly-js

4. Fetch the Config

In that same folder, create a JavaScript file with the following content:

const API_KEY = 'YOUR_API_KEY';
const Configly = require('configly-js').Configly;
const configly = Configly.init(API_KEY);

(async () => {
  try {
    const greetings = await configly.get('greetings');
    if (!greetings) {
      console.log("Cannot find key on Configly's server! Wrong API Key?");
      return;
    }
    console.log("To you, Config.ly says:");
    greetings.forEach( (v) => console.log(v) );

  } catch (error) {
    const { status, message, originalError } = error;
    console.log(`Sorry something went wrong: ${status}: ${message}`);
  }
})();

Run that file via node file.js and you should see the payload printed! Try changing some values on https://www.config.ly/config to confirm that the client is getting the updates.

Congratulations you have Configly working end-to-end! Now, feel free to use Configly with all your projects!

For use in browsers

We recommend downloading the SDK via npm and including it on your site for maximum availability.

However, you can also access the library via the following url: https://cdn.jsdelivr.net/npm/configly-js@2.0.4/dist/config.js

If you include this script in your page, you can load a config via code similar to the following:

<html>
<script src="https://cdn.jsdelivr.net/npm/configly-js@2.0.4/dist/config.js"></script>
<script type="text/javascript">
  var configly = Configly.init('Dem0apiKEY');
  configly.get('react-text').then((value) => {
    if (value) {
      document.querySelector('#main').innerHTML = value;
    }
  });
</script>
<body>
  <div id="main">loading..</div>
</body>
</html>

This assumes that you have an element on your page with the id main.

Usage

The golden rule of Configly library use is: do NOT assign the result of a get() to a long-lived variable; in order to check for new values from the server, you must call get().

The package needs to be configured with your account's API key, which is available in the Configly Configulator

// This value is stored on the Config.ly servers.
store_catalog:
 {
   has_sale: true,
   discount: 0.8,
   items: ['T Shirt', 'Hoodie', 'Ferrari'],
   price: [ 100, 250,  200000],
 }

On the Node.JS / JavaScript client:

You can run this code as-is since it uses our demo API Key.

const API_KEY = 'Dem0apiKEY'; // This is our demo API Key.
const Configly = require('configly-js').Configly;
const configly = Configly.init(API_KEY);

(async () => {
  try {
    const params = await configly.get('store_catalog');
    if (!params) {
      console.log("Cannot find store_params on Configly's server! Wrong API Key?");
      return;
    }

    let { has_sale, discount, items, prices } = params;
    if (has_sale) {
      prices =  prices.map((price) => price*discount);
    }

    items.forEach( (_, i) => {
      console.log(`${items[i]}: ${prices[i]} USD`);
    });

  } catch (error) {
    const { status, message, originalError } = error;
    console.log(`Sorry something went wrong: ${status}: ${message}`);
    // You may want to submit error to any error reporting service you use like Sentry
    // originalError shows the error the Configly library caught, if any, and can help you investigate.
  }
})();

With Typescript

Configly works natively with TypeScript:

import Configly from 'configly-js';
Configly.init('api_key');

Using Promises

Configly's get() returns a chainable promise which can be used instead of a regular callback:

You can run this code as-is since it uses our demo code.

const Configly = require('configly-js');
const configly = Configly.init('Dem0apiKEY'); // This uses our demo API Key.

let favoriteSuperhero = 'Batman';
configly.get('the_best_superhero')
  .then((value) => {
    if (value != undefined) {
      favoriteSuperhero = value;
    }
    return configly.get(favoriteSuperhero);
  })
  .then((heroInfo) => {
    console.log("hero stats for " + favoriteSuperhero + ":");
    console.log(heroInfo);
  })
  .catch((error) => {
    const { status, message, originalError } = error;
    console.log(`sorry something went wrong: ${status}: ${message}`);
    // Deal with error
  });

Configly requires support for ES6 Promises. You can polyfill if your stack does not support ES6 promises.

API Reference

Initialization

The library uses the Singleton Pattern; you should not create a new instance via the constructor.

Initialize the Configly library via the init() static method, which returns the global instance:

const Configly = require('configly-js');
const configly = Configly.init('YOUR_API_KEY');

You can initialize the library with several options:

const configly = Configly.init(API_KEY, {
  enableCache: true,
  timeout: 2000,
  host: 'https://api.config.ly',
});

The API_KEY is the only required parameter; you can get it via logging in with your account on the Configly Configulator.

Options

All options are optional. The options object itself can be omitted.

Option Default Description
enableCache true Permits you to disable the cache, resulting in an HTTP fetch on every get() call
timeout 3000 Timeout for requests to Configly's backend in milliseconds (ms).
host 'https://api.config.ly' Host that requests are made to

Errors

The init() method throws an Error if the API key is not supplied or if it is called multiple times. The method does NOT check for validity of the key; that happens on each get() request. See: get() errors

isInitialized()

Return true if init has been successfully called.

If you would like to make your initialization code idempotent, you can write something like this:

if (!Configly.isInitialized()) {
   Configly.init(API_KEY);
 }

getInstance()

To access the global instance, you can call getInstance(). You must call init() first.

// Perhaps this line is some initialization code in a seprate file such as
// 'app.js'
Configly.init(API_KEY);

// And perhaps this line is in a separate file like 'routes.js'
const getIndex = async (req, res) => {
  let tagLine = await Configly.getInstance().get('marketing_tag_line');
  res.render('marketing_splash', { tagLine });
};

get(key, options?)

get() exposes the core function of the library; is to request values stored in Configly.

get() accepts a string as its first argument‐a key. Configly will fetch the corresponding value from the Configly servers (or look it up in the local library cache). get() returns an ES6 Promise, fulfilled with the value. So, the first value passed to get('test_key').then() will be the key's value.

Configly.getInstance().get(key)
  .then((value) => {
    console.log(`${key}'s corresponding value on Configly's server is ${value}.`)
  });

This is an async call; sometimes it will be lightning fast as the value could be cached. Other times, it will make a (fast) server call to fetch the value.

Basic example

In the following example, the Configulator has a JSON string array stored with the key favorite_games

// This value is stored on the Config.ly servers.
product_info: {
  name: "Factorio",
  description: {
    en: "Factorio is a game in which you build and maintain factories",
    es: "Factorio es un videojuego en cual construyes y mantienes fábricas",
    cn: "Factorio是一款视频游戏,您可以在其中建立和维护工厂。"
  }
}

The JavaScript client code:

async getLandingPageData = (user) => {
  // The internet is inherently unreliable.
  // It's not a bad idea to have defaults juuust in case
  const defaultValue = {
    name: "Factorio",
    description: "Literally the best game you've ever played",
  };

  try {
    const productInfo = await Configly.getInstance().get('product_info');

    // This means no value for the key was found. Perhaps the wrong API key was used?
    if (!productInfo) {
      return defaultValue;
    }

    const { name, description } = productInfo;
    const language = user.getLanguage();
    return {
      name: name,
      description: description[language],
    }
  } catch ({ status, message }) {
    console.log(`Error fetching product_info: ${status}: ${message}`);
    return defaultValue;
  }
}

Unknown keys

When get() encounters a key it could not find, it return the value undefined.

Parallel calls

You may want to fetch multiple values. Because get() sometimes makes a server call, in the worst case, this would mean multiple server calls. To be safe, you should execute the calls in parallel. Note that get() returns an ES6 Promise, so you can use Promise.all.

const configly = Configly.getInstance();

Promise.all([ configly.get('NUM_WEATHER_RETRY_ATTEMPTS'), configly.get('WEATHER_MARKETING_COPY') ])
  .then(([numRetries, copy]) => {
    // WeatherService is a made up service that returns an ES6 Promise.
    return Promise.all([ WeatherService.getWeather(numRetries), copy ]);
  })
  .then(([weather, copy]) => {
    // Assumes this method renders an HTML template&mdash;for example like in express.
    res.render('forecast', {
      weatherInfo: weather,
      marketingCopy: copy,
    });
  });

Options

Like the constructor, get() accepts the same set of options that override any global options for that get() call only.

const Configly = require('Configly').Configly;
const configly = Configly.init(API_KEY, {
  timeout: 1000,
  enableCache: false,
};

configly.get('pricing_info', {
  // Because pricing info is so important to our business logic, we're willing
  // to have a longer timeout.
  timeout: 5000,
}).then((pricingInfo) => {
  // This next `get` call will default to the constructor's `timeout` value (1000).
  return configly.get(pricingInfo['currency']);
});

For both calls to configly.get() in this example, the cache is disabled. But only the first call has a timeout of 5000 ms; the second call uses the global setting of 1000 ms.

Errors

Note that when get() encounters a key it could not find, it return the value undefined; this case is not treated as an error.

When there is an error,get() returns a rejected promise fulfilled with an object with the following properties

  • status: the error name You can see the potential values in the Configly.ERRORS object.
  • message: text description of the error. Hopefully it's helpful!
  • originalError: the originating JavaScript Error object
Error statuses

The potential values for the status key of the error returned via get (i.e. get(key).catch(error)) are:

Key Explanation
INVALID_API_KEY Configly's server returned a 401. This likely means the API Key supplied in init() is incorrect. You can see your API Key in the https://config.ly/config.
CONNECTION_ERROR There was a problem communicating with the Config.ly backend. This could be due to a network fault or bad internet connection. Try again later. If the problem persists let us know!
OTHER A miscellaneous error. Take a look at the originalError value inside the returned object. This could indicate a problem with the library; if so, you can create a Github issue and we'll look into it.

These values can be referenced via Configly.ERRORS. e.g. Configly.ERRORS.INVALID_API_KEY.

Example error handling code
Configly.getInstance().get('best_star_wars_movie')
  .then((movie) => {
    doSomethingMagical(movie);
  }).catch((error) => {
    if (error.status == 'CONNECTION_ERROR') {
      // Place retry code here
    } else {
      console.log(error.status, error.message, error.originalError);
    }
  });

or with the alternative asynchronous syntax async/await:

const doMovieMagic = async () => {
  try {
    const movie = await Configly.getInstance().get('best_star_wars_movie');
    doSomethingMagical(movie);
  } catch (error) {
    if (error.status == 'CONNECTION_ERROR') {
      // Place retry code here
    } else {
      console.log([error.status, error.message, error.originalError]);
    }
  }
};

License

This repository is published under the MIT license.