npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@soapjs/soap

v0.5.8

Published

Lightweight, clean architecture-based framework designed to streamline the development of modular and maintainable web applications

Downloads

449

Readme

SoapJS

OMG, Yet Another Framework

Yes, we know there are many frameworks out there, and they are excellent. However, there's a gap: separating logic from the framework itself to ease the trouble during migration or framework switching. As backend developers, we often had to rewrite code tailored to specific frameworks. SoapJS, rooted in Clean Architecture, offers customizable code structures through configuration files and presets, allowing you to decide on your code's structure. We provide a solution that can be adapted and applied as preferred, eliminating the need to start from scratch. Our CLI facilitates file generation based on configurations, letting developers focus on writing the actual code.

Description

SoapJS is a framework designed to empower developers by adhering to the principles of Clean Architecture, enhancing modularity and maintainability in web application development.

Development Status and Future Plans

The @soapjs/soap package, along with its related packages, is currently under active development. As these packages have not yet reached version 1.0.0, they are subject to potentially significant changes. We release updates at least once a week.Currently, the documentation on docs.soapjs.com is not being updated in real-time but will undergo comprehensive updates to coincide with the release of version 1.0.0 of our packages.

Pre-release Usage Notice: While you are welcome to download and use the packages, please be aware that they are not yet considered stable releases. The official announcement and launch of these packages will coincide with the release of version 1.0.0.

Current and Upcoming Features:

  • Our development roadmap includes releasing fully documented and tested packages for several frameworks and technologies:
    • Express Framework Integration (@soapjs/soap-express)
    • NestJS Framework Integration (@soapjs/soap-nestjs)
    • Authentication (@soapjs/soap-auth)
    • Database Integration for:
      • MongoDB (@soapjs/soap-node-mongo)
      • PostgreSQL (@soapjs/soap-node-postgres)
      • Redis (@soapjs/soap-node-redis)
      • MySQL (@soapjs/soap-node-mysql)
    • CLI Updates: We are also focusing on updating the Command Line Interface to enhance usability and features.

Changelog:

More information can be found later in this README.

New Features

  • Event Bus Components:

    • Added an EventBus interface and implementations for various messaging systems (e.g., RabbitMQ, Kafka).
    • Introduced support for advanced message processing strategies, including retries, dead-letter queues (DLQs), and message validation.
  • WebSocket Components:

    • Added a robust WebSocketClient with features like automatic reconnection, rate limiting, subscriptions, and custom authentication strategies.
    • Added a CustomWebSocketServer with client management, message broadcasting, and heartbeat support.
  • Dependency Injection Support:

    • Introduced decorators for dependency injection to enable seamless integration with frameworks like inversify or nestjs without directly coupling the core logic to these libraries.

Enhancements

  • Database Transactions:

    • Refactored database transaction definitions to improve flexibility and maintainability.
  • Result and Failure:

    • Made cosmetic changes to the Result and Failure classes for improved clarity and usability in handling application errors and outcomes.

Future Expansions:

  • Following the initial releases, we plan to extend support to additional frameworks and platforms such as:
    • NestJS: Dedicated package integration.
    • AWS: Solutions tailored for Amazon Web Services.
    • And more based on community feedback and demand.

We value community input and encourage users to provide feedback and contribute to the development process. Stay tuned for more updates and please consider the current stage of development when using the packages in production environments.

Getting Started

Everything you need to know can be found at SoapJS Documentation.

To install the package, use the command:

npm i @soapjs/soap
# or
# yarn add @soapjs/soap

However, we recommend familiarizing yourself with the documentation and CLI at the provided address.

Usage

This package does not include dependencies, IoC, databases, or web frameworks; you must provide them on your own. This makes the SoapJS package flexible. We also have several dedicated packages (some still in development).

For MongoDB, Redis, and MySQL databases, you will find necessary helpers in the packages @soapjs/soap-node-mongo, @soapjs/soap-node-redis, and @soapjs/soap-node-mysql respectively.

There are also dedicated packages for web frameworks, containing dedicated routers, helpers, and startup scripts. They are divided accordingly: @soapjs/soap-express, @soapjs/soap-nestjs, and @soapjs/soap-aws.

Using the @soapjs/soap package:

Soap provides interfaces, abstractions, and basic implementations to help organize code and maintain the structure you define in clean architecture.

Example usage:

Dependencies

Every created component from the domain layer must be bound in a container. How you do this depends on you; you can use our container or, for example, Inversify. It's important to use strings as labels and bind interfaces to implementations from the data layer directly or to classes like controllers or use cases.

