Actionhero Key Concepts


Introduction


Actionhero is an API server. The type of workload Actionhero excels at involves producing and consuming APIs, storing and retrieving data from databases, modifying files, and similar jobs. Actionhero has 5 key concepts that make up each application: Actions, Tasks, Initializers, Chat, and Servers. This page will contain a brief overview of these key concepts, and provide a like to the related tutorial which contains more in-depth information.

If you are coming from a framework like Rails that has a strong focus on the MVC (model - view - controller) pattern, you might be surprised to see that those concepts are missing from Actionhero. That is because Actionhero is a backend-agnostic API framework.

"Backend-agnostic" means that Actionhero can work with any backend/storage engine you might want to use. Since "models" are so closely tied to the storage engine around them, Actionhero doesn't have any opinions on how they should work. That said, there are many Actionhero plugins to help with that! One of the most popular is ah-sequelize-plugin which is a great way to introduce traditional database-backed models & migrations with MySQL or Postgres.

"API framework" means that by default, Actionhero only speaks JSON over HTTP(S) or websockets. It doesn't render HTML or any other type of "view" meant to be consumed by a human. Again, there are plugins to introduce new protocols to Actionhero if you want them, but they are optional. Actionhero is not a font-end server, although it's easy to pair Actionhero with Next.JS, for example, to also render a website from the same application. Actionhero focuses on the parts of the stack at the "controller" level - how to smoothly speak to clients over multiple protocols, handle caching, background jobs, and real-time communication.

Actionhero was built from the ground up to include all the features you expect from a modern API framework. Written in Typescript, Actionhero makes it easy to build a modern API server with ES6 features like Async/Await... and it also knows when to get out of the way so you can customize your stack to fit your needs.


Actions


import { Action } from "actionhero";

export class RandomNumber extends Action {
    constructor() {
    super();
    this.name = "randomNumber";
    this.description = "I am an API method which will generate a random number";
    this.outputExample = { randomNumber: 0.123 };
  }

  async run({ connection }) {
    const randomNumber = Math.random();
    const stringRandomNumber = connection.localize([
      "Your random number is {{ randomNumber }}",
      {randomNumber},
    ]);

    return { randomNumber, stringRandomNumber };
  }
}

Actions are the main way that you interact with your clients - responding to their requests and performing actions for them. An Action exists to read a connection's request information (which we call params), preform some operation (perhaps reading or writing to a database), and then finally responding to that request with a response. When you think of an API in the most general sense, Actions are probably what you are thinking of. Actions are most like traditional "controller" objects from an MVC framework... but they work for all connection types like web, websocket, etc. Actions are a uniform way to define what methods your API exposes to any client that wants to access it.

