@imqueue vs Moleculer: two takes on Node.js microservices
Moleculer is a full-featured microservices framework with many transporters; @imqueue is a focused, TypeScript-first RPC layer over a message queue. Here's an honest comparison to help you pick.
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:
// 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:
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' };
}
}
imq client generate User ./src/clients
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 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 and what guaranteed delivery costs.
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 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.
@imqueuedeliberately doesn't provide them.
Where @imqueue's model costs you
- Redis only, as above.
- JSDoc is mandatory and
removeComments: falseis required in consumers. - Client generation needs the service running, which is a real step in CI and on a fresh checkout.
callTimeoutis 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, or read @imqueue vs NestJS for the other framework comparison and gRPC vs message-queue RPC for the transport one. Shipping inside a closed-source product? See commercial licensing & support.
Read this page as plain markdown — no HTML, no navigation. For pasting into an LLM, or for an agent to fetch.
Building on @imqueue? The open-source packages live on GitHub and the docs at imqueue.org. Shipping inside a closed-source product? See commercial licensing & support.