# @imqueue vs Moleculer: two takes on Node.js microservices

Source: https://imqueue.org/blog/imqueue-vs-moleculer/
Published: 2026-06-19
Updated: 2026-07-29
Author: Andrii Glushko — Maintainer, @imqueue (https://github.com/creomobile)

**Moleculer is a batteries-included microservices framework; `@imqueue` is a focused, TypeScript-first RPC layer.** Moleculer gives you a service broker with pluggable transporters, built-in discovery, load-balancing strategies, circuit breakers and an API gateway. `@imqueue` gives you self-describing service classes over a message queue and a typed client generated from them. Moleculer offers more out of the box; `@imqueue` offers less to configure and stronger static typing. Both are actively maintained, so this is a fit question, not a health one.

(Details about Moleculer reflect its documented behavior at the time of writing; check its docs for the current state.)

## Transport and topology

Moleculer abstracts the transporter: pick NATS, Redis, AMQP, Kafka or TCP and the broker handles the rest, including a registry so nodes discover each other and a choice of balancing strategies. That flexibility is genuinely useful if you already run NATS, or expect to switch buses later.

`@imqueue` takes a simpler route: a service consumes from its own named queue, and multiple instances compete on that queue — so you get balancing without a balancer and without a discovery layer. The queue name is the address. Fewer moving parts to run, at the cost of the transport-and-registry flexibility Moleculer offers. It is Redis only: `vendor` defaults to `'Redis'` and is currently the only supported value, with `IMessageQueue` as the documented seam for another adapter.

## The same service, both ways

Moleculer services are broker-registered definitions with actions, called by name:

```javascript
// service
broker.createService({
  name: 'user',
  actions: {
    get(ctx) {
      return this.findById(ctx.params.id);
    },
  },
});

// caller — 'user.get' is a runtime string; params validated at runtime
const user = await broker.call('user.get', { id: '42' });
```

`@imqueue` services are classes, and the client is generated from them:

```typescript
import { IMQService, expose } from '@imqueue/rpc';

export class User extends IMQService {
    /**
     * Returns a user by id
     *
     * @param {string} id - user identifier
     * @return {Promise<{ id: string; name: string } | null>}
     */
    @expose()
    public async get(id: string): Promise<{ id: string; name: string } | null> {
        return { id, name: 'Jane Doe' };
    }
}
```

```bash
imq client generate User ./src/clients
```

```typescript
import { user } from './clients/index.js';

const client = new user.UserClient({ callTimeout: 5000 });
await client.start();

const found = await client.get('42'); // signature came from the service
```

The difference isn't decorators versus objects — it's `broker.call('user.get', …)` versus `client.get(…)`. One is a string the compiler can't check; the other is a method whose signature was derived from the service. Rename the action in Moleculer and callers keep compiling until something fails at runtime; rename it in `@imqueue`, regenerate, and every call site breaks the build.

## Typing model: Moleculer's runtime validation vs `@imqueue`'s generated types

The typing model is the sharpest difference between Moleculer and `@imqueue`, and worth being precise about. Moleculer is JavaScript-first and ships official typings; action parameters are typically validated at runtime with a schema validator, which gives you real runtime safety and good error messages. What it doesn't give you is a static contract across the call boundary — that's something you assemble.

`@imqueue` is TypeScript-first by design. The trade is that JSDoc is load-bearing rather than optional: it is the *only* type source the generator reads, so an unannotated parameter silently becomes `any`, the `@param` count must match real arity, and consuming projects must compile with `removeComments: false`. Cheap once you know it; confusing if you don't.

Neither model catches a *running* peer drifting from the client you generated last month — that's what [versioning your service contracts](https://imqueue.org/blog/versioning-microservices-without-breaking-callers/) is for either way.

## Resilience features, and one thing not to assume

Moleculer ships a large surface as first-class, configured features: circuit breakers, bulkheads, retries, timeouts, fallbacks, request tracing, metrics, caching, an API gateway and mixins. If you want those without assembling them, that's a real advantage.

`@imqueue` keeps the core small — reliable queueing, RPC, typed client generation, delayed messages, optional caching and locking decorators — and leans on `@imqueue/cli` for scaffolding, CI wiring and fleet management. Some resilience comes free from the transport rather than from features: if a callee is down the request waits on its queue instead of failing, so there's less need for a breaker on that particular failure mode, and services can start in any order.

One thing to get right, because it's the easiest wrong assumption to make: **`@imqueue`'s `@lock()` decorator is in-process only.** It coalesces concurrent identical calls within one process. Separate processes, cluster workers and service replicas each keep their own locks and will run the guarded code concurrently. If you need mutual exclusion across replicas, use a Redis- or database-backed lock. Don't read `@lock()` as a distributed primitive.

Similarly, safe delivery is narrower than it sounds: it re-queues a message a dying worker never *started*, and delivery is **at-least-once** in either mode, so exposed methods should be idempotent. Nothing in the framework drains in-flight work on shutdown — see [graceful shutdown and zero-drop deploys](https://imqueue.org/blog/graceful-shutdown-zero-drop-deploys/) and [what guaranteed delivery costs](https://imqueue.org/blog/guaranteed-message-delivery-cost/).

## Licensing: Moleculer's MIT vs `@imqueue`'s dual GPL/commercial

Licensing deserves more than a footnote, because for a lot of teams it settles the Moleculer-or-`@imqueue` question before any technical comparison.

**Moleculer is MIT.** Use it in anything, including closed-source commercial products, with no obligations beyond attribution.

**`@imqueue` is GPL-3.0**, with a [commercial license](https://imqueue.org/license/) for shipping inside closed-source products. If you're building an internal system, GPL-3.0 is usually a non-issue. If you're shipping a product your customers install, or your legal team has a blanket policy against copyleft, that's a real constraint — and one worth resolving early rather than after you've built on it.

Neither model is better; they reflect different funding intentions. But it's the difference most likely to be decisive, so it belongs above the feature table, not below it.

## Where Moleculer is the better choice

- **Your transport isn't Redis** — NATS, Kafka and AMQP are first-class there.
- **You need MIT licensing** for a closed-source product and don't want a commercial agreement.
- **You want resilience features configured, not assembled** — breakers, bulkheads, retries, tracing, gateway.
- **Your codebase is JavaScript**, or TypeScript is secondary.
- **You want a bigger ecosystem** — more mixins, more integrations, more people who've hit your problem.
- **You need distributed coordination primitives.** `@imqueue` deliberately doesn't provide them.

## Where @imqueue's model costs you

- **Redis only**, as above.
- **JSDoc is mandatory** and `removeComments: false` is required in consumers.
- **Client generation needs the service running**, which is a real step in CI and on a fresh checkout.
- **`callTimeout` is unset by default** — a call to a service that never answers stays pending forever.
- **No rest or spread parameters** on exposed methods; pass an array.
- **At-least-once delivery, no drain, no distributed locks**, no streaming.
- **GPL-3.0** unless you buy a commercial license.

## Quick comparison

| | @imqueue | Moleculer |
|---|---|---|
| Primary language | TypeScript-first | JavaScript-first (official typings) |
| Transport | Message queue (Redis only) | Pluggable (NATS, Redis, Kafka, AMQP, TCP, …) |
| Addressing | Queue name = service class name | Broker registry + action names |
| Service discovery | Not needed | Built-in registry/discovery |
| Load balancing | Competing consumers on a queue | Built-in strategies |
| Typed clients | Generated from the service | Assemble yourself / runtime validation |
| Contract drift caught | At regeneration, as compile errors | At runtime |
| Resilience | Mostly from the transport | Breakers, bulkheads, retries, fallbacks |
| Distributed locks | No (`@lock()` is in-process) | — |
| Feature breadth | Focused core | Broad (breakers, gateway, metrics, …) |
| Licence | GPL-3.0 / commercial | MIT |

## How to choose

- **Choose Moleculer** if you want a broad framework with many transporters, built-in resilience features and a permissive MIT licence, and you're comfortable assembling type safety yourself.
- **Choose @imqueue** if your services are TypeScript, you want generated typed clients and minimal infrastructure in the request path, and the GPL-3.0 or commercial model works for you.

They're not better or worse — they're aimed at different priorities.

## Frequently asked questions about @imqueue and Moleculer

### Is Moleculer still maintained?
Yes. It's actively developed and widely used; nothing in this comparison depends on it being stale.

### Which is better for a TypeScript codebase?
`@imqueue`, on the specific axis of static typing across service boundaries, because the client is generated from the service rather than declared alongside it. Moleculer works fine in TypeScript — you just don't get a compile-time contract between caller and action for free.

### Can I use Moleculer's transporters with @imqueue?
No. `@imqueue` supports Redis today; `IMessageQueue` is the documented interface if you want to implement another adapter.

### Does @imqueue have circuit breakers?
No. Some of that need is absorbed by the transport — a request to a down service waits on the queue rather than failing — but there's no breaker, bulkhead or retry policy to configure. If you want those as features, Moleculer has them.

### Is the GPL-3.0 licence a problem for commercial use?
It depends entirely on whether you distribute your product. Internal systems are usually unaffected; shipping closed-source software to customers needs the commercial licence. Resolve it with your legal team before you build, not after.

### Can I migrate from Moleculer to @imqueue incrementally?
In principle yes, service by service, since both can sit on Redis and a gateway can front either. Expect the work to be in the typing model and the loss of Moleculer's built-in resilience features, not in the transport.

---

If typed, low-infrastructure, queue-based RPC is what you're after, start with [**Getting Started**](https://imqueue.org/get-started/), or read [@imqueue vs NestJS](https://imqueue.org/blog/imqueue-vs-nestjs/) for the other framework comparison and [gRPC vs message-queue RPC](https://imqueue.org/blog/grpc-vs-message-queue-rpc/) for the transport one. Shipping inside a closed-source product? See [commercial licensing & support](https://imqueue.org/license/).

