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 --singleThen 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-servercanhazdb-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.pemcanhazdb-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:7061npm install --save canhazdb-server canhazdb-driver-ejdbconst 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.