The Node.JS and JavaScript library for Configly: the modern config/static data key/value store that's updatable through a fancy web UI.
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.
- 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.
- 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:
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"} |
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",
}
}
}
In four easy steps!
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.
From https://www.config.ly/config, create a new Config via the "Add" button:
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:
Be sure to save via clicking 'Send to Clients'. Now, we'll write client code to fetch this key.
In a new folder, run:
npm install configly-js
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!
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
.
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 callget()
.
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.
}
})();
Configly works natively with TypeScript:
import Configly from 'configly-js';
Configly.init('api_key');
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.
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.
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 |
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
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);
}
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()
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.
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;
}
}
When get()
encounters a key it could not find, it return the value undefined
.
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—for example like in express.
res.render('forecast', {
weatherInfo: weather,
marketingCopy: copy,
});
});
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.
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 theConfigly.ERRORS
object.message
: text description of the error. Hopefully it's helpful!originalError
: the originating JavaScriptError
object
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
.
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]);
}
}
};
This repository is published under the MIT license.