Phase 2 - Core Concepts | Post 3 | The ServiceBroker — The Heart of Moleculer

Post 3 of 15 | Phase 2: Core Concepts


The ServiceBroker — The Heart of Moleculer

In the previous post you created a project, ran it, and wrote your first service. You saw broker.start() and moleculer.config.js but we did not go deep into what the broker actually is and how it works. This post covers that completely.

Understanding the broker well is the most important thing in this entire course. Every other concept — services, actions, events, transporters — all sit on top of the broker. If you understand the broker, everything else becomes easy.


What is the ServiceBroker?

Think of a large company. The company has multiple departments — HR, Finance, Engineering, Sales. Each department does its own job. But there is a central management office that:

  • Knows which departments exist
  • Routes requests to the right department
  • Manages communication between departments
  • Handles failures when a department is unavailable
  • Keeps logs of everything happening

The ServiceBroker is exactly that central management office for your Moleculer application. Every service registers itself with the broker. Every action call goes through the broker. Every event passes through the broker. Nothing happens in Moleculer without the broker knowing about it.


Creating a Broker — Three Ways

Way 1: Default broker with no configuration


    const { ServiceBroker } = require("moleculer");

    const broker = new ServiceBroker();

This creates a broker with all default settings. Logger is enabled, no transporter, no caching. Fine for quick experiments.

Way 2: Inline configuration


    const { ServiceBroker } = require("moleculer");

    const broker = new ServiceBroker({
        nodeID: "my-node-1",
        logLevel: "info",
        transporter: null
    });

You pass a configuration object directly when creating the broker.

Way 3: Using moleculer.config.js (recommended for real projects)

This is what the CLI generates. The configuration lives in a separate file and moleculer-runner reads it automatically when you run npm start or npm run dev.


    // moleculer.config.js
    module.exports = {
        nodeID: "my-node-1",
        logLevel: "info",
        transporter: null
    };

In real projects you always use Way 3. Ways 1 and 2 are for quick scripts and learning purposes.


Understanding moleculer.config.js

Open the moleculer.config.js file in your my-project folder. It looks like this:


    "use strict";

    module.exports = {
        namespace: "",
        nodeID: null,

        metadata: {},

        logger: {
            type: "Console",
            options: {
                colors: true,
                moduleColors: false,
                formatter: "full",
                objectPrinter: null,
                autoPadding: false
            }
        },

        logLevel: "info",

        transporter: null,

        cacher: null,

        serializer: null,

        requestTimeout: 10 * 1000,

        retryPolicy: {
            enabled: false,
            retries: 5,
            delay: 100,
            maxDelay: 1000,
            factor: 2,
            check: err => err && !!err.retryable
        },

        maxCallLevel: 100,

        heartbeatInterval: 10,
        heartbeatTimeout: 30,

        contextParamsCloning: false,

        tracking: {
            enabled: false,
            shutdownTimeout: 5000,
        },

        disableBalancer: false,

        registry: {
            strategy: "RoundRobin",
            preferLocal: true
        },

        circuitBreaker: {
            enabled: false,
            threshold: 0.5,
            minRequestCount: 20,
            windowTime: 60,
            halfOpenTime: 10 * 1000,
            check: err => err && err.code >= 500
        },

        bulkhead: {
            enabled: false,
            concurrency: 10,
            maxQueueSize: 100,
        },

        validator: true,

        errorHandler: null,

        metrics: {
            enabled: false
        },

        tracing: {
            enabled: false
        },

        middlewares: [],

        replCommands: null,

        created(broker) { },
        started(broker) { },
        stopped(broker) { },
    };

This looks like a lot but you do not need to understand everything right now. Let us go through the important ones one by one.


Key Configuration Options Explained

nodeID


    nodeID: null

Every broker instance has a unique ID called nodeID. When set to null, Moleculer auto-generates one using your hostname and a random number, for example my-pc-3721.

The nodeID matters when you run multiple instances of your application on different machines. Each machine needs a unique nodeID so the broker knows which node is which. For local development, auto-generate is fine.

namespace


    namespace: ""

Think of namespace as a room name. If you have multiple Moleculer applications running on the same network, they can accidentally talk to each other. Setting a namespace isolates them.