Actions can have middleware to help with things like authentication or logging. Actions are generally stateless, and throw errors if something goes wrong (user not found or you aren't signed in). In general actions are short, and don't have much business logic. They rely on other objects for the business logic, perhaps in your models, or service objects you've created in other parts of your application.

Actions rely on servers to handle routing requests to them, and to format responses.


Tasks


import { Task } from "actionhero";
import { sendWelcomeEmail } from "./../serviceObjects/email";

export class SendWelcomeMessage extends Task {
  constructor() {
    super();
    this.name = "SendWelcomeEmail";
    this.description = "I send the welcome email to new users";
    this.frequency = 0;
    this.queue = "high-priority";
  }

  async run(data) {
    await sendWelcomeEmail({ address: data.email });
    return true;
  }
}

Tasks are background jobs. Tasks can be either enqueued by an Action or another Task, or they can be recurring, and run every few minutes, hours, or days. Actionhero is "cluster-aware", which means that it knows how to distribute tasks between many servers, ensure that only one is running at a time, and how to retry them when something go wrong. Tasks can be enqueued to run ASAP, or delayed until a specific time in the future.

import { task } from "actionhero";

// Enqueue the task now, and process it ASAP
await task.enqueue("sendWelcomeEmail", { to: "evan@evantahler.com" });

// Enqueue the task now, and process it once `timestamp` has ocurred
await task.enqueueAt(10000, "sendWelcomeEmail", { to: "evan@evantahler.com" })

When working with a third-party API or doing a particularly slow operation, it's probably a good idea to use a Task so your users do not need to wait. Also, if some operation might fail and you want to retry it, a Task again would be a good choice.

A good task is short-lived and idempotent. Tasks that deal with complex workflows can enqueue other Tasks, store state in a database or elsewhere in your application, like Actionhero's built-in cache.

Under the hood, Actionhero uses the node-resque package to manage tasks. If you want a user-interface to visually inspect your task queues, check out the ah-resque-ui Actionhero plugin. just like Actions, middleware can be used to help with Task retrying, error handling, unique-jobs, and more.


Initializers


import { Initializer, api, log } from "actionhero";
import { Database } from "../classes/database";

export class DatabaseInit extends Initializer {
  constructor() {
    super();
    this.name = "DatabaseInit";
  }

  async initialize() {
    await Database.connect();
  }

  async start() {
    await Database.migrate();
    await Database.check();
  }

  async stop() {
    await Database.disconnect();
  }
}

Initializers are how your server connects to databases and other APIs. Initializers hook into the Actionhero server's lifecycle methods, (initialize, start, and stop), and provide a great place to run any code you need. This is also a great place to do per-server chores, like clearing a disk cache or compressing files. For example, the ah-sequelize-plugin connects to your Postgres or MySQL server in the initialize phase, runs migrations in the start phase, and disconnects at the stop phase.


Chat


// from a connected websocket client
client.roomAdd("public-chat-room");
client.say("public-chat-room", "Hello everyone")

client.on('message', (message) => {console.log(message)})

Actionhero provides a robust cluster-ready chat system. "Chat" doesn't just mean human-to-human communication, but rather any client-to-client and client-to-server communication that you want to happen in real time. This can be sharing live updates to a web page, game data about other players or the state of the world, and of course, human-to-human chat!

The chat system is available to use both by the server, and by clients.

// or, from the srver
chatRoom.broadcast({}, "public-chat-room", "welcome to the room");

Just like Actions, middleware can be used to help with chat room presence, authentication, and more. Try an example of the chat here.


Servers


Actionhero is unique in that it allows you to build or add many types of servers into one application. Not only can you support HTTP and websockets, but you can add custom protocols like Quick and Protobuf to your application and easily reuse your Actions!

Servers handle incoming connections, and routing their requests to actions or the chat system. There are a number of unique use-cases where a server might be good way to interact with other real-time APIs, like consuming the streaming Twitter API or custom responses from IOT or embedded devices.


Testing


import { Process, specHelper } from "actionhero";

const actionhero = new Process();

describe("Action", () => {
  describe("randomNumber", () => {
    beforeAll(async () => {
      await actionhero.start();
    });

    afterAll(async () => {
      await actionhero.stop();
    });

    let firstNumber = null;

    test("generates random numbers", async () => {
      const { randomNumber } = await specHelper.runAction("randomNumber");
      expect(randomNumber).toBeGreaterThan(0);
      expect(randomNumber).toBeLessThan(1);
      firstNumber = randomNumber;
    });

    test("is unique / random", async () => {
      const { randomNumber } = await specHelper.runAction("randomNumber");
      expect(randomNumber).toBeGreaterThan(0);
      expect(randomNumber).toBeLessThan(1);
      expect(randomNumber).not.toEqual(firstNumber);
    });
  });
});

Actionhero would not be a complete framework unless it included a convenient way to write tests for the above key concepts. Actionhero comes with a specHelper which includes ways to easily mock Actions and Tasks.

Actionhero will generate Jest tests for each new Action and Task that you generate. Actionhero's configuration is NODE_ENV-aware, and makes it simple to change your database configurations between environments.


Up Next

Tutorials

Solutions

Actionhero was built from the ground up to include all the features you expect from a modern API framework.

Open Source


The Actionhero server is open source, under the Apache-2 license


Actionhero runs on Linux, OS X, and Windows


You always have access to the Actionhero team via Slack and Github



Premium Training & Review


We provide support for corporate & nonprofit customers starting at a flat rate of $150/hr. Our services include:


  • Remote training for your team
  • Code Reviews
  • Best Practices Audits
  • Custom plugin & Feature Development

We have packages appropriate for all company sizes. Contact us to learn more.


Premium Training & Review


We provide support for corporate & nonprofit customers starting at a flat rate of $150/hr. Our services include:


  • Remote training for your team
  • Code Reviews
  • Best Practices Workshops
  • Custom plugin & Feature Development

We have packages appropriate for all company sizes. Contact us to learn more.


Enterprise


For larger customers in need of a support contract, we offer an enterprise plan including everything in the Premium plan plus:


  • 24/7 access to core members of the Actionhero Team
  • Emergency response packages
  • Deployment support
  • ...and custom development against Actionhero’s core as needed.