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.

No comments:

Post a Comment

Phase 4 - Fault Tolerance | Post 8 | Fault Tolerance — Keeping Your App Alive When Things Break

Post 8 of 15 | Phase 4: Fault Tolerance Fault Tolerance — Keeping Your App Alive When Things Break In every post so far we have assumed that...