For example:

// App 1
namespace: "ecommerce"

// App 2
namespace: "blog"

These two apps will never interfere with each other even if they share the same transporter.

logLevel


    logLevel: "info"

Controls how much the broker prints to the console. Available levels from most to least verbose:

  • trace — prints everything, very noisy
  • debug — prints debugging details
  • info — prints normal operational messages (recommended for development)
  • warn — prints only warnings and errors
  • error — prints only errors
  • fatal — prints only fatal errors
  • silent — prints nothing

During development use info. In production use warn or error.

requestTimeout


    requestTimeout: 10 * 1000

This is 10000 milliseconds, meaning 10 seconds. If you call an action and it does not respond within 10 seconds, Moleculer automatically throws a RequestTimeoutError. This prevents your application from hanging forever waiting for a response.

You can override this per call as well, which we will see later.

transporter


    transporter: null

When null, all services run in the same process and communicate directly in memory. No network involved. This is perfect for development.

When you want services on different machines to communicate, you set a transporter here, for example NATS or Redis. We cover this in Post 7.

validator


    validator: true

When true, Moleculer automatically validates action params using the fastest-validator library. This is what makes the params schema work in your service actions. Keep this true always.

retryPolicy


    retryPolicy: {
        // Enable feature
        enabled: false,
        // Count of retries
        retries: 5,
        // First delay in milliseconds.
        delay: 100,
        // Maximum delay in milliseconds.
        maxDelay: 1000,
        // Backoff factor for delay. 2 means exponential backoff.
        factor: 2,
        // A function to check failed requests.
        check: err => err && !!err.retryable
    }

When enabled, if an action call fails, Moleculer automatically retries it. retries is how many times to retry. delay is the initial wait in milliseconds between retries. factor means each retry waits longer — first retry waits 100ms, second waits 200ms, third waits 400ms, and so on. We cover this in depth in Post 8.

circuitBreaker


    circuitBreaker: {
        // Enable feature
        enabled: false,
        // Threshold value. 0.5 means that 50% should be failed for tripping.
        threshold: 0.5,
        // Minimum request count. Below it, CB does not trip.
        minRequestCount: 20,
        // Number of seconds for time window.
        windowTime: 60,
        // Number of milliseconds to switch from open to half-open state
        halfOpenTime: 10 * 1000,
        // A function to check failed requests.
        check: err => err && err.code >= 500
    }

The circuit breaker is a fault tolerance mechanism. If a service keeps failing, the circuit breaker stops sending requests to it temporarily and returns an error immediately instead of waiting for a timeout. We cover this in Post 8.

registry.strategy


    registry: {
        // Define balancing strategy. More info: https://moleculer.services/docs/0.15/balancing.html
        // Available values: "RoundRobin", "Random", "CpuUsage", "Latency", "Shard"
        strategy: "RoundRobin",
        // Enable local action call preferring. Always call the local action instance if available.
        preferLocal: true
    },

When you have multiple instances of the same service running, the broker uses a load balancing strategy to decide which instance handles each request. RoundRobin means requests are distributed evenly, one by one to each instance in turn. preferLocal means if a service is available on the same machine, prefer that over a remote one.


The Broker Lifecycle

The broker has a clear lifecycle with three stages. Understanding this is important when you need to do things like connect to a database when the app starts, or clean up resources when it stops.

Stage 1: created

This runs immediately when the broker object is created, before any services start. Use this for very early initialization.

Stage 2: started

This runs after all services have started and the broker is fully ready to handle requests. This is where you put code that needs the broker to be ready — like seeding initial data or connecting to external services.

Stage 3: stopped

This runs when the broker is shutting down. Use this for cleanup — closing database connections, flushing logs, etc.

In moleculer.config.js these are defined as:


    created(broker) {
        console.log("Broker created");
    },

    started(broker) {
        console.log("Broker started, ready to handle requests");
    },

    stopped(broker) {
        console.log("Broker stopped, cleaning up");
    }

Each service also has its own lifecycle hooks which we cover in Post 4.


The Broker Lifecycle in a Service