import * as Soap from '@soapjs/soap';
import { Container } from 'inversify';

export class Dependencies implements Soap.Dependencies {
  constructor(public readonly container: Container){}
  public async configure() {
    // rest of the components ...
    this.container
      .bind<CustomerController>(CustomerController.Token)
      .to(CustomerController);
  }
}

Route

A route is an object containing a description, route instructions, and the handler to be invoked upon route activation. A route may also contain options regarding authentication, validation, or request and response mapping. Middleware implementations for these options must be provided by you. To construct a RouteIO, use the interface from the @soapjs/soap package.

import * as Soap from '@soapjs/soap';
import { Container } from 'inversify';

export class GetCustomerDetailsRoute extends Soap.GetRoute {
  static create(container: Container) {
    const controller = container.get<CustomersController>(CustomersController.Token);
    const handler = controller.getCustomerDetails.bind(controller);

    // ... other setup operations

    return new GetCustomerDetailsRoute('customers/:id', handler, {
      io: new GetCustomerDetailsRouteIO()
    });
  }
}

Route Model

Similar to a regular model, but this one should reside alongside routes rather than with other database models. It's a special type of model dedicated only to routes.

export type GetCustomerDetailsResponse = {
  orders: OrderModel[],
  address: AddressModel,
  loyalty_points: number
}

RouteIO

Contains methods for parsing a request into a handler's input and vice versa, parsing the handler's result into a response. RouteIO should be executed before the handler is run.

import * as Soap from '@soapjs/soap';
import { Request, Response } from 'express';

export class GetCustomerDetailsRouteIO implements Soap.RouteIO<Customer, GetCustomerDetailsResponse, Request, Response> {
  public toResponse(response: Response, result: Result<Customer>) {
    if (result.isFailure) {
      // if error is instanceof Soap.HttpError or 500
      const status = result.failure.error.status || 500;
      const message = result.failure.error.message || 'YOUR MESSAGE';
      
      response.status(status).send(message);
    } else {
      const { ... } = result.content;
      // map result content to output if needed
      response.status(200).send({...});
    }
  }
  
  public fromRequest(request: Request): string {
    // add appropriate logic and data validation
    return request.params.id;
  }
}

Router

The Router in @soapjs/soap serves to manage routes, specifically embedding and executing them. To utilize the router, the framework and all dependencies must be initialized beforehand. The actual implementation of a router, such as ExpressRouter from the @soapjs/soap-express package, requires you to either write your own code or use existing implementations.

Using a router is not mandatory but is recommended to facilitate the definition of paths and the management of settings associated with them. The router is also natively supported by the framework's CLI.

Example Implementation for an Express-based Router

import { ExpressRouter } from '@soapjs/soap-express';

// Example implementation for an Express-based router
export class MyRouter extends ExpressRouter {
  constructor(private container: Container) {
    super('api', 'v1');
  }

  public setupRoutes(): void {
    /**
     * Depending on your choice of where to specify the handler source, you can do it in the setupRoutes() method - the default approach - or inside the Route itself.
     */
    this.mount(GetCustomerDetailsRoute.create(this.container));
  }
}

The content of setupRoutes is not predefined but requires the use of this.mount to add a route to the router. If you wish to implement your own router or one for a framework other than Express, check the implementation of ExpressRouter in @soapjs/soap-express and use it as a basis for writing your own.

Controllers

These are entry points for logic, where you should place and manage use cases. They shouldn't contain complex logic. Handlers, i.e., controller methods assigned to specific routes, can accept input data but should not directly handle, for example, requests. Instead, they should receive input prepared and mapped by RouteIO. Handlers return Result objects with expected outcomes or errors. You decide what to do with these in RouteIO.

export class CustomerController {
  static Token = 'CustomerController';

  public async getCustomerDetails(input: string): Promise<Result<Customer>> {
    let result: Result<Customer>;
    // call use cases (may include simple logic)
    return result;
  }
  ...
}

Use Case

These are classes with a single public execute method, used to perform one more or less complex task. A use case may invoke other use cases to achieve the desired result. Use cases should be as simple as possible, but how you organize them depends on you.

import * as Soap from '@soapjs/soap';
// Example use case class
export class ProcessOrderUseCase implements Soap.UseCase<OrderConfirmation>{
  static Token = "ProcessOrderUseCase";

  public async execute(
    orderItems: Product[],
    customerDetails: Customer,
    paymentMethod: string,
  ): Promise<Result<OrderConfirmation>> {
    let confirmation: OrderConfirmation;
    // logic ...
    return Result.withSuccess(confirmation);
  }
}

