Codehooks are JavaScript functions which can intercept REST API calls to your database. Codehooks gives you greater flexibility and power than using webhooks.
Some typical use-cases for codehooks are:
🚀 If you like the codehooks concept of restdb.io, please also check out our new backend service: codehooks.io. It integrates Javascript serverless functions with a document database, a key/value database, a message queue and a CRON-like job system. A powerful CLI lets you instantly deploy new code.
Codehooks can also run as background jobs to perform tasks at specific times or intervals (using crontab).
A codehook is a plain JavaScript function with a specific name to indicate when it's invoked, e.g.
function beforeGET(req, res) {
...
res.end();
}
This function is called before a GET
request.
Codehooks are created in the Collection settings, under the "Codehook" tab.
The codehooks listed here intercept API traffic and internal traffic to your database. They are invoked either before or after combined with one of the http verbs: GET, POST, PUT and DELETE. Only the combinations listed here are available.
Codehook | Available parameters | Description |
---|---|---|
Restful hooks | ||
beforeGET(req, res) | req.query, req.hint, res.end([optional error]) | Called before a GET operation |
beforePUT(req, res) | req.body, res.end([optional error]) | before a PUT |
afterPUT(req, res) | req.body, req.result | after a PUT |
beforePOST(req, res) | req.body, res.end([optional data, error]) | before a POST |
afterPOST(req, res) | req.body, req.result, res.end() | after a POST |
beforeDELETE(req, res) | req.body, res.end([optional error]) | before a DELETE |
afterDELETE(req, res) | req.body | after a DELETE |
beforePATCH(req, res) | req.body, res.end([optional error]) | before a PATCH |
afterPATCH(req, res) | req.body, req.result | after a PATCH |
Background hooks | ||
runJob(req, res) | res.end("Optional message to log") | triggers on a crontab expression |
Codehooks now also support using async/await
and/or promises
. You can now avoid using callback functions and nesting when calling database API or network API functions. Ex:
const beforePOST = async (req,res) => {
const cars = await db.get("/rest/automobiles");
const car_exists = cars.find(car => car.name === req.body.carname);
res.end({"data": {car_exists}});
}
Function | Parameters | Description |
---|---|---|
db.get(path, query, hint, callback) | path: REST url, query: query object, hint: hint object, callback: function(error, data) | get operation to the current database. E.g. db.get('/rest/customer',{}, {}, function(error, data){...} |
db.put(path, data, callback) | path: REST url, data: JSON, callback: function(error, data) | put operation to the current database |
db.patch(path, data, callback) | path: REST url, data: JSON, callback: function(error, data) | patch operation to the current database |
db.post(path, data, callback) | path: REST url, data: JSON, callback: function(error, data) | post operation to the current database |
db.delete(path, callback) | path: REST url, data: JSON, callback: function(error, data) | delete operation to the current database |
Function | Parameters | Description |
---|---|---|
request(options, callback) | options: json, callback(error, response, body) | Network API |
slack(options, callback) | options: message, slackhookurl, channel, callback: function(result) | Send a message to Slack |
sendMail(options, callback) | options: to, subject, html, company, callback: function(result) | Send a html email |
Function | Parameters | Description |
---|---|---|
log.debug(str, ...) | Variable list of arguments log.info, log. debug, log. error, log.fatal | Writes output to Rest inspector |
async.series(funcarray, callback) | Array of functions | Call an array of functions in a sequence and callback when all are done. See examples. |
async.waterfall(funcarray, callback) | Array of functions | First function calls next etc. in sequence passing parameters along and finally callback when all are done. See examples. |
async.apply(function, arguments) | Apply one function with arguments | Creates a continuation function with some arguments already applied. Add to array and use with async.series. See examples. |
template(str, context) | Run Handlebars on str with context json data | Handlebars templating function |
markdown(str) | Run Markdown parser on str | Returns valid HTML |
auth.decode_jwt(token, callback) | A valid JWT token | callback with (err, decodedjwt) as arguments |
auth.verify_jwt(token, secret, callback) | A valid JWT token and your secret | callback with (err, decodedjwt) as arguments |
verifyHash(token, secret, 'RSA-SHA256', 'base64'); | token-string, secret-string, algorithm, encoding | Verify crypto tokens, e.g. Shopify webhook token |
A codehook must call res.end()
when it is done. Failing to to so will lead to a timeout, and finally degrading the performance of your system.
Return method | Parameters | Description |
---|---|---|
res.end() | -na- | Normal return, no data |
res.end({"data": {}}) | json | Change data on POST or PUT |
res.end({"error": [{field:<fieldname>,message:<message>},...]}) | json | Validation on POST or PUT (field name is optional). |
res.end({"query": {}, "hint": {}}) | json | Change query or hint on GET |
var beforeGET = function(req, res) {
log.debug("Calling get ", req.query, req.hint);
// modify all queries to only show people with Hunter as lastname
req.query.lastname = "Hunter";
res.send({"query": req.query});
}
function beforeGET(req, res) {
if (req.hint["#usersession"].email) {
// logged in session allowed
res.end();
} else {
// check something, e.g. JWT token
res.end({"error": {"statuscode": 401, "message": "Forbidden to GET"}});
}
}
// helper function
function validate(field) {
// field must contain the string London or Berlin
if (!/london|berlin/.test(field)) {
var validation = [
{field: "address",message:"not valid, must be London or Berlin"}
];
return validation;
} else {
return null;
}
}
// codehook
function beforePUT(req, res){
log.debug("PUT: ",req.body);
// call a helper function for custom validation
var errors = validate(req.body.address);
if (errors){
res.end({"error": errors});
} else {
// add a calculated field
const tax = (req.body.salary * 0.33).toFixed(2);
res.end({"data": {tax}});
}
}
function afterPUT(req, res){
log.debug("Inside after PUT, data is saved as", req.body);
...
res.end();
}
function beforePOST(req, res){
log.debug("POST: ",req.body);
...
res.end();
}
function afterPOST(req, res){
log.debug("POST after: ",req.body);
...
res.end();
}
function beforeDELETE(req, res){
log.debug("Inside DELETE ",req.body);
res.end();
}
You can access shared settings of your database using "context.settings":
function beforePOST(req, res){
var zap_url = context.settings.zap_url;
...
res.end();
}
As you seen in the examples above, you can log stuff from inside a codehook. The output can be seen in the Rest inspector. Use different log levels to better filter your log.
...
log.info("All is well");
log.debug("what's going on with", a, b, c);
log.error("Sorry, that broke: ", brokendata);
log.fatal("Yaiks, this should not happen");
...
A codehook function can perform network calls to other system using an the request API.
function afterPOST(req, res) {
var opt = {
method: 'GET',
headers : {
'User-Agent' : 'restdb.io (codehook)'
},
url: "http://fancyservice.io/api/foo"
};
request(opt, function(error, response, data){
if(response.statusCode !== 200){
log.error('Invalid Status Code Returned:', response.statusCode);
res.end({error:[{message:"Unable to call fancyservice"}]});
return
}
var json = JSON.parse(data);
log.info("Got data: ",response.statusCode, json.length);
// do something with json data
...
res.end();
});
}
// POST|PUT|DELETE
request({
url: url,
method: "POST | PUT | DELETE",
json: true,
headers: {
"content-type": "application/json",
},
body: jsondata
}, function(error, response, body) {
...
});
db.get("/rest/customer", {"name": "Jane"}, {$max: 5}, function (err, results) {
log.debug("Done get: ",err, results);
...
res.end();
});
var htmlmail = "<h1>Hello</h1><p>Dear customer ...</p>";
var mailopt = {
to: "bg@ms.com",
subject: "Issue report: "+body.ticket,
html: htmlmail,
company: "My company Inc"
};
sendMail(mailopt, function(maildata){
res.end();
});
var slackhookurl = context.settings.slack.url;
var slackopt = {
"message": ":loudspeaker:\n New mezz: \n Hello Slack!",
"slackhookurl": slackhookurl,
"channel": "#dev"
}
// post to Slack
slack(slackopt, function(){
res.end();
});
var token = req.hint['#headers'].authorization.split(' ')[1];
auth.decode_jwt(token, function(jwterr, jwttoken){
log.debug("JWT", jwterr, jwttoken);
…
res.end();
})
var token = req.hint['#headers'].authorization.split(' ')[1];
var secret = "my xxx secret key";
auth.verify_jwt(token, secret, function(jwterr, jwttoken){
log.debug("JWT", jwterr, jwttoken);
…
res.end();
})
Records in the System_jobs collection are used to schedule tasks that invokes your codehooks.
Creating jobs in the System_jobs collection.
You can check the status of any current job in the System_log collection. It shows the res.end(...) output from a job. Any log.debug()
statements can be inspected with the Rest inspector.
A crontab expression is a special string with five components that builds an expression for a job schedule:
E.g. run job each 10. th minute: */10 * * * *
.
Tip: let https://crontab.guru help you build your expression.
Example code:
function runJob(req, res) {
log.debug("Inside a background job");
...
res.end();
}
It's important to understand that a codehook function runs in a confined space with limited rights. It will not execute in the case of:
res.end()