Services also participate in the broker lifecycle through their own hooks:


    module.exports = {
        name: "greeter",

        created() {
            // Runs when the service is created
            // broker is not yet started here
            this.logger.info("Greeter service created");
        },

        async started() {
            // Runs when the broker starts
            // Safe to call other services from here
            // Good place to connect to database
            this.logger.info("Greeter service started");
        },

        async stopped() {
            // Runs when the broker stops
            // Good place to close connections
            this.logger.info("Greeter service stopped");
        },

        actions: {
            hello(ctx) {
                return `Hello ${ctx.params.name}!`;
            }
        }
    };

Notice this.logger inside the service. Every service automatically gets a logger instance from the broker. You do not need to import or configure it. Just use this.logger.info(), this.logger.warn(), this.logger.error() anywhere inside a service.


Customizing the Logger

The default logger prints to the console. You can customize the format in moleculer.config.js:


    // Enable/disable logging or use custom logger. More info: https://moleculer.services/docs/0.15/logging.html
    // Available logger types: "Console", "File", "Pino", "Winston", "Bunyan", "debug", "Log4js", "Datadog"
    logger: {
        type: "Console",
        options: {
            // Using colors on the output
            colors: true,
            // Print module names with different colors (like docker compose for containers)
            moduleColors: false,
            // Line formatter. It can be "json", "short", "simple", "full", a `Function` or a template string like "{timestamp} {level} {nodeID}/{mod}: {msg}"
            formatter: "full",
            // Custom object printer. If not defined, it uses the `util.inspect` method.
            objectPrinter: null,
            // Auto-padding the module name in order to messages begin at the same column.
            autoPadding: false
        }
    }

The formatter option controls the log line format. Options are:

  • full — shows timestamp, level, nodeID, service name, and message. Best for development.
  • short — shorter format, less information.
  • simple — minimal format, just level and message.
  • json — outputs logs as JSON objects. Best for production log aggregation tools.

For development, full with colors is the most readable.


Running Multiple Brokers Locally (Preview)

Right now everything runs in one process with one broker. In real microservices you run each service as a separate process, possibly on separate machines. Each process has its own broker. They communicate via a transporter.

Here is a preview of what that looks like. Do not run this yet, just read it to understand the concept:


    // Process 1: runs user-service
    const broker1 = new ServiceBroker({
        nodeID: "node-user",
        transporter: "NATS"
    });
    broker1.loadService("./services/user.service.js");
    broker1.start();


    // Process 2: runs order-service
    // This is a completely separate Node.js process
    const broker2 = new ServiceBroker({
        nodeID: "node-order",
        transporter: "NATS"
    });
    broker2.loadService("./services/order.service.js");
    broker2.start();

Both brokers connect through NATS. The order service can call user.getUser and the broker automatically routes it to Process 1 over the network. From the developer's perspective the call looks exactly the same as a local call.

This is the power of Moleculer. The location of a service is transparent to the caller.


Practical Exercise

Open your my-project folder and make these changes to moleculer.config.js to understand how config changes affect behavior.

Change 1: Change logLevel to debug and restart the server. Notice how much more information appears in the console. Change it back to info after.

Change 2: Change requestTimeout to 3 * 1000. This sets a 3 second timeout. Restart and call any action. It still works because actions respond in milliseconds.

Change 3: Add your own started hook:


    async started(broker) {
        broker.logger.info("==== Application is ready ====");
    }

Restart and look for your message in the console output.

After each change, restore the file to its original state before moving on.


Summary

  • The ServiceBroker is the central manager of your entire Moleculer application.
  • Every service registers with the broker. Every call and event passes through it.
  • moleculer.config.js is the recommended place to configure the broker in real projects.
  • nodeID identifies each broker instance. Namespace isolates multiple apps on the same network.
  • logLevel controls console output verbosity. Use info for development.
  • requestTimeout prevents your app from hanging when a service does not respond.
  • transporter: null means all services run in one process. Setting a transporter enables multi-node communication.
  • The broker has three lifecycle stages: created, started, stopped.
  • Services also have their own lifecycle hooks: created, started, stopped.
  • Every service gets this.logger automatically from the broker.

Up Next

Post 4 goes deep into Services and Actions — the building blocks you write every single day in Moleculer. We will cover service schema in full detail, action parameters, validation, shorthand vs full action syntax, calling actions with options, and service mixins.