Repository and BaseRepository

Repositories are components that perform operations on databases. They typically use one data context (i.e., database client or specific table), mapper, and query builder. Repositories should handle all data-related logic but not business logic (exceptions may include decisions like caching data). BaseRepository contains basic implementations and is tailored to using a single data context. If you need custom (dedicated) methods, parameters, or want to use multiple contexts, such as for aggregation, you should write your own implementation, perhaps by extending BaseRepository. Below are examples of setting up a repository and creating custom ones.

// Example repository abstract class (or interface)
export abstract class CustomerRepository extends Soap.Repository<Customer> {
  static Token = "CustomerRepository";
  // place for additional methods ...
}

// Example custom repository implementation
export class CustomerRepositoryImpl extends Soap.BaseRepository<Customer, CustomerMongoModel> implements CustomerRepository {
  constructor(
    context: Soap.DatabaseContext<Customer, CustomerMongoModel>,
    // feel free to add more contexts if you need
  ) {
    super(context);
  }
  // place for additional methods ...
}

// Example setup of repository and creation of custom ones in the Dependencies class
export class Dependencies implements Soap.Dependencies {
  public async configure(config: Config) {
    // Creates a MongoDB client using the configuration provided.
    // The 'SoapMongo.create' function initializes a client based on MongoDB settings defined in 'config.mongo'.
    const mongoClient = await SoapMongo.create(config.mongo);

    // Creates a new DatabaseContext:
    // 1. MongoSource: Represents the MongoDB collection using the MongoDB client. 
    //    'CustomerMongoModel' refers to the model class that maps the MongoDB collection 'customers'.
    // 2. CustomerMongoMapper: Handles the mapping of data between the domain entity and the database model.
    // 3. MongoSessions: Represents the Mongo database session registry.
    // 4. The 'modelClass' option specifies the model class that includes decorators for field mapping.
    const context = new Soap.DatabaseContext(
      new MongoSource<CustomerMongoModel>(mongoClient, 'customers'),
      new CustomerMongoMapper(),
      new MongoSessions(),
      { modelClass: CustomerMongoModel }
    );

    // Creates an instance of 'CustomerRepositoryImpl' with the configured context.
    // This repository implementation will handle all data operations for 'Customer' entities.
    const impl = new CustomerRepositoryImpl(context);
    // Alternatively, if using a generic repository pattern:
    // const impl = new BaseRepository(context)

    // Binds the 'CustomerRepositoryImpl' instance to the 'CustomerRepository' token in the IoC container.
    // This makes 'CustomerRepositoryImpl' available for injection throughout the application.
    this.container
      .bind<CustomerRepository>(CustomerRepository.Token)
      .toConstantValue(new CustomerRepositoryImpl(context));
  }
}

Sources (Collections)

Sources are components that perform operations directly on a collection or table in the database using a client. To use them, you need a dedicated implementation, like @soapjs/soap-node-mongo, or you can write your own, ensuring consistency with the interface.

export class CustomerMongoSource extends MongoSource<CustomerMongoModel> {
  constructor(client: MongoSource) {
    super(client, 'customers.collection');
  }
  // additional methods...
}

Mapper

This component is used to map entities to models and vice versa, similar to RouteIO. Mappers should handle a single type of document, so you should create mappers per collection and repository. You don't necessarily need to use the mapper in your code if you're using existing implementations. BaseRepository handles this before and after executing commands on the collection, depending on the type of operation.

export class CustomerMongoMapper extends MongoMapper<Customer, CustomerMongoModel> {
  public toEntity(model: CustomerMongoModel): Customer {
    const { ... } = model;
    // parsing model types to entity types ...
    return new Customer(...);
  }
  
  public fromEntity(entity:Customer): CustomerMongoModel {
    const { ... } = entity;
    // parsing entity types to model types ...
    return removeUndefinedProperties({ ... });
  }
}

Entity

