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:
beforeGet
: Executed onGET
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.afterGet
: Executed onGET
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 otherbefore*
hooks, also has the power to abort the operation.beforeCreate
: Executed onPOST
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.afterCreate
: Executed onPOST
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).beforeUpdate
: Executed onPUT
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.afterUpdate
: Executed onPUT
requests, after the query is processed. Similar toafterCreate
, 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.beforeDelete
: Executed onDELETE
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.afterDelete
: Executed onDELETE
requests, after the query is processed. Similar toafterCreate
andafterUpdate
, 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
andbeforeDelete
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!