Course Progress: 3 of 15 posts complete.

Phase 1 - Foundation | Post 2 | Installation and Your First Working Moleculer Service

Post 2 of 15 | Phase 1: Foundation


Installation and Your First Working Moleculer Service

In the previous post you learned what Moleculer is and why it exists. In this post you will install everything, set up a proper project structure, and build your first working service. Every line of code will be explained.


What You Need Installed on Your Machine

Before writing any Moleculer code, confirm you have the following:

  • Node.js version 18 or above. Moleculer 0.15 requires minimum Node 18.
  • npm, which comes bundled with Node.js.
  • A code editor. VS Code is recommended.

Check your Node version:


    node -v

If it shows below v18, go to nodejs.org, download the LTS version, and install it.


Setting Up the Project

You have two ways to create a Moleculer project.

Way 1: Moleculer CLI (recommended)

The CLI scaffolds a complete project with proper folder structure, config file, and example services automatically.

Way 2: Manual setup

Create a folder, run npm init, install moleculer, write everything yourself.

We will start with the CLI because it reflects real-world usage. Then we will manually go through each generated file so you understand exactly what everything does.


Manual Setup — Step by Step

Step 1: Create a project folder


    mkdir moleculer-course
    cd moleculer-course
    npm init -y

Step 2: Install Moleculer


    npm install moleculer

That is it. No other dependency is needed to run your first service.

Step 3: Check your folder structure

After these steps your folder should look like this:


    moleculer-course/
        node_modules/
        package.json
        package-lock.json

Step 4: Create your first file

Create a file called index.js in the root of the project.


Writing Your First Service

Open index.js and write the following code. Read each comment carefully.


    // Step 1: Import ServiceBroker from the moleculer package
    const { ServiceBroker } = require("moleculer");

    // Step 2: Create a broker instance
    // The broker is the central manager of your entire Moleculer app
    // Think of it as the "boss" who knows about all services and routes calls
    const broker = new ServiceBroker();

    // Step 3: Create a service
    // A service is a module that does one specific job
    // This one is called "greeter" and it handles greeting-related tasks
    broker.createService({
        name: "greeter",   // This is the service name. Used when calling its actions.

        actions: {
            // An action is a function that can be called by other services or externally
            // This action is called "hello"
            // Full action name becomes: "greeter.hello"
            hello(ctx) {
                // ctx is the Context object
                // ctx.params contains whatever data was passed when calling this action
                const name = ctx.params.name;
                return `Hello, ${name}! Welcome to Moleculer.`;
            },

            // A second action on the same service
            // Full action name: "greeter.welcome"
            welcome(ctx) {
                const { firstName, lastName } = ctx.params;
                return `Welcome, ${firstName} ${lastName}!`;
            }
        }
    });

    // Step 4: Start the broker
    // broker.start() returns a Promise
    // The .then() runs after broker has fully started and all services are ready
    broker.start()
        .then(() => {
            // Step 5: Call an action
            // broker.call("serviceName.actionName", { ...params })
            // This returns a Promise that resolves with the action's return value
            return broker.call("greeter.hello", { name: "Rahul" });
        })
        .then(result => {
            console.log(result);
            // Output: Hello, Rahul! Welcome to Moleculer.
        });

Run it:


    node index.js

You should see output like this in your terminal:

[2025-05-12T10:00:00.000Z] INFO  broker/BROKER: Moleculer v0.15.0 is starting...

[2025-05-12T10:00:00.000Z] INFO  broker/BROKER: Node ID: my-pc-1234

[2025-05-12T10:00:00.000Z] INFO  broker/BROKER: Service 'greeter' registered.

Hello, Rahul! Welcome to Moleculer.

Your first Moleculer service is working.


Breaking Down What Just Happened

Let us go through each part carefully.

ServiceBroker


    const broker = new ServiceBroker();

This creates the central brain of your Moleculer app. When you call this with no arguments, it uses default settings — console logging, no transporter (everything runs in the same process), and auto-generated node ID.

createService

    
    broker.createService({ name: "greeter", actions: { ... } });

This registers a service with the broker. The broker now knows this service exists and can route calls to it. The name field is mandatory. Every service must have a unique name.