Mabe unlike in other frameworks, entities here are used as data containers in the domain layer. They may contain helper methods but should not include mappers to database documents (that's what mappers are for), and they should not directly reference databases. Treat them as data representations in logic, independent of the data source.

export class Customer {
  constructor(
    public readonly orders: Order[],
    public readonly address: Address,
    public readonly loyaltyPoints: number) {}
}

Model

@soapjs/soap allows defining data models either as classes or types. The choice between these depends on whether you prefer using decorators for automatic field mapping or manual field mappings for flexibility without decorators.

Class-based Models with Decorators

  • Use when: You prefer an object-oriented approach with automatic field mapping via decorators.
  • Benefits: Simplifies ORM/ODM integration, supports encapsulation of business logic, and facilitates data validation.
  • Example:
    import { ObjectId } from 'mongodb';
    import { EntityField } from '@soapjs/soap';
    
    class CustomerMongoModel {
      @EntityField('customerId')
      _id: ObjectId;
    
      @EntityField('customerName')
      name: string;
    }
    
    const customerSource = new MongoSource<CustomerMongoModel>(..., { modelClass: CustomerMongoModel });

Type-based Models with Manual Field Mappings

  • Use when: You need flexibility or do not wish to use class decorators, often suitable for projects with schema-less NoSQL databases.
  • Benefits: Allows precise control over how entity properties map to database fields, especially when using utilities like Where to construct queries.
  • Setup:
    • Specify modelFieldMappings in the Source to manually map entity properties to model properties.
    • This approach is necessary if you opt-out of class-based models with decorators.

Example:

const fieldMappings = {
  customerId: { name: '_id', type: 'ObjectId' },
  customerName: { name: 'name', type: 'string' }
};

const customerSource = new MongoSource<CustomerMongoModel>(..., { modelFieldMappings: fieldMappings });

Integration with Database Queries:

  • With Decorators: Field names should be automatically resolved in queries and the Where clauses.
  • Without Decorators: You must manually ensure that field names in Where clauses match those defined in modelFieldMappings. If no mappings are defined, the field names used in queries and Where must directly correspond to the database field names.

Choosing between class-based and type-based models impacts how you interact with the database in @soapjs/soap. Making each suitable for different scenarios and database technologies.

Transactions

Transactions are used to ensure that a sequence of operations on a database is executed atomically. This means either all operations succeed, or none of them are applied, maintaining data consistency and integrity.

In this framework, transactions can be implemented in the following ways:

  1. Custom Transaction Methods in Repositories
  2. Transaction Class and TransactionRunner
  3. Declarative Transactions with Decorators

Click here for details

Using Inject and Injectable Decorators

The decorators @Injectable and @Inject provide a simple, framework-agnostic way to mark classes and class members for dependency injection. By default, no actual IoC container is included; instead, you set up your classes with these decorators, and then a separate adapter (for NestJS, Inversify, or another DI framework) can interpret the metadata and perform the actual binding/resolution.

Click here for details

Using Sockets

Sockets provide a persistent, full-duplex communication channel over a single TCP connection. Unlike HTTP, which operates in a request-response model, Sockets enable real-time interaction between a client and a server, making them an excellent choice for applications requiring low-latency communication.

Click here for details

Service and Toolset

Services are used to communicate with other APIs or any other external data sources (not databases; that's what repositories and collections are for). Toolsets, on the other hand, are categorized sets of tools. Instead of having a general Utils or manager, it's a component where you can place any methods that don't fit into other patterns. They can be static or instantiated, and you can obtain them via a container.

// Example service abstract class (or interface)
export interface WeatherService {
  static Token = "WeatherService";
  public getWeatherByCity(city: string): Promise<Result<Weather>>;
}

// Example service implementation
class WeatherServiceImpl implements WeatherService {
  constructor(
    private apiKey: string,
    private baseUrl: string) {}

  async getWeatherByCity(city: string): Promise<Result<Weather>> {
    try {
      const response =
        await axios.get(`${this.baseUrl}?q=${city}&appid=${this.apiKey}`);
      return Result.withSuccess(Weather.from(response.data));
    } catch (error) {
      return Result.withFailure(error);
    }
  }
}
// Example data toolset
export class DateToolset {
  static getFirstDayOfMonth(date: Date): Date {
    return new Date(date.getFullYear(), date.getMonth(), 1);
  }
  
  static getLastDayOfMonth(date: Date): Date {
    return new Date(date.getFullYear(), date.getMonth() + 1, 0);
  }
}

Using EventBus

The EventProcessor and EventBus provides a flexible and scalable solution for managing events in distributed systems. With built-in support for retry policies, error handling, message parsing, and validation, the framework is designed to work seamlessly with various messaging systems like RabbitMQ, Kafka or AWS SQS.

Click here for details

Feeling overwhelmed?

Fear not! We're developing a CLI (Work in Progress) and working on AI for even greater comfort. Stay tuned for more convenience!

Issues

If you encounter any issues, please feel free to report them here.

Contact

For any questions, collaboration interests, or support needs, you can contact us through the following:

License

SoapJS is licensed under the MIT License.