Blog Home

Working with Reaction 3.8

Reaction Commerce 3.8 represents a big shift in API architecture and requires a different way of developing and customizing the platform. This post will explain:

  • the various parts of the open source Reaction Commerce application and how they fit together
  • how to download and run Reaction Commerce 3.8 on your computer
  • how to run a Reaction Commerce service in development mode
  • how API plugins are distributed, installed, and registered
  • how you can add custom API plugins or make changes to stock API plugins

But the 3.8 release is just the latest in many big shifts that have propelled Reaction Commerce to the forefront of the headless commerce universe. So let's start with a brief history of how Reaction Commerce has evolved.

The Evolution of Reaction Commerce Architecture

1.0.0

Reaction Commerce 1.0 (and earlier) was a single Meteor app. Meteor is a client-server development platform built on top of Node.js. In 1.0, and subsequent releases leading up to 2.0, you could run a single app and it provided a DDP API, a file server, background workers, an administrative browser UI, a storefront browser UI, and identity services.

1.10.0

Reaction Commerce 1.10 introduced a new GraphQL API, still within the single Meteor app. This was the beginning of a move toward a focus on headless commerce.

2.0.0

By the time Reaction Commerce 2.0 was released, the GraphQL API was feature complete for storefront use. To demonstrate this, the Reaction Commerce platform now included an example storefront project, which ran as a separate service and used only GraphQL for data. For login, Reaction Commerce 2.0 introduced a Hydra service, which is a third-party OAuth service configured for Reaction.

In Reaction Commerce 2.0, the identity provider, the DDP API, the GraphQL API, and the administrative UI all still lived within a single Meteor app.

2.0.0 minor releases

Throughout the version 2 minor releases, the Reaction Commerce GraphQL API continued to expand toward full feature parity with the DDP API. Additionally, much work was done to better separate all of the code in preparation for further splitting to come, and to remove dependencies on the Meteor framework.

3.0.0

The Reaction Commerce 3.0 release was the first fully headless release with a feature complete GraphQL API. In this release, the GraphQL API was a Node.js project with no Meteor framework. The identity service and UI were split into a separate Meteor application, and the old Meteor app containing the administrative UI and DDP API were rebranded as "Reaction Admin".

3.7.0

After the 3.0 release, work continued in the minor releases to solidify the API plugin architecture. Beginning with Reaction Commerce 3.7.0, with improved tooling in 3.8.0, all API code was moved to NPM packages. This brings us up to the current state of things.

Current Reaction Commerce Architecture

In Reaction Commerce 3.8, here's how everything fits together.

At the center of everything, you have the headless GraphQL API. This is really just a combination of the api-core NPM package and 37 API plugin packages. That's if you're using a stock Reaction Commerce image. But actually it could be any combination of stock plugins, community plugins, and custom plugins that you create.

Beyond the API, we have two stock browser clients: the Example Storefront and Reaction Admin. Note that you don't have to use these; Reaction is headless, so you can build your own clients if you prefer. In fact, we expect that most organizations will want to heavily customize their storefront, which is why we call it an "example" project.

Most API requests need to be authenticated with a token. To support identity and OAuth, we have two additional services: Reaction Identity and Hydra. Note that Hydra is built, maintained, and supported by a different company named Ory. Reaction Identity is a lightweight Meteor app that adapts Meteor's built-in accounts system to work with the Hydra OAuth flows. Any changes you want to make to login, register, and password screens are made in the Reaction Identity project.

If you are using the stock "files" plugin, then the API service also acts as a file server for uploading and downloading files such as product images.

If you are using the stock "jobs" plugin, then the API service also runs code that works background jobs.

For running the whole platform on a single computer, for demos, testing, or development, there is a project called the Reaction Development Platform, which provides commands that allow you to easily start and stop all of the necessary services together.

Reaction API Core

Let's take a closer look at how the Reaction Commerce headless API works, by building one from scratch.

mkdir my-reaction-api
cd my-reaction-api
echo "12.14.1" >> .nvmrc
nvm use
# run nvm install if prompted
npm init -y
touch index.js