Actions and the ctx object


    hello(ctx) {
        return ctx.params.name;
    }

Every action receives one argument — the Context object, always named ctx by convention. The ctx.params object contains the data you passed when calling the action. You return a value from the action and the caller receives it as the result.

broker.call


    broker.call("greeter.hello", { name: "Rahul" })

This is how you call an action. First argument is the full action name in the format "serviceName.actionName". Second argument is the params object, which becomes ctx.params inside the action. It always returns a Promise.


Calling Multiple Actions

Update your index.js to call both actions:


    const { ServiceBroker } = require("moleculer");

    const broker = new ServiceBroker();

    broker.createService({
        name: "greeter",
        actions: {
            hello(ctx) {
                return `Hello, ${ctx.params.name}! Welcome to Moleculer.`;
            },
            welcome(ctx) {
                const { firstName, lastName } = ctx.params;
                return `Welcome, ${firstName} ${lastName}!`;
            }
        }
    });

    broker.start()
        .then(async () => {
            // Using async/await instead of chaining .then()
            // Both approaches work. async/await is cleaner for multiple calls.

            const result1 = await broker.call("greeter.hello", { name: "Rahul" });
            console.log(result1);

            const result2 = await broker.call("greeter.welcome", {
                firstName: "Rahul",
                lastName: "Sharma"
            });
            console.log(result2);
        });

Output:
Hello, Rahul! Welcome to Moleculer. Welcome, Rahul Sharma!

Using Multiple Services

In a real microservices project, you will have many services. Here is how two services look in the same broker, and how one service calls another.


    const { ServiceBroker } = require("moleculer");

    const broker = new ServiceBroker();

    // Service 1: math service
    broker.createService({
        name: "math",
        actions: {
            add(ctx) {
                return ctx.params.a + ctx.params.b;
            },
            multiply(ctx) {
                return ctx.params.a * ctx.params.b;
            }
        }
    });

    // Service 2: calculator service
    // This service calls the math service internally
    broker.createService({
        name: "calculator",
        actions: {
            // This action calls another service's action using ctx.call()
            // ctx.call() is the same as broker.call() but used from inside a service
            async compute(ctx) {
                const sum = await ctx.call("math.add", { a: ctx.params.x, b: ctx.params.y });
                const product = await ctx.call("math.multiply", { a: ctx.params.x, b: ctx.params.y });
                return {
                    sum,
                    product
                };
            }
        }
    });

    broker.start()
        .then(async () => {
            const result = await broker.call("calculator.compute", { x: 4, y: 5 });
            console.log(result);
            // Output: { sum: 9, product: 20 }
        });

Two important things to notice here.

First, inside a service action, you use ctx.call() to call other actions, not broker.call(). Both work, but ctx.call() is the correct way because it carries the request context forward, which matters for tracing and metadata later in the course.

Second, services in the same broker can call each other freely. The broker routes the call automatically. You do not need to know where the other service is running.


Installing the Moleculer CLI

Run this once globally on your machine:


    npm install -g moleculer-cli

Verify it installed correctly:


    moleculer --version


Creating Your Project

Navigate to wherever you keep your projects and run:


    moleculer init project my-project

The CLI will ask you a series of questions. Answer exactly as shown below:
Add HTTP API Gateway (moleculer-web) service? Yes
Add GraphQL Gateway? No
Add Socket.Io Gateway? No
Would you like to communicate with other nodes? No
Would you like to use cache? No
Add DB sample service? No
Add Moleculer Channels middleware? No
Add Moleculer Workflows middleware? No
Would you like to enable metrics? No
Would you like to enable tracing? No
Add Docker & Kubernetes sample files? No
Use ESLint to lint your code? No
Would you like to run npm install? Yes

The most important one is "Add DB sample service? No". If you select Yes here, it installs sqlite3 which is a native module that requires Visual Studio Build Tools to compile on Windows. Avoiding it keeps the setup simple for now. We will add our own database integration later in the course using MongoDB.

After npm install completes you should see no errors. Your terminal should end cleanly without any npm error lines.


Understanding the Generated Folder Structure

Open the my-project folder in VS Code. You will see this structure:

my-project/
  services/
    api.service.js
    greeter.service.js
  moleculer.config.js
  package.json
  package-lock.json
  node_modules/

