Custom Component: HelloAppmixer

At this point, you have your Appmixer system up and running and you know how to create and start a flow. Now it's time to guide you through the process of implementing your own, custom component. In our tutorial, we implement a component that has one input port and prints out the text "Hello Appmixer!" to the console as soon as data arrives to its input port.

Directory structure and files

Our component will live in a namespace "appmixer.myservice.mymodule.HelloAppmixer". All components in Appmixer are organized in the hierarchy [vendor].[service].[module].[component]. Create the following files (and the required directories):

  • components/src/appmixer/myservice/mymodule/HelloAppmixer/HelloAppmixer.js, component source code file

  • components/src/appmixer/myservice/mymodule/HelloAppmixer/component.json, component manifest file

  • components/src/appmixer/myservice/service.json, service manifest file

The resulting directory structure should look like this:

/PATH/TO/TRIAL/components/src/appmixer$ tree myservice/
myservice/
├── mymodule
│ └── HelloAppmixer
│ ├── component.json
│ └── HelloAppmixer.js
└── service.json
2 directories, 3 files

Component Definition

Now let's fill up the files with the minimum content necessary for the engine to consider this as a valid component.

components/src/appmixer/myservice/mymodule/HelloAppmixer/HelloAppmixer.js

This file exports a plain NodeJS module. Normally, this module exports virtual methods that the Appmixer engine understands (the most important one is the receive()). For now, we just leave the module empty. It's enough to create bare minimum for the engine to work with our component.

module.exports = {};

components/src/appmixer/myservice/mymodule/HelloAppmixer/component.json

The component manifest file defines properties of our component (such as name, icon, input/output ports, ....). For now, we just fill in the only required field, the name and the optional but useful icon. It's important to note that the name must follow the directory structure we just created.

{
"name": "appmixer.myservice.mymodule.HelloAppmixer",
"icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gEeCRYRYwe3nwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAXSURBVAjXY2BgYPj//z8WErsoBAw+HQBdc1+hmBJKIwAAAABJRU5ErkJggg=="
}

components/src/appmixer/myservice/service.json

The service manifest defines properties of our app as it appears in the left palette in the Designer UI.

{
"name": "appmixer.myservice",
"label": "My Service",
"category": "applications",
"description": "My Custom App",
"icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gEeCRYRYwe3nwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAXSURBVAjXY2BgYPj//z8WErsoBAw+HQBdc1+hmBJKIwAAAABJRU5ErkJggg=="
}

If you now restart the engine and refresh the Designer page in your browser, you should see a new app in the left palette:

# First kill the engine process running in the foreground with e.g. Ctrl-c
/PATH/TO/TRIAL$ ./run.sh
Custom App and Component

Extending our Component Definition

Now let's make our component actually do something. First, we start with extending the component manifest file (component.json) by adding an input port so that we can connect our component to other components:

{
"name": "appmixer.myservice.mymodule.HelloAppmixer",
"icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gEeCRYRYwe3nwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAXSURBVAjXY2BgYPj//z8WErsoBAw+HQBdc1+hmBJKIwAAAABJRU5ErkJggg==",
"inPorts": [
{
"name": "in"
}
]
}

This is enough to define an input port with no restrictions. However, we would like to add two properties (text and count) that we can count on in our component behaviour and that we can assume are always defined (i.e. marked as required in the Designer UI). Moreover, we will also define the Inspector UI for both our properties so that the user can fill them in in the Designer UI.

{
"name": "appmixer.myservice.mymodule.HelloAppmixer",
"icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gEeCRYRYwe3nwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAXSURBVAjXY2BgYPj//z8WErsoBAw+HQBdc1+hmBJKIwAAAABJRU5ErkJggg==",
"inPorts": [
{
"name": "in",
"schema": {
"type": "object",
"properties": {
"text": { "type": "string" },
"count": { "type": "number" }
},
"required": ["text"]
},
"inspector": {
"inputs": {
"text": {
"type": "text",
"group": "transformation",
"label": "Text",
"index": 1
},
"count": {
"type": "number",
"group": "transformation",
"label": "Count",
"index": 2
}
},
"groups": {
"transformation": {
"label": "Transformation"
}
}
}
}
]
}

When you now restart the Appmixer engine, refresh the Designer page and connect our component to another component, you should see both our properties in the Inspector:

Custom Component Inspector panel

