Using API hooks

All the Hooks are chainable blocks of arbitrary logic that run at specific points in the lifecycle of a request, having the power to modify the request and response, or to abort an operation completely.

The main method for interacting with data in DADI API is a set of RESTful endpoints, created automatically for each collection, that allow you to query, create, update or delete documents based on the HTTP verb sent in the request.

# Get documents
GET /1.0/my-database/my-collection

# Create documents
POST /1.0/my-database/my-collection

# Update documents
PUT /1.0/my-database/my-collection

# Delete documents
DELETE /1.0/my-database/my-collection

This a hassle-free, zero-setup-involved way of interacting with your data in the simplest way possible: when you ask for a document, API will give you the document exactly as it is stored in the database; when you create a document, API will store it exactly as you supply it.

But sometimes you need a bit more flexibility, like adding to a created document a compound field that is automatically generated from the content of other fields. Or massage the way data is presented in a response without actually changing the way it’s stored internally. For this, we have hooks.

🔗Introducing hooks

Hooks are chainable blocks of arbitrary logic that run at specific points in the lifecycle of a request, having the power to modify the request and response, or to abort an operation completely. There are 8 types of hooks:

  1. beforeGet: Executed on GET requests, before the query is processed. Has the ability to change the parameters supplied in the request, so effectively modify the query before it’s processed. Can abort the operation by throwing an error or returning a rejected Promise.

  2. afterGet: Executed on GET requests, after the query has been processed and before the response is sent to the user. Has the ability to modify the response contents and, unlike other before* hooks, also has the power to abort the operation.

  3. beforeCreate: Executed on POST requests, before the query is processed. Has the ability to change the documents about to be inserted. Can abort the operation by throwing an error or returning a rejected Promise.

  4. afterCreate: Executed on POST requests, after the query is processed. Does not have the ability to do any further changes to the documents inserted or to change the contents of the response. It’s designed to perform asynchronous, fire-and-forget operations as a result of the document creation (e.g. notify an external service).

  5. beforeUpdate: Executed on PUT requests, before the query is processed. Has the ability to change the query that defines which documents are about to be updated, as well as the contents of the update itself. Can abort the operation by throwing an error or returning a rejected Promise.

  6. afterUpdate: Executed on PUT requests, after the query is processed. Similar to afterCreate, it does not have the ability to do any further changes to the updated documents or to change the contents of the response. It’s designed to perform asynchronous, fire-and-forget operations as a result of the document update.

  7. beforeDelete: Executed on DELETE requests, before the query is processed. Has the ability to change the query that defines which documents are about to be deleted. Can abort the operation by throwing an error or returning a rejected Promise.

  8. afterDelete: Executed on DELETE requests, after the query is processed. Similar to afterCreate and afterUpdate, it does not have the ability to do any further changes to the deleted documents (like undo the operation) or change the contents of the response. It’s designed to perform asynchronous, fire-and-forget operations as a result of the document deletion.

🔗Example

Imagine an articles collection with the following fields:

  • title: The article title (e.g. Decentralized Web Services with DADI)
  • slug: A URL-friendly version of the title (e.g. decentralized-web-services-with-dadi)
  • body: The article body

To create an article, you could simply put the fields above in the body of a POST request and sent it to API, like so:

POST https://api.somedomain.tech/1.0/your-database/articles

{
    "title": "DADI: Decentralized Architecture for a Democratic Internet",
    "slug": "dadi-decentralized-architecture-for-a-democratic-internet",
    "body": "A new era of cloud computing services, powered by blockchain technology."
}

Whilst this works, it puts on consumers the responsibility of generating the slug from the title, which leaves room for inconsistencies due to different implementations. This feels like a task that should be performed automatically by API at the point of insertion. This is where hooks come in.

We start by creating the hook file. We’ll place it at workspace/hooks/slugify.js.

// Creates a URL-friendly version (slug) of any given string
function slugify(text) {
  return text.toString().toLowerCase()
    .replace(/\s+/g, '-')
    .replace(/[^\w\-]+/g, '')
    .replace(/\-\-+/g, '-')
    .replace(/^-+/, '')
    .replace(/-+$/, '')
}

module.exports = function (obj, type, data) {
  // `obj` is the document being inserted;
  // `type` is a numeric code for the type of operation being carried out:
  //    - 0: create
  //    - 1: update
  //    - 2: delete
  // `data` is an object containing additional data, such as the collection
  // schema and other properties that vary with the type of hook being used.

  // In this case, we're simply creating a `slug` property in the document
  // with the result of slugifying the content of the `title` property.
  // For now, we're hardcoding the names of the `slug` and `title` fields
  // in the hook, more on this shortly.
  obj.slug = slugify(obj.title)
  return obj
}

The last thing we need to do is to activate this hook in the collection schema, specifying where in the lifecycle of the request it will get executed.

"settings": {
  "hooks": {
    "beforeCreate": ["slugify"],
    "beforeUpdate": ["slugify"]
  }
}

The above means that the hook will get executed just before a document is created or updated, ensuring that the slug field is always populated with the URL-friendly version of the title field.

Alternatively to defining an array of strings where each element is the name of a hook, we can supply an array of objects, allowing us to provide extra options.

"settings": {
  "hooks": {
    "beforeCreate": [
      {
        "hook": "slugify",
        "options": {
          "from": "title",
          "to": "slug"
        }
      }
    ]
  }
}

In the example above, we’re specifying an options object, which gets passed to the hook file. This allows us to make the hook a bit more generic, as we can remove any hardcoded field names from its logic and instead supply them via the options object. To reflect this, we must also change the hook file:

// Creates a URL-friendly version (slug) of any given string
function slugify(text) {
  return text.toString().toLowerCase()
    .replace(/\s+/g, '-')
    .replace(/[^\w\-]+/g, '')
    .replace(/\-\-+/g, '-')
    .replace(/^-+/, '')
    .replace(/-+$/, '')
}

module.exports = function (obj, type, data) {
  // We get the names of the source (`from`) and target (`to`) fields
  // from the options object we defined in the collection schema, available
  // at `data.options`.
  obj[data.options.to] = slugify(obj[data.options.to])
  return obj
}

With the hook in place, we can achieve the same result as before without having to specify the contents of the slug field in the payload:

POST https://api.somedomain.tech/1.0/your-database/articles

{
    "title": "DADI: Decentralized Architecture for a Democratic Internet",
    "body": "A new era of cloud computing services, powered by blockchain technology."
}

🔗Taking it further

The example above is just the simplest of possible use cases for hooks, but the possibilities are endless. These are just a few things I can think off the top of my head where you could use them:

  • A beforeCreate hook that takes a URL from a field, fires an HTTP request to an external API and injects the response in another field — for example, get a URL for a tweet, grab its content and add it to the document

  • A bespoke authentication/authorisation layer that uses a set of beforeGet, beforeCreate, beforeUpdate and beforeDelete hooks to grant or block access to resources based on the information sent in the request

  • An afterCreate hook that indexes documents on a search system after they’re created

With great flexibility comes great… awesomeness? I’m looking forward to know what you’ll build with API hooks – hit me up!

Related articles

More tutorials articles
Mail icon

Like what you are reading? Want to earn tokens by becoming a DADI Node? Save money on cloud computing services? Build amazing digital product with DADI Web Services? Join our mailing list.

To hear about our news, events and products or services subscribe now. You can also indicate which services you are interested in, which we use for research and to inform the content that we send generally.

* You can unsubscribe at any time by emailing us at data@dadi.cloud or by clicking on the unsubscribe link which can be found in our emails to you. Read our Privacy Policy.