Here is what each file does.

services/greeter.service.js

This is an example service. It has two actions called hello and welcome. This is where you will learn the structure of a real service file.

services/api.service.js

This is the API Gateway service. It exposes your internal Moleculer services over HTTP so the outside world can reach them via a browser or Postman. Think of it as the front door of your application.

moleculer.config.js

This is the central configuration file for your ServiceBroker. Logger settings, transporter settings, caching settings — all go here. We will go through this file in detail in Post 3.


Running the Project

Go into the project folder:

cd my-project

Start the project:

npm run dev

You should see output like this in your terminal:

[INFO]  broker/BROKER: Moleculer v0.15.0 is starting...
[INFO]  broker/BROKER: Node ID: my-pc-1234
[INFO]  broker/BROKER: Service 'greeter' registered.
[INFO]  broker/BROKER: Service 'api' registered.
[INFO]  broker/BROKER: ServiceBroker started successfully.
[INFO]  api/API-GW: API Gateway listening on http://0.0.0.0:3000

Your application is now running. The API Gateway is listening on port 3000.

Open your browser and go to:

http://localhost:3000/api/greeter/welcome?name=Rahul

You should see:

"Welcome, Rahul"

That response came from the greeter service, through the API Gateway, over HTTP. You just called a Moleculer action from a browser.


Understanding the Greeter Service File

Open services/greeter.service.js. It looks like this:


    "use strict";

    /**
     * @typedef {import('moleculer').ServiceSchema} ServiceSchema Moleculer's Service Schema
     * @typedef {import('moleculer').Context} Context Moleculer's Context
     */

    /** @type {ServiceSchema} */
    module.exports = {
        name: "greeter",

        /**
         * Settings. More info: https://moleculer.services/docs/0.15/services.html#Settings
         */
        settings: {},

        /**
         * Dependencies. More info: https://moleculer.services/docs/0.15/services.html#Dependencies
         */
        dependencies: [],

        /**
         * Actions. More info: https://moleculer.services/docs/0.15/actions.html
         */
        actions: {
            /**
             * Say a 'Hello' action.
             *
             * @returns
             */
            hello: {
                rest: {
                    method: "GET",
                    path: "/hello"
                },

                async handler() {
                    return "Hello Moleculer";
                }
            },

            /**
             * Welcome, a username
             *
             * @param {String} name - User name
             */
            welcome: {
                rest: "/welcome",
                params: {
                    name: "string"
                },

                /** @param {import('moleculer').Context<{name: String}>} ctx */
                async handler(ctx) {
                    return `Welcome, ${ctx.params.name}`;
                }
            }
        },

        /**
         * Events. More info: https://moleculer.services/docs/0.15/events.html
         */
        events: {},

        /**
         * Methods. More info: https://moleculer.services/docs/0.15/services.html#Methods
         */
        methods: {},

        /**
         * Service created lifecycle event handler
         * More info: https://moleculer.services/docs/0.15/lifecycle.html#created-event-handler
         * @this {import('moleculer').Service}
         */
        created() { },

        /**
         * Service started lifecycle event handler
         * More info: https://moleculer.services/docs/0.15/lifecycle.html#started-event-handler
         * @this {import('moleculer').Service}
         */
        async started() { },

        /**
         * Service stopped lifecycle event handler
         * More info: https://moleculer.services/docs/0.15/lifecycle.html#stopped-event-handler
         * @this {import('moleculer').Service}
         */
        async stopped() { }
    };


Let us go through each part.

module.exports

A Moleculer service file exports a plain JavaScript object. No class, no constructor, just an object. The broker reads this object and registers it as a service.

name

Every service must have a name. This is how other services and the broker identify it. The name greeter means its actions are called as greeter.hello and greeter.welcome.

settings

An optional object for service-level configuration. For example, default page size for a list action, or a base URL. We will use this in later posts.

actions

This is where all the callable functions live. Each key inside actions is one action.

rest

This tells the API Gateway how to expose this action over HTTP. method is the HTTP method, path is the URL path. So greeter.hello becomes GET /api/greeter/hello.

params

