A sharded and clustered database communicated over http rest with notifications included.
You must have a minimum version of Node 12 installed.
Create the tls files you need to secure your cluster.
A bash script ./makeCerts.sh
provided will create a folder with test certs you can use.
You can opt out of tls by omitting the tls option from canhazdb.
You can talk to the database via http/https using your favourite http client, or you can use the official client.
As of version 5.0.0, drivers have been abstracted out and installed separately.
The current official drivers are:
It should be fairly trivial to implement a driver for other databases. If you would like to create a custom driver, take a look at the nedb driver index file for an example.
The quickest way to setup a test server is via:
docker run -itp 8060:8060 canhazdb/server --single
Then visit http://localhost:8060
But you can create a production ready and scalable stack by using the stack.yml file as an example.
This will give you TLS authenication and encryption along with persistent storage.
npm install --global canhazdb-server
canhazdb-server \
--driver canhazdb-driver-ejdb \
--host localhost \
--port 7061 \
--query-port 8061 \
--data-dir ./canhazdb/one \
--tls-ca ./certs/ca.cert.pem \
--tls-cert ./certs/localhost.cert.pem \
--tls-key ./certs/localhost.privkey.pem
canhazdb-server \
--driver canhazdb-driver-ejdb \
--host localhost \
--port 7062 \
--query-port 8062 \
--data-dir ./canhazdb/two \
--tls-ca ./certs/ca.cert.pem \
--tls-cert ./certs/localhost.cert.pem \
--tls-key ./certs/localhost.privkey.pem \
--join localhost:7061
canhazdb-server \
--driver canhazdb-driver-ejdb \
--host localhost \
--port 7063 \
--query-port 8063 \
--data-dir ./canhazdb/three \
--tls-ca ./certs/ca.cert.pem \
--tls-cert ./certs/localhost.cert.pem \
--tls-key ./certs/localhost.privkey.pem \
--join localhost:7061
npm install --save canhazdb-server canhazdb-driver-ejdb
const fs = require('fs');
const https = require('https');
const axios = require('axios');
const canhazdb = require('canhazdb-server');
async function main () {
const tls = {
key: fs.readFileSync('./certs/localhost.privkey.pem'),
cert: fs.readFileSync('./certs/localhost.cert.pem'),
ca: [ fs.readFileSync('./certs/ca.cert.pem') ],
requestCert: true /* this denys any cert not signed with our ca above */
};
const node1 = await canhazdb({
driver: 'canhazdb-driver-ejdb',
host: 'localhost',
port: 7061, queryPort: 8061,
dataDirectory: './canhazdata/one',
tls, single: true
});
const node2 = await canhazdb({
driver: 'canhazdb-driver-ejdb',
host: 'localhost',
port: 7062, queryPort: 8062,
dataDirectory: './canhazdata/two',
tls, join: ['localhost:7061']
});
// You can join to other nodes after starting:
// await node2.join({ host: 'otherhost', port: 7063 })
const postRequest = await axios(`${node1.url}/tests`, {
httpsAgent: new https.Agent(tls),
method: 'POST',
data: {
a: 1,
b: 2,
c: 3
}
});
// node2.url === 'https://localhost:8061'
const result = await axios(`${node2.url}/tests/${postRequest.data.id}`, {
httpsAgent: new https.Agent(tls)
});
console.log(result.data);
/*
{
a: 1,
b: 2,
c: 3
}
*/
}
The system
namespace is used for storing the following metadata related to the database.
You can query them like any normal collection.
The system.collections
collection contains a document for each collection, along with the
amount of documents that stores.
axios('/system.collections', {
httpsAgent: new https.Agent(tls)
}) === [{
id: 'uuid-uuid-uuid-uuid',
collectionId: 'tests',
documentCount: 1
}]
Method | Path | Description | |
---|---|---|---|
1 | GET | /:collectionId?fields | List all documents for a collection |
2 | GET | /:collectionId/:documentId?query&count&fields&limit&order | Get a document by id |
3 | POST | /:collectionId | Create a new document |
4 | PUT | /:collectionId/:documentId | Replace a document by id |
5 | PUT | /:collectionId/:documentId?query | Replace multiple document matching query |
6 | PATCH | /:collectionId/:documentId | Partially update a document by id |
7 | PATCH | /:collectionId/:documentId?query | Partially update multiple document matching query |
8 | DELETE | /:collectionId/:documentId | Delete a document by id |
9 | DELETE | /:collectionId/:documentId?query | Delete multiple document matching query |
10 | POST | /_/locks | Lock a collection/document/field combination |
11 | DELETE | /_/locks/:lockId | Release a lock |
1. Get item by id
Method | GET |
URL | /collectionId |
Fields | JSON Array |
HTTP Request:
axios({
url: 'https://localhost:8061/tests/example-uuid-paramater?fields=["firstName"]',
})
Client:
client.get('tests', {
query: {
id: 'example-uuid-paramater'
}
});
2. Get document count in a collection
Method | GET |
URL | /collectionId?count=true |
Query | Mongo Query Syntax |
HTTP Request:
axios({
url: 'https://localhost:8061/tests?count=true&query={"firstName":"Joe"}',
})
Client:
client.count('tests', {
query: {
firstName: 'Joe'
}
});
3. Get items in a collection
Method | GET |
URL | /collectionId |
Query | Mongo Query Syntax |
Fields | JSON Array |
Limit | Number |
Order | Direction(fieldName) |
HTTP Request:
axios({
url: 'https://localhost:8061/tests?query={"firstName":"Joe"}&fields=["firstName"]&limit=10&order=desc(firstName)',
})
Client:
client.get('tests', {
query: {
firstName: 'Joe'
},
limit: 10,
order: 'desc(firstName)'
});
4. Create a new document in a collection
Method | POST |
URL | /collectionId |
Data | JSON |
HTTP Request:
axios({
url: 'https://localhost:8061/tests',
method: 'POST',
data: {
firstName: 'Joe'
}
})
Client:
client.post('tests', {
firstName: 'Joe'
});
5. Replace a document by id
Method | PUT |
URL | /collectionId/documentId |
Data | JSON |
HTTP Request:
axios({
url: 'https://localhost:8061/tests/example-uuid-paramater',
method: 'PUT',
data: {
firstName: 'Zoe'
}
})
Client:
client.put('tests', {
firstName: 'Joe'
});
6. Replace multiple documents by query
Method | PUT |
URL | /collectionId/documentId |
Data | JSON |
HTTP Request:
axios({
url: 'https://localhost:8061/tests?query={"location":"GB"}',
method: 'PUT',
data: {
firstName: 'Zoe',
location: 'GB',
timezone: 'GMT'
}
})
Client:
client.put('tests', {
firstName: 'Zoe',
location: 'GB',
timezone: 'GMT'
}, {
query: {
location: 'GB'
}
});
7. Partially update multiple documents by id
Method | PATCH |
URL | /collectionId/documentId |
Data | JSON |
HTTP Request:
axios({
url: 'https://localhost:8061/tests/example-uuid-paramater',
method: 'PATCH',
data: {
timezone: 'GMT'
}
})
Client:
client.patch('tests', {
timezone: 'GMT'
}, {
query: {
location: 'GB'
}
});
8. Partially update multiple documents by query
Method | PATCH |
URL | /collectionId/documentId |
Data | JSON |
HTTP Request:
axios({
url: 'https://localhost:8061/tests?query={"location":"GB"}',
method: 'PATCH',
data: {
timezone: 'GMT'
}
})
Client:
client.patch('tests', {
timezone: 'GMT'
}, {
query: {
location: 'GB'
}
});
9. Delete a document by id
Method | DELETE |
URL | /collectionId/documentId |
HTTP Request:
axios({
url: 'https://localhost:8061/tests/example-uuid-paramater',
method: 'DELETE'
})
Client:
client.delete('tests', {
query: {
id: 'example-uuid-paramater'
}
});
10. Delete multiple documents by query
Method | DELETE |
URL | /collectionId/documentId |
HTTP Request:
axios({
url: 'https://localhost:8061/tests?query={"location":"GB"}',
method: 'DELETE'
})
Client:
client.delete('tests', {
query: {
location: 'GB'
}
});
11. Lock a collection/document/field combination
Method | POST |
URL | /_/locks |
Data | JSON Array |
HTTP Request:
const lock = await axios({
url: 'https://localhost:8061/_/locks',
method: 'POST',
data: ['users']
});
const lockId = lock.data.id;
Client:
const lockId = await client.lock('users');
12. Release a lock
Method | DELETE |
URL | /_/locks/:lockId |
HTTP Request:
const lock = await axios({
url: 'https://localhost:8061/_/locks',
method: 'POST',
data: ['users']
});
const lockId = lock.data.id;
const lock = await axios({
url: 'https://localhost:8061/users',
method: 'POST',
headers: {
'x-lock-id': lockId,
'x-lock-strategy': 'wait' // optional: can be 'fail' or 'wait'. default is 'wait'.
}
});
await axios({
url: `https://localhost:8061/_/locks/${lockId}`,
method: 'DELETE'
});
Client:
const lockId = await client.lock(['users']);
const newDocument = await client.post('users', {
name: 'mark'
}, {
lockId,
lockStrategy: 'wait' // optional: can be 'fail' or 'wait'. default is 'wait'.
});
await client.unlock(lockId);
This project is licensed under the terms of the AGPL-3.0 license.