README
plano
A simple single-process REST server for LevelDB.
What is Plano?
Plano is a simple single-process REST server to provide LevelDB as a network service. It performs like a Redis key/value store, except that it also allows key range queries whereby all keys and values between a start-key and end-key may be retrieved. LevelDB stores data on-disk for greater crash resilience than an in-memory Redis server. By default, LevelDB data is compressed too. (Note that Redis scales to multiple servers, whereas Plano runs on only one.)
Why call it "plano"?
"Plano" is Spanish for flat or level. Simple really?
Why use LevelDB?
LevelDB is more than just a key/value store. It's impressively fast, and it offers range queries to retrieve keys and values within bounds. This enables LevelDB to solve problems that might typically require a relational database, or MongoDB. LevelDB also offers data compression by default, unlike MongoDB.
Whereas servers like Redis offer the concept of TTL (expiring data), Plano
offers deletion over key ranges. Data tables that are keyed on epoch times
are easily purged of old data by using the delRange
API method (below).
Installing LevelDB
To use Plano, you'll first need to install LevelDB. On a Mac, install LevelDB with Homebrew (see http://brew.sh/) like this:
brew install leveldb
On Ubuntu Linux, install LevelDB with:
apt-get install libleveldb-dev
On Windows, heck I have no idea.
Use cases for Plano
Generally it's a Very Bad Idea to make Plano directly available to web/app front-ends because the API doesn't (yet) support authentication/authorization. Instead, Plano is ideally suited to the kinds of roles where you might use MongoDB, Redis or Riak as part of a secured NodeJS back-end server system. It's a great tool for prototyping projects where you'd like to avoid writing complex database schemas and migrations.
How to run the server
> npm install plano
> $(npm bin)/plano --addr=127.0.0.1 --port=9876 --path=./data &
Default command line options
- The default
addr
is0.0.0.0
- The default
port
is9999
- The default
path
is./db
(a folder to store LevelDB data files)
Native JavaScript API (see below for a raw HTTP API)
Start a Plano server from inside your JavaScript code like this:
var Plano = require('plano'),
plano = new Plano({addr: '0.0.0.0', port: 9999, path: './db'});
plano.start(function(){
// We're running!
});
Once your Plano server is running, you can make API calls to it from multiple servers. The only restrictions are:
- Just one Plano server process may run at a time on each address and port.
- The same databases cannot be served by more than one Plano server process.
All Plano API methods return Promise
objects. See https://www.promisejs.org/
for details if you're not familiar with them.
Database names must be plain words, without any special characters except "_". Typically you will want to use what would usually be a table name, such as "users", "customers" or "orders".
Keys must be plain strings. If you need to use a different datatype
(e.g. a Date
) or a more complex object, then use JSON.stringify(key)
to encode it as a string first.
Values may be plain strings, booleans, numbers, Date
objects or more complex
objects like arrays or hashes. The API attempts to handle all data encoding and
decoding transparently and efficiently.
Put data using the API
plano.API.put("myDatabaseName", "myKey1", "myValue1").then(function(body){
// Our first key/value pair is now stored (a simple string)
body.params.key === "myKey1";
return plano.API.put("myDatabaseName", "myKey2", 2);
}).then(function(body){
// Our second key/value pair is now stored (a simple number)
body.params.key === "myKey2";
return plano.API.put("myDatabaseName", "myKey3", {num: 3, odd: true});
}).then(function(body){
// Our third key/value pair is now stored (a complex object)
body.params.key === "myKey3";
});
Put bulk updates using the API
plano.API.putAll("myDatabaseName", {
"myKey1": "myValue1",
"myKey2": 2,
"myKey3": {num: 3, odd: true}
}).then(function(body){
// Our first key/value pair is now stored (a simple string)
// Our second key/value pair is now stored (a simple number)
// Our third key/value pair is now stored (a complex object)
body.params.keys === ["myKey1, "myKey2", "myKey3"];
});
Get data using the API
plano.API.get("myDatabaseName", "myKey1").then(function(body){
// Our key/value pair is retrieved
body.data.myKey1 === "myValue1";
body.data.myKey2 === undefined; // we didn't request it
body.data.myKey3 === undefined; // we didn't request it
});
Get all data in a table using the API
plano.API.getAll("myDatabaseName").then(function(body){
// All key/value pairs are retrieved in "body.data"
body.data.myKey1 === "myValue1";
body.data.myKey2 === 2;
body.data.myKey3.num === 3;
body.data.myKey3.odd === true;
});
Get all data in a table using the API (with a "greater than" option)
plano.API.getAll("myDatabaseName", {gt: "myKey1"}).then(function(body){
// All key/value pairs are retrieved in "body.data"
body.data.myKey1 === undefined; // it's not greater than "myKey1"
body.data.myKey2 === 2;
body.data.myKey3.num === 3;
body.data.myKey3.odd === true;
});
Get a range of data using the API (the range is inclusive)
plano.API.getRange("myDatabaseName", "myKey0", "myKey1").then(function(body){
// All key/value pairs are retrieved in "body.data"
body.data.myKey1 === "myValue1";
body.data.myKey2 === undefined; // it's greater than "myKey1"
body.data.myKey3 === undefined; // it's greater than "myKey1"
});
Delete data using the API
plano.API.del("myDatabaseName", "myKey1").then(function(body){
// Our first key/value pair is now deleted
body.deleted === "myKey1";
});
Delete a range of data using the API (the range is inclusive)
plano.API.delRange("myDatabaseName", "myKey0", "myKey3").then(function(body){
// Our second and third key/value pairs are now deleted
body.deleted === ["myKey2", "myKey3"]; // "myKey0" does not exist...
}); // ...and "myKey1" was deleted
HTTP API
The HTTP API returns JSON objects. Each response includes a server timestamp
called time
and a server processing duration in milliseconds called msecs
.
Params passed to the server are returned in the response as params
, any data
in data
and a list of deleted keys in deleted
.
http://addr:port/db/:db/:key
POST or PUT The body of the POST or PUT request is the value to be stored in the database. By default, values are plain strings and should be sent as "text/plain" content. When sending a JSON value, be sure to use a content type of "application/json". JSON objects must have a JSON root of "data" and must be key/value objects, not arrays, booleans, dates or numbers.
If you want to PUT a JSON object, then you're probably better off using the API (see above) because it handles the marshalling of various datatypes, including arrays, booleans, dates and numbers.
Params
:db
- your database name (it'll be created if it doesn't exist):key
- the key whose value is to be stored in the database (in the body of the request)?callback
- an optional JavaScript callback function for JSONP requests
Example (plain text value):
curl -X PUT --data "myStoredValue" http://localhost:9999/db/myDatabaseName/myKey
Response:
{"params":{"db":"myDatabaseName","key":"myKey"},"time":1453889946843,"msecs":20}
Example (JSON value):
curl -X PUT -H 'content-type: application/json' --data '{"data":{"ok":true}}' http://localhost:9999/db/myDatabaseName/status
Response:
{"params":{"db":"myDatabaseName","key":"status"},"time":1453972791640,"msecs":20}
http://addr:port/db/:db
(for batch updates)
POST or PUT The body of the POST or PUT request is a map of keys and values to be stored in the database, encoded as JSON with a root of "data" (be sure to use a content type of "application/json").
Params
:db
- your database name (it'll be created if it doesn't exist)?callback
- an optional JavaScript callback function for JSONP requests
Example:
curl -X PUT -H 'content-type: application/json' --data '{"data":{"myKey1":"myStoredValue1","myKey2":{"value":2}}}' http://localhost:9999/db/myDatabaseName
Response:
{"params":{"db":"myDatabaseName","keys":["myKey1","myKey2"]},"time":1453889946843,"msecs":20}
http://addr:port/db/:db/:key
GET Get the value for a key in a database.
Params
:db
- your database name (it'll be created if it doesn't exist):key
- the key whose value is to be retrieved from the database?callback
- an optional JavaScript callback function for JSONP requests
Example (plain text value):
curl http://localhost:9999/db/myDatabaseName/myKey
Response:
{"params":{"db":"myDatabaseName","key":"myKey"},"data":{"myKey":"myStoredValue"},"time":1453889946843,"msecs":20}
Example (JSON value):
curl http://localhost:9999/db/myDatabaseName/status
Response:
{"params":{"db":"myDatabaseName","key":"status"},"data":{"status":{"ok":true}},"time":1453972859367,"msecs":20}
Error response:
{"error":"Key not found in database [myOtherKey]","time":1453889946843,"msecs":20}
http://addr:port/db/:db
GET Get all keys and values in a database.
Params
:db
- your database name (it'll be created if it doesn't exist)
Example:
curl http://localhost:9999/db/myDatabaseName
Response:
{"params":{"db":"myDatabaseName"},"data":{"myKey1":"myValue1","myKey2":"myValue2","myKey3":"myValue3"},"time":1453889946843,"msecs":20}
NOTE: This optionally allows query parameters to be passed to LevelDB:
?lt
(less than) - return keys and values where the key is less than this param?gt
(greater than) - return keys and values where the key is greater than this param?lte
(less than or equal) - return keys and values where the key is less than or equal to this param?gte
(greater than or equal) - return keys and values where the key is greater than or equal to this param
Example:
curl http://localhost:9999/db/myDatabaseName?lt=myKey2
Response:
{"params":{"db":"myDatabaseName","lt":"myKey2"},"data":{"myKey1":"myValue1"},"time":1453889946843,"msecs":20}
http://addr:port/db/:db/:fromKey/:toKey
GET Get the keys and values for a range of keys in a database (the range is inclusive). If either of your keys includes a slash ("/") then use the following query instead:
GET http://addr:port/db/:db?gte=:fromKey<e=:toKey
Params
:db
- your database name (it'll be created if it doesn't exist):fromKey
- the key at the start of the range:toKey
- the key at the end of the range?callback
- an optional JavaScript callback function for JSONP requests
Example:
curl http://localhost:9999/db/myDatabaseName/myKey1/myKey2
Response:
{"params":{"db":"myDatabaseName","fromKey":"myKey1","toKey":"myKey2"},"data":{"myKey1":"myValue1","myKey2":"myValue2"},"time":1453889946843,"msecs":20}
http://addr:port/db/:db
DELETE Delete all key/value pairs in a database.
Params
:db
- your database name (it'll be created if it doesn't exist)?callback
- an optional JavaScript callback function for JSONP requests
Example:
curl -X DELETE http://localhost:9999/db/myDatabaseName
Response:
{"params":{"db":"myDatabaseName"},"deleted":["myKey1","myKey2"],"time":1453889946843,"msecs":20}
NOTE: This optionally allows query parameters to be passed to LevelDB:
?lt
(less than) - delete keys and values where the key is less than this param?gt
(greater than) - delete keys and values where the key is greater than this param?lte
(less than or equal) - delete keys and values where the key is less than or equal to this param?gte
(greater than or equal) - delete keys and values where the key is greater than or equal to this param
Example:
curl -X DELETE http://localhost:9999/db/myDatabaseName?lt=myKey2
Response:
{"params":{"db":"myDatabaseName","lt":"myKey2"},"deleted":["myKey1"],"time":1453889946843,"msecs":20}
http://addr:port/db/:db/:key
DELETE Delete a key/value pair in a database.
Params
:db
- your database name (it'll be created if it doesn't exist):key
- the key whose value is to be retrieved from the database?callback
- an optional JavaScript callback function for JSONP requests
Example:
curl -X DELETE http://localhost:9999/db/myDatabaseName/myKey
Response:
{"params":{"db":"myDatabaseName","key":"myKey"},"deleted":"myKey","time":1453889946843,"msecs":20}
http://addr:port/db/:db/:fromKey/:toKey
DELETE Delete a range of key/value pairs in a database. If either of your keys includes a slash ("/") then use the following query instead:
DELETE http://addr:port/db/:db?gte=:fromKey<e=:toKey
Params
:db
- your database name (it'll be created if it doesn't exist):fromKey
- the key at the start of the range:toKey
- the key at the end of the range?callback
- an optional JavaScript callback function for JSONP requests
Example:
curl -X DELETE http://localhost:9999/db/myDatabaseName/myKey1/myKey2
Response:
{params:{"db":"myDatabaseName","fromKey":"myKey1","toKey":"myKey2"},"deleted":["myKey1","myKey2"],"time":1453889946843,"msecs":20}
http://addr:port/version
GET Get the current version.
Example:
curl http://localhost:9999/version
Response:
{"version":"2.1.3","time":1453889946843,"msecs":5}
JSONP
Yes, you can add a ?callback=myCallback
query parameter to have the response be sent as JavaScript instead of JSON. But remember it's a Very Bad Idea to
connect a Plano server to a web/app front-end because there's no auth (yet).
TODO
- Mirroring/replication/backups