(Note: You can choose a higher version of Node.js, but 12.14.1 is the minimum.)

This will create a simple Node.js app. Now open this directory in your favorite code editor. For example, enter code . to open it in Visual Studio Code.
Reaction Commerce API packages assume that your project is using ECMAScript modules, so first edit package.json and add ”type”: “module”.
Also in package.json, add the following

"engines": {
  "node" : ">=12.14.1"
},

Add a start script in the scripts object:

"scripts": {
  "start": "node --experimental-modules --experimental-json-modules ./index.js"
},

Note: if you’re using Node.js 14+, the --experimental-modules flag is no longer necessary but --experimental-json-modules flag may still be needed
Then install the @reactioncommerce/api-core NPM package:

npm install @reactioncommerce/api-core

Edit the index.js file and paste in the following:

import { ReactionAPICore } from "@reactioncommerce/api-core";
import packageJson from "./package.json";
const api = new ReactionAPICore({
  version: packageJson.version
});
async function run() {
  await api.start();
}
run().catch((error) => {
  console.error(error);
  process.exit(1);
});

This is technically all you need to do to create a barebones Reaction Commerce API. Before we start it, though, you’ll need a MongoDB server running on the default port on localhost. The quickest way to do this is:

docker pull mongo:4.2.0
docker run -p 27017:27017 mongo:4.2.0 mongod \
  --oplogSize 128 --replSet rs0 --storageEngine=wiredTiger

With the database now running, you can enter npm start in the my-reaction-api directory and you should see some startup logging ending with “GraphQL listening at http://localhost:3000/graphql (port 3000)”
If you go to that URL in any browser, you should see a GraphQL Playground UI. But view the Docs on the right side of the screen and you’ll notice that there are only 3 operations available: ping, echo, and tick. These are simple test operations included with the api-core package, but most of Reaction Commerce is missing! That’s because the stock Reaction Commerce API is really a combination of 37 API plugins, which need to be installed and registered.

Registering a Plugin

To get an idea of what registering an API plugin entails, add this in the run function, above the api.start() call.

await api.registerPlugin({
  name: "test",
  functionsByType: {
    startup: [
      function testStartupFunction() {
        console.log("I am startup code for the test plugin.");
      },
    ],
  },
});

Press CTRL+C to stop the running API, and then npm start to start it again. It should now pick up our test plugin and you should see the startup logging. Hopefully this gives you an idea of how plugins work, but plugins can actually do much more than this, and we recommend that all plugins be separate packages that you can install with NPM. For more information, refer to https://github.com/reactioncommerce/api-core#plugins

A Better Way to Register Plugins

We saw above how you can call api.registerPlugin one or more times before calling api.start to register plugins. You could npm install any plugin packages, import them into index.js, and then pass each one in to api.registerPlugin, but there is a simpler, more declarative way.
The recommended way to add plugins to a Reaction Commerce API project is by listing them in a JSON file:

  1. Create a file plugins.json alongside index.js
  2. npm install each plugin package you need
  3. List all plugins in the plugins.json file, which is an object where each key is any unique identifier for the plugin and each value is the plugin package name, for example, @reactioncommerce/api-plugin-carts
  4. In index.js, import and use the importPluginsJSONFile function from api-core
async function run() {
  const plugins = await importPluginsJSONFile("./plugins.json");
  await api.registerPlugins(plugins);
  await api.start();
}

Note that if you want to experiment with a plugin without yet creating a separate package for it, the keys in the plugins.json file can also be a relative path to a local ES module file that exports the package configuration.

Stock Reaction Commerce

As we’ve seen, you can build a Reaction Commerce API from scratch with your own mix of plugins. However, if you have simple needs or just want to try it out, it’s unlikely that you’ll need to do that. There’s an easier way!

The reaction GitHub repo is a Node.js project in which we’ve already installed and registered a particular “blessed” set of API plugins. This is the API configuration that Reaction Commerce maintainers test against, and which we believe to be the most useful for most use cases. So you can start by simply cloning or forking that repository if you know how to do so.

