Skip to content

Why Missive.js?

Why Missive.js?

As a developer, whether you primarily work on the backend using frameworks like NestJS, Express, Koa, or Fastify, where your focus is solely on backend services, or you focus on frontend development with modern tools like Next.js, Remix, or Astro that integrate backend functionality through Backend for Frontend (BFF) patterns, you often need to build and manage server-side code.

For pure backend applications, maintaining clarity and structure as your codebase grows can be challenging. Business logic, API handling, and middleware can become intertwined, leading to complex, tightly coupled systems that are difficult to scale and maintain. On the other hand, for frontend frameworks that incorporate backend logic (BFF), frontend and backend concerns can intermingle, further complicating the separation of responsibilities and maintainability.

Missive.js offers a solution by bringing the principles of CQRS (Command Query Responsibility Segregation) and Clean Architecture into your projects, whether you are building standalone backend services, frontend applications with backend capabilities, or bridging both. Missive.js helps you build modular, maintainable, and scalable systems that cleanly separate concerns and promote best practices.

Let’s simplify CQRS

CQRS (Command Query Responsibility Segregation) is a pattern where we split data handling into three parts:

  • Commands: These are actions that change data. For example, creating a post or updating a profile. When you submit a form or trigger an action, you’re sending a command.

  • Queries: These are requests to read data without changing it. For example, fetching a list of posts to display on the page. Queries just get information.

  • Events: When a command is successfully executed (like creating a post), an event is triggered to indicate that something has changed. Events help notify other parts of the system about the update, and the frontend can listen for these events to update the UI in real-time.

Problem that it solves

Traditional approaches to building backend logic, whether as standalone services or within frontend-integrated frameworks (BFF), often suffer from:

  • Tightly coupled code: Business logic, API routes, and middleware are often tightly bound together, making it difficult to introduce changes or extensions without affecting multiple parts of the application.
  • Difficult scaling: As applications grow, monolithic codebases become harder to manage, leading to more bugs and slower development cycles.
  • Lack of modularity: Reusing components or logic becomes challenging, especially when different parts of your application need to interact in a loosely coupled manner.
  • Observability and debugging: Understanding how data flows through your system, tracking errors, and monitoring performance can be challenging without clear separation of concerns.

How it helps

Missive.js addresses these challenges by introducing a Service Bus pattern inspired by CQRS and Clean Architecture principles. It provides a centralized system for handling Commands, Queries, and Events, allowing you to build modular and decoupled components that are easy to manage and scale.

  1. Separation of concerns: Missive.js helps you clearly separate different types of operations:
  • Commands: Actions that mutate(change) the state of your application, such as creating a new user or updating a profile.
  • Queries: Requests for information that do not alter the state, like fetching user data or retrieving a list of products.
  • Events: Notifications about state changes that can be handled in an asynchronous manner, useful for integrating with other systems or services.
  1. Middleware system: Missive.js includes a powerful middleware system that allows you to inject cross-cutting concerns, such as:
  • Validation: Reusable validation logic that ensures all incoming data adheres to your application’s requirements before it reaches the handlers.
  • Authorization: Easy-to-implement access control checks that run before commands or queries are processed.

And on top of what you can do on your own, Missive.js provides a set of built-in middlewares to handle common concerns like:

  • Caching: Cache the results of queries to speed up your application.
  • Lock: Lock the processing of a message based on something in the intent itself.
  • Retryer: Retry handling if an error occurs.
  • Webhook: Send the envelope to webhook(s).
  • Logger: Log the messages and the full Envelope (with the Stamps) before and once handled (or errored) in the backend of your choice.
  • FeatureFlag: Control the activation of specific features and manage dynamic feature management, safer rollouts, and efficient A/B testing.
  • Mocker: Mock the result of a specific intent to bypass the handler.
  • Async: Defer handling to a consumer via a queue.

This middleware architecture is familiar if you’ve used libraries like Express, Fastify, Koa, etc. making it intuitive for both backend and frontend developers. It centralizes the processing logic, making your code cleaner and easier to maintain.

  1. Clean Architecture: Missive.js promotes Clean Architecture principles by providing a clear separation of concerns and a structured way to handle Commands, Queries, and Events. This separation makes it easier to maintain and extend your application as it grows.

How does it work?

With Missive.js, an intent is either a command, a query, or an event and it is created using the specialized bus. Usually you will have 3 different buses:

  • Query Bus: For read-only operations that retrieve data.
  • Command Bus: For operations that mutate application state.
  • Event Bus: For broadcasting changes and triggering asynchronous actions.

Here’s how it works:

  1. You begin by creating an intent. This could be a command (e.g., to create a new user), a query (e.g., to fetch product details), or an event (e.g., an order being placed). At the point of creation, the intent is validated to ensure it has the correct structure and necessary parameters, meeting all predefined requirements.

  2. Once returned (or later in the lifecycle of your application), you dispatch the intent to the bus.

  3. On dispatch, the bus wraps the intent in an Envelope.

  4. The Envelope then flows through the series of configured middleware functions. These middleware can:

    • log relevant information for monitoring
    • apply additional business logic or validations as needed
    • attach Stamps to the Envelope. Stamps serve as markers influencing how the intent is processed.
    • observe the result of the command and dispatch events based on the outcome.
  5. Finally, the Envelope reaches the handler defined for the Intent. The handler processes the intent based on the information and stamps within the Envelope, and returns the result, which could be data, a confirmation, or any other type of response.

This structured pipeline ensures that intents are processed in a controlled and consistent manner, allowing for flexibility in adding features like logging, validation, and custom behavior, all while keeping the core business logic clean and maintainable.

Here is a diagram to illustrate the flow of an intent through Missive.js:

Missive.js - Diagram

Example

  1. Create the Command bus

    type CommandHandlerRegistry = SendRegistrationEmailDefinition & SendNotificationDefinition;
    const bus = createCommandBus<CommandHandlerRegistry>();
    bus.register('sendRegistrationEmail', Schema, Factory({ mailer }));
  2. Dispatch an intent

    const intent = bus.createCommand('sendRegistrationEmail', { email: 'plopix@example.com' });
    // ...
    const { envelope, result } = await bus.dispatch(intent);

Going further

Here are some resources to help you get started with CQRS, Clean Architecture, and Missive.js:


Missive.js. MIT License.
Powered by Astro Starlight.
Inspired by Symfony Messenger