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 and one output port, consumes messages on its input port, sends an HTTP request to an external API for each incoming message and outputs the response to the output port for other connected components to ingest.

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

We prepared the HelloAppmixer component from this tutorial for you. You can download it from here:

You can just download the component and publish it with:

# Install and initialize the Appmixer CLI tool if you haven't done so already:
$ npm install -g appmixer
$ appmixer url http://localhost:2200
# Use e.g. the same user as you signed-up with in the Getting Started guide.
$ appmixer login your@user.com
$ appmixer publish appmixer.myservice.zip

If you prefer to go step by step instead (which we recommend), create the following files and the required directories:

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

  • myservice/mymodule/HelloAppmixer/component.json, component manifest file

  • myservice/service.json, service manifest file

The resulting directory structure should look like this:

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

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 be able to work with our component.

module.exports = {};

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 be a fully qualified namespace or our component:

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

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=="
}

Now you can pack and publish your component using the Appmixer CLI tool. When you then refresh the Designer page in your browser, you should see a new app in the left pane:

# Initialize your Appmixer CLI client if you haven't done that already:
$ npm install -g appmixer
$ appmixer url http://localhost:2200
# Use e.g. the same user as you signed-up with in the Getting Started guide.
$ appmixer login your@user.com

# Now we can pack and publish our component:
$ appmixer pack myservice
$ appmixer publish appmixer.myservice.zip

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 republish your component, refresh the Designer page and connect our component to another component, you should see both our properties in the Inspector:

$ appmixer pack myservice
$ appmixer publish myservice.zip

Note that we're using the Controls/OnStart component in our flow below. This utility component is useful especially during development of your custom components as the only thing it does is that it triggers/fires as soon as we start our flow.

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 Insights/Logs page (click on the triple dot and then go to Insights):

This is because we have defined an input port on our component but did not yet implement the receive() method.

Sending HTTP Requests

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. We will call an 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 be done by creating a package.json file with the Node package manager and installing our library:

$ cd 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 => {
                return context.sendJson({
                    mydata: 'Received from Postman echo: ' + JSON.stringify(response.data.args)
                }, 'out');
            });
    }
};

Notice the context.messages 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.

Now we can republish our component again and create a flow like this:

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

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

Conclusion

In this tutorial, we demonstrated 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 consume. 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.

Last updated