In fact, this stock Reaction Commerce configuration is also published as a Docker image, so you can very easily install and run it on any computer or on Docker-based cloud hosting services, too.

But what about all of the related services and UI applications? As explained in the “Current Reaction Commerce Architecture” section, Reaction is actually made up of several different services. How do you run the whole system locally?

Well, actually all Reaction Commerce services are published as Docker images, so all you need to do is pull, configure, and run them, with proper connections among them. For an easy way to do this, there is the Reaction Development Platform, which is where most people, whether you are looking to try, demo, test, or develop Reaction Commerce, will want to start.

Reaction Development Platform

The Reaction Development Platform is actually just a Makefile, or a series of pre-written commands, that help you easily work with any combination of Reaction Git repositories and Reaction Docker images. It supports two modes for each service: a standard mode that runs a service from its published Docker image, or a development mode that runs a service from a local checkout of its source code, allowing you to make changes and see the effects of them.

Here’s how to use the Reaction Development Platform in the most basic way, suitable for trials and demos:

git clone https://github.com/reactioncommerce/reaction-development-platform.git
cd reaction-development-platform
make

This will use docker-compose commands to pull and run the latest “blessed” configuration of all Reaction Commerce services.

The make command takes a bit of time, especially the first time you run it, so be patient. After it finishes, it will look like not much has happened, but there are a few things you can do to confirm everything worked.

ls -l

This will list files and directories in the platform directory. You should see that subfolders now exist for each Reaction Commerce service.

docker ps

This command will list all currently running Docker containers. You should see 8 or 9 running containers. (The hydra-migrate container intentionally runs for only a brief period and then stops itself.)

docker-compose logs -f

If you change to one of the service subdirectories, you can run this command to see and tail the container logs, which should show you that the service is running, or starting up.

In the reaction folder (API), you’ll want to run docker-compose logs -f api to avoid having the MongoDB logs mixed in.
On some operating systems, Docker is installed with a dashboard UI that can be an easier way to view the logs and confirm the container statuses. From the Docker menu, select Dashboard.

Initial Setup

After you check Docker Dashboard or otherwise confirm that all the services started without errors, there are two main things to do for initial setup:

  • Register a user
  • Create a shop

To register a user, go to http://localhost:4080 in any browser. This is the Reaction Admin UI for the locally running instance. You should be immediately redirected to a different URL, which is a login page.
Choose to register a new user, and enter an email address and password. Because this will be the first user account created, it will be automatically added to the System Administrator authorization group, which has permission to do things like manage other users’ permissions and create new shops.

You will be redirected back to the Reaction Admin UI and you will now be logged in. Because there is not yet a shop, you should see a Create Shop form. Simply enter a name for the shop, and you’re in! (You can edit other shop settings with the Settings forms after.)

Switching to Development Mode

Now let’s say you want to make some changes to a service and be able to see those changes reflected in the running service. By default, any changes you make do not affect the running service because all services run in standard mode. It’s done this way because it is much faster to start a service in standard mode. To develop a service, you need to switch it to development mode.
Development mode differs from standard mode in the following ways:

  • A generic Node.js development Docker image is used for the container rather than the published service image.
  • Your locally checked out project files, other than those under node_modules, are mirrored into the container by way of a Docker volume mount.
  • NPM packages are installed when the container starts and therefore reflect the project’s package.json file. (In other words, you can add additional NPM dependencies and then stop and restart the container.)
  • In the container, the project runs within nodemon, which will restart the app whenever you change any file in the project.

Switching a project to development mode is easy.

  1. Start the whole system in standard mode using make command in the Reaction Development Platform directory.
  2. Run make dev-<project folder name> for one or more services to restart them in development mode. For example, make dev-reaction if you want to make API changes.

