Codehooks

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:

  • custom logic
  • validation and calculation
  • system integration

🚀 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.

example codehooks

Codehook API

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.

CodehookAvailable parametersDescription
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.resultafter 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.bodyafter a DELETE
beforePATCH(req, res)req.body, res.end([optional error])before a PATCH
afterPATCH(req, res)req.body, req.resultafter a PATCH
Background hooks
runJob(req, res)res.end("Optional message to log")triggers on a crontab expression

Async/await and promise support

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}});
}

Database API functions

FunctionParametersDescription
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

Network API functions

FunctionParametersDescription
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

Utility API functions

FunctionParametersDescription
log.debug(str, ...)Variable list of arguments log.info, log. debug, log. error, log.fatalWrites output to Rest inspector
async.series(funcarray, callback)Array of functionsCall an array of functions in a sequence and callback when all are done. See examples.
async.waterfall(funcarray, callback)Array of functionsFirst 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 argumentsCreates 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 dataHandlebars templating function
markdown(str)Run Markdown parser on strReturns valid HTML
auth.decode_jwt(token, callback)A valid JWT tokencallback with (err, decodedjwt) as arguments
auth.verify_jwt(token, secret, callback)A valid JWT token and your secretcallback with (err, decodedjwt) as arguments
verifyHash(token, secret, 'RSA-SHA256', 'base64');token-string, secret-string, algorithm, encodingVerify crypto tokens, e.g. Shopify webhook token

Return values from a codehook

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 methodParametersDescription
res.end()-na-Normal return, no data
res.end({"data": {}})jsonChange data on POST or PUT
res.end({"error": [{field:<fieldname>,message:<message>},...]})jsonValidation on POST or PUT
(field name is optional).
res.end({"query": {}, "hint": {}})jsonChange query or hint on GET

Examples

beforeGET

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});
}

beforeGET with error

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"}});
    }
}

beforePUT / PATCH

// 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}});
    }
}

afterPUT / PATCH

function afterPUT(req, res){
    log.debug("Inside after PUT, data is saved as", req.body);
        ...
    res.end();   
}

beforePOST

function beforePOST(req, res){
    log.debug("POST: ",req.body);
    ...
    res.end();
}

afterPOST

function afterPOST(req, res){
    log.debug("POST after: ",req.body);
    ...
    res.end();
}

beforeDELETE

function beforeDELETE(req, res){
    log.debug("Inside DELETE ",req.body);
    res.end();
}

Shared settings

You can access shared settings of your database using "context.settings":

function beforePOST(req, res){
    var zap_url = context.settings.zap_url;
        ...
    res.end();
}

Logging

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");
...

Network calls from your codehook

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) {
        ...
    });

Database calls from your codehook

db.get("/rest/customer", {"name": "Jane"}, {$max: 5}, function (err, results) {
    log.debug("Done get: ",err, results);
    ...
    res.end();
});

Send email

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();
    });

Post a message to Slack

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(); 
}); 

JWT decoding

var token = req.hint['#headers'].authorization.split(' ')[1];
auth.decode_jwt(token, function(jwterr, jwttoken){
        log.debug("JWT", jwterr, jwttoken);
                …
        res.end();    
 })

JWT verification

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();
 })

Background hooks

Records in the System_jobs collection are used to schedule tasks that invokes your codehooks.

Creating jobs in the System_jobs collection.

background codehooks

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.

system log

Crontab expressions

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();
}

Warning

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:

  • syntax errors
  • not explicitly returning with res.end()
  • not returning within x seconds
  • hacking attempts and other malicious code (may lead to blacklisting of account)