An optional validation schema. If you define params, Moleculer automatically validates incoming data before the handler runs. In the welcome action, name must be a string. If it is missing or wrong type, Moleculer throws a validation error automatically. No manual validation code needed.

handler

This is the actual function that runs when the action is called. It receives ctx which is the Context object. ctx.params contains the data passed to the action.


Writing Your First Service From Scratch

Now that you understand the structure, create a new file inside the services folder called math.service.js.


    "use strict";

    module.exports = {
        name: "math",

        actions: {
            add: {
                // Define what params this action expects
                params: {
                    a: "number",
                    b: "number"
                },
                handler(ctx) {
                    return ctx.params.a + ctx.params.b;
                }
            },

            subtract: {
                params: {
                    a: "number",
                    b: "number"
                },
                handler(ctx) {
                    return ctx.params.a - ctx.params.b;
                }
            }
        }
    };

Save the file. Because you are running npm run dev, Moleculer watches for file changes and reloads automatically. You do not need to restart the server.

You will see this in your terminal:

[INFO]  broker/BROKER: Service 'math' registered.

Now test it in your browser. But wait — you did not add a rest property to these actions. That means the API Gateway does not know how to expose them over HTTP yet.

There are two ways to call them now.

Way 1: Add rest to the actions

Update add action like this:


    add: {
        rest: {
            method: "GET",
            path: "/add"
        },
        params: {
            a: "number",
            b: "number"
        },
        handler(ctx) {
            return ctx.params.a + ctx.params.b;
        }
    },

Then visit:

http://localhost:3000/api/math/add?a=5&b=3

Response:

8

Way 2: Use the REPL console

Moleculer has a built-in interactive console called REPL. Stop the server with Ctrl+C and start it in REPL mode:

npm run repl

Now type this in the console:

call math.add {"a": 5, "b": 3}

Response:

>> 8

The REPL is extremely useful for testing actions quickly without needing HTTP or Postman. We will use it throughout the course.


How the Project Starts — Understanding package.json Scripts

Open package.json and look at the scripts section:

"scripts": {
    "dev": "nodemon services/**/*.service.js moleculer.config.js",
    "start": "moleculer-runner services",
    "repl": "moleculer-runner --repl services"
}

npm run dev uses nodemon to watch all service files and the config file. Any time you save a change, it restarts automatically. Use this during development.

npm start uses moleculer-runner which is Moleculer's own process manager. It reads moleculer.config.js and loads all service files from the services folder. Use this in production.

npm run repl starts the application with an interactive console attached. Use this for quick testing.


One Important Thing About Query Params and Numbers

When you call an action via HTTP GET with query params like ?a=5&b=3, the values arrive as strings, not numbers. So "5" + "3" would give you "53" instead of 8.

Moleculer's built-in param validation handles this automatically. When you declare params as number type, Moleculer coerces the string to a number before passing it to the handler. This is why defining params is important, not just for validation but also for type coercion.


Current Folder Structure

After adding the math service, your project looks like this:

my-project/
  services/
    api.service.js
    greeter.service.js
    math.service.js       <-- you created this
  moleculer.config.js
  package.json

This is the pattern you will follow throughout the course. One file per service, all inside the services folder. The broker automatically picks up any file matching *.service.js.


Summary

  • Use moleculer init project to scaffold a new project.
  • Always answer No to "Add DB sample service" on Windows to avoid the sqlite3 native compilation error.
  • A service file exports a plain object with name and actions.
  • Each action has an optional params schema, an optional rest definition, and a required handler function.
  • ctx.params holds the incoming data inside a handler.
  • The rest property on an action tells the API Gateway how to expose it over HTTP.
  • npm run dev for development with auto-reload.
  • npm run repl for quick action testing via the console.
  • Param type declarations handle both validation and type coercion automatically.

Up Next

Post 3 goes deep into the ServiceBroker — its configuration options, lifecycle hooks, logging settings, and how to read and customize moleculer.config.js. This is the foundation that everything else sits on.


Course Progress: 2 of 15 posts complete.

Phase 5 - Caching | Post 9 | Caching — Making Your Services Fast With Zero Extra Effort

Post 9 of 15 | Phase 5: Caching Caching — Making Your Services Fast With Zero Extra Effort In the previous post you learned how to keep your...