But what exactly is this doing? Hopefully you don’t need to concern yourself with the details, but for those who want to know or who run into troubles, here’s the breakdown:

  • Every project repo has two Docker Compose configuration files: docker-compose.yml for standard mode and docker-compose.dev.yml for dev mode. The dev mode file is intended to extend the standard file, so it isn’t a complete configuration.
  • There is a feature built in to Docker Compose that looks for a docker-compose.override.yml file and uses it to extend docker-compose.yml whenever you run docker-compose commands.
  • The make dev-* commands stop the service if necessary, symlink docker-compose.dev.yml to docker-compose.override.yml in that service’s project folder, and then restart the service with the dev override now in effect. Conversely the make command and other non-dev-mode make subcommands always remove the docker-compose.override.yml symlink before running the service, ensuring that it will revert to standard mode.

To put that another way, this sequence of commands:

make dev-reaction
cd reaction

is equivalent to this sequence of commands:

cd reaction
docker-compose down
ln -sf docker-compose.dev.yml docker-compose.override.yml
docker-compose pull
docker-compose up -d

Developing API Plugins

Everything discussed so far has been true since the 3.0.0 release or earlier. With the 3.7.0 release, though, developing the API is slightly more complicated because almost all API code actually lives in NPM plugins, each of which lives in its own GitHub repository. You may be familiar with the npm link command as a way of temporarily linking NPM package code into a project to test before publishing it, but unfortunately npm link doesn’t work easily with code running inside a Docker container.

Initially, the solution was to use temporary Docker volume links to map NPM package code from the host machine into the node_modules directory in the API container. However, this had a number of rather severe down sides, including the fact that the node_modules folder in the linked plugin project folder on the host machine would often get completely deleted.
So Reaction Commerce 3.8.0 introduces two new scripts that implement a smarter way of linking in plugin packages (or any NPM packages): bin/package-link and bin/package-unlink.

But before we discuss the linking approach, let’s talk about how to clone the built-in plugin packages in the first place. There are nearly 40 API plugins. That’s a lot of repositories to clone, and it can be helpful to clone them all so that you can run searches across the full codebase. Fortunately, there is a quick way to do that, too:

make clone-api-plugins

When you run this command in the Reaction Development Platform directory, it will clone every built-in plugin into an api-plugins subfolder, alongside the service subfolders. You can then modify files in these plugins as necessary and link them into the API project to test them.

So back to the linking scripts. Let’s say you think there’s a bug in the built-in carts plugin. You cloned all the plugins and then changed a file in api-plugins/api-plugin-carts directory in an attempt to fix the bug. Now the first thing to do is to put the API in development mode if you haven’t already. After that, linking in your local carts plugin code is as simple as this:

cd reaction
bin/package-link @reactioncommerce/api-plugin-carts

The already-running API server will automatically restart to pull in your changes.

If your fix wasn’t quite correct and you make more changes to files in the carts plugin, you’ll have to run the link command again:

bin/package-link @reactioncommerce/api-plugin-carts

If necessary, you can run the link command for other plugins as well. You can even run it for other NPM packages that are not API plugins, but then you’ll need an additional argument that is the relative or absolute path to the package code on your host machine. For example:

bin/package-link some-other-package ../../some-other-package

(You can also use the code path argument for API plugin linking if you have cloned your API plugins to a non-standard location.)

When you’re done, be sure to unlink before stopping the API service or running make stop:

bin/package-unlink @reactioncommerce/api-plugin-carts

Note that there is currently a [bug](https://github.com/reactioncommerce/reaction/issues/6248] in the npm unlink command that in turn causes our bin/package-unlink command to unlink ALL linked packages instead of just the one you specify. Because of this you’ll need to relink any that you still wanted linked after you unlink one.

This linking approach works pretty well but has the potential to get the API into a state where it complains about missing dependencies and won’t start. If this happens and restarting the API service does not fix it, you will need to use the docker volume rm command to delete the API node_modules volume (usually named something like reaction_api_node_modules). If that doesn’t work, running docker-compose down -v in the reaction directory will work, but be careful because that command will also wipe out your local MongoDB database.

comments powered by Disqus