Note that at this point, our component will still not work. If you try to run this flow, you will notice errors coming from the engine in the console. This is because we have defined an input port on our component but did not yet implement the receive() method. Let's do that and let's make our component simply print a text count-times to the console. We do that by modifying the HelloAppmixer.js file:

module.exports = {
receive(context) {
let count = context.messages.in.content.count || 1;
let text = context.messages.in.content.text;
for (let i = 0; i < count; i++) {
console.log(text);
}
}
};

Notice the context.message object that is a dictionary with keys pointing to input ports and values containing the content property that gives us a dictionary with all our properties that we defined on the input port. If you now restart the Appmixer engine, refresh the Designer page and configure our flow like this:

Custom Component Inspector panel

and then run the flow, you should see an output in the console that looks something like this:

HelloAppmixer started: 2019-03-13T14:32:14.887Z
HelloAppmixer started: 2019-03-13T14:32:14.887Z
HelloAppmixer started: 2019-03-13T14:32:14.887Z
HelloAppmixer started: 2019-03-13T14:32:14.887Z
HelloAppmixer started: 2019-03-13T14:32:14.887Z
HelloAppmixer started: 2019-03-13T14:32:14.887Z
HelloAppmixer started: 2019-03-13T14:32:14.887Z
HelloAppmixer started: 2019-03-13T14:32:14.887Z
HelloAppmixer started: 2019-03-13T14:32:14.887Z
HelloAppmixer started: 2019-03-13T14:32:14.887Z

Sending HTTP Requests

We now have a working component that is able to process incoming messages. Our component is not yet very useful though. In this section, we will show how to extend our component to be able to receive messages, call an external API and send the result to the output port so that other connected components can work with the data. Let's now again extend the component manifest file (component.json) by adding an output port:

{
"name": "appmixer.myservice.mymodule.HelloAppmixer",
"icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAIAAABLbSncAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gEeCRYRYwe3nwAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAAXSURBVAjXY2BgYPj//z8WErsoBAw+HQBdc1+hmBJKIwAAAABJRU5ErkJggg==",
"inPorts": [
{
"name": "in",
"schema": {
"type": "object",
"properties": {
"text": { "type": "string" },
"count": { "type": "number" }
},
"required": ["text"]
},
"inspector": {
"inputs": {
"text": {
"type": "text",
"group": "transformation",
"label": "Text",
"index": 1
},
"count": {
"type": "number",
"group": "transformation",
"label": "Count",
"index": 2
}
},
"groups": {
"transformation": {
"label": "Transformation"
}
}
}
}
],
"outPorts": [
{
"name": "out",
"options": [
{ "label": "Hello Data", "value": "mydata" }
]
}
]
}

Our component behaviour will also change. This time, we will not print anything to the console. Instead, we will call some external HTTP endpoint and pass the result to our output port. For a convenience, we will use a NodeJS library Axios to send HTTP requests. Before we can start using the library, we have to install it first. This can simply by done by creating a package.json file with the Node package manager and installing our library:

$ cd components/src/appmixer/myservice/mymodule/HelloAppmixer/;
$ npm init -y
$ npm install axios --save

Now we can use the library in our component module (HelloAppmixer.js):

const axios = require('axios');
module.exports = {
receive(context) {
let count = context.messages.in.content.count || 1;
let text = context.messages.in.content.text;
return axios.get('https://postman-echo.com/get?text=' + text + '&count=' + count)
.then(response => {
context.sendJson({
mydata: 'Received from Postman echo: ' + JSON.stringify(response.data.args)
}, 'out');
});
}
};

Now we can restart the Appmixer engine and create a flow like this:

Custom Component Inspector panel

When you run this flow, you should see a new tweet in your Twitter account that looks something like this:

Tweet received from Appmixer Custom Component

You can also check the Insights page for this flow to see the logs:

Insights page

Conclusion

In this tutorial, we showed how you can create a custom component that processes incoming messages, calls an external HTTP API and sends outgoing messages for other connected components to process. We used simple example flows with the OnStart component as a trigger. However, instead of our OnStart trigger component, we could have used other triggers, such as schedulers, polling components (e.g. the included PipeDrive.NewPerson trigger that checks for new contacts in the Pipedrive CRM), webhooks and more. We suggest to browse the online documentation to learn more about all the features of Appmixer and how you can apply them in your custom components.