SkillAgentSearch skills...

Nodetskeleton

A NodeJS Skeleton based in Clean Architecture to use TypeScript with ExpressJS, KoaJS or any other web server framework. Please give star to project to show respect to author and his efforts. ๐Ÿš€

Install / Use

/learn @harvic3/Nodetskeleton

README

NodeTSkeleton <img height="50" src="https://i.ibb.co/BZkYR9H/esqueletots.png" alt="skeleton" border="0">

Introduction ๐Ÿค–

NodeTskeleton is a Clean Architecture based OOP Template Project for NodeJs using TypeScript to implement with any web server framework or even any user interface.

The main philosophy of NodeTskeleton is that your solution (domain and application, โ€œbusiness logicโ€) should be independent of framework you use, therefore your code SHOULD NOT BE COUPLED to a specific framework or library, it should works in any framework.

The design of NodeTskeleton is based in Clean Architecture, an architecture that allow us to decouple the dependencies of our solutions, even without the need to think about the type of database, providers or services, frameworks, libraries or any other dependency.

NodeTskeleton has the minimum and necessary tools for you to develop the business logic of your application, you can even decide not to use its included tools (you can remove them), and use the libraries or packages of your choice.

Table of contents

  1. cli functions ๐Ÿ“Ÿ
  2. Philosophy ๐Ÿง˜๐Ÿฝ
  3. Included tools ๐Ÿงฐ
    1. Errors Flow
    2. Locals
    3. Mapper
    4. UseCase
    5. UseCases Iterator
    6. Validator
    7. API Docs generator
  4. Dependency injection strategy ๐Ÿ“ฆ
  5. Using NodeTskeleton ๐Ÿ‘พ
    1. Using with KoaJs ๐Ÿฆ‹
    2. Using with ExpressJs ๐Ÿ›
    3. Using with another web server framework ๐Ÿ‘ฝ
    4. Create your first use case ๐ŸŽฌ
  6. Workers ๐Ÿ”„
  7. Sockets ๐Ÿ•ณ
  8. Infrastructure ๐Ÿ—๏ธ
  9. Installation ๐Ÿ”ฅ
  10. Run Test ๐Ÿงช
  11. Application debugger ๐Ÿ”ฌ
  12. Build for production โš™๏ธ
  13. Test your Clean Architecture ๐Ÿฅ
  14. Coupling ๐Ÿงฒ
  15. Clustering the App (Node Cluster) ๐ŸŽš
  16. Strict mode ๐Ÿ”’
  17. Multi service monorepo ๐Ÿงฎ
  18. Conclusions (Personal) ๐Ÿ’ฉ
  19. Code of Conduct ๐Ÿ‘Œ
  20. Warning ๐Ÿ’€
  21. Acknowledgments

Philosophy

Applications are generally developed to be used by people, so people should be the focus of them.

For this reason user stories are written, stories that give us information about the type of user, procedures that the user performs in one or some parts of the application (modules), important information that serves to structure the solution of our applications, and in practice, how is that?

The user stories must be in the src/application path of our solution, there we create a directory that we will call modules and inside this, we create a directory for the task role, for example (customer, operator, seller, admin, ...) and inside the role we create a directory of the corresponding use case module, for example (users, sales, products, organizations, purchases, ...), and in practice that looks more or less like this:

<div style="text-align:center"> <img src="https://i.ibb.co/t2mHGmC/Node-Tskeleton.png" alt="Node-Tskeleton" border="10"> </div>

โฌ† go to the future

"Buy Me A Coffee"

Observations ๐Ÿ‘€

  • If your application has no roles, then there's no mess, it's just modules and it can be something like users, sales, products, organizations, purchases and an amount others according your own needs.

  • But taking into consideration that if the roles are not yet defined in your application, the best option would be to follow a dynamic role strategy based on permissions and each use case within the application (or use case group) would be a specific permission that would feed the strategy of dynamic roles like ACL.

  • Note that you can repeat modules between roles, because a module can be used by different roles, because if they are different roles then the use cases behavior should also be different, otherwise those users would have the same role.

  • This strategy makes the project easy to navigate, easy to change, scale and maintain (Talking about development), which boils down to good mental status, besides you will be able to integrate new developers to your projects in a faster way.

I personally recommend the permission-based dynamic role strategy to avoid complications due to roles and permissions in the future because this is what usually happens when developing a product, even if you are convinced that it is very well defined.

โฌ† go to the future

Included tools

NodeTskeleton includes some tools in the src/application/shared path which are described below:

Errors Flow

Is a tool for separating controlled from uncontrolled errors and allows you to launch application errors according to your business rules. This is important because the main idea when developing applications is to try as much as possible to predict the behavior of these, that's why controlled errors are useful and we need a strategy to allow us to identify them, for example:

/*
** context: it's the context where the error will be launched.
*/
export class ApplicationError extends Error {
  constructor(
    readonly context: string,
    readonly message: string,
    readonly errorCode: number | string,
    readonly stack?: string,
  ) {
    super(message);
    this.name = `${context.replace(/\s/g, StringUtil.EMPTY)}_${ApplicationError.name}`;
    this.errorCode = errorCode;
    this.stack = stack;
  }
  // ...
}

Is important to note that the name of the execution CONTEXT is concatenated with the name of the ApplicationError class in order to better identification of the controlled errors. It's very useful for observability tools in order to filter out real errors from those we are controlling.

The straightforward way to use it is as follows:

throw new ApplicationError(
  this.CONTEXT,
  appMessages.get(appMessages.keys.ERROR_TO_CREATE_SOMETHING),
  ApplicationStatus.BAD_REQUEST,
  JSON.stringify(error),
);

Or if the pointer of your program is in the scope of your UseCase, you can use the error control function in the BaseUseCase class:

The dirty way:

if (!someCondition) { // Or any validation result
  result.setError(
    this.appMessages.get(this.appMessages.keys.PROCESSING_DATA_CLIENT_ERROR),
    this.applicationStatus.INTERNAL_SERVER_ERROR,
  );
  this.handleResultError(result);
}

The clean way one:

// In the UseCase context in Execute method
const user = await this.getUser(result, userId);
if (result.hasError()) return result;

// In the UseCase context in out of Execute method
private async getUser(result: IResult, userId: string): Promise<User> {
  const user = await this.userRepository.getById(userId):
  if (!user) {
    result.setError(
      this.appMessages.get(this.appMessages.keys.USER_CAN_NOT_BE_CREATED),
      this.applicationStatus.INTERNAL_CONFLICT,
    );
  }

  return user;
}

The clean way two:

// In the UseCase context in Execute method
const { value: userExists } = await result.execute(
  this.userExists(user.email?.value as string),
);
if (userExists) return result;

// In the UseCase context in out of Execute method
private async userExists(email: string): ResultExecutionPromise<boolean> {
  const userExists = await this.userRepository.getByEmail(email);
  if (userExists) {
    return {
      error: this.appMessages.getWithParams(
        this.appMessages.keys.USER_WITH_EMAIL_ALREADY_EXISTS,
        {
          email,
        },
      ),
      statusCode: this.applicationStatus.INVALID_INPUT,
      value: true,
    };
  }

  return {
    value: false,
  };
}

The function of this class will be reflected in your error handler as it will let you know when an exception was thrown by your system or by an uncontrolled error, as shown below:

  handle: ErrorHandler = (
    err: ApplicationError,
    req: Request,
    res: Response,
    next: NextFunction,
  ) => {
  const result = new Result();
  if (err?.name.includes(ApplicationError.name)) {
    console.log("Controlled application error", err.message);
  } else {
    console.log("No controlled application error", err);
  }
};

Which use? Feel free, it's about colors and flavours, in fact you can developed your own strategy, but if you are going to prefer to use the The clean way one keep present the next recommendations:

  • Never, but never, use setData or setMessage methods of the result inside functions out of the UseCase Execute method context, only here (Inside the UseCase Execute method) this functions can be call.
  • You only must use methods to manage errors in result objects outside of the UseCase Execute method context.

Why?, itยดs related to side effects, I normally use the The clean way one and I have never had a problem related to that, because I have been careful about that.

โฌ† go to the future

Locals

It is a basic internationalization tool that will allow you to manage and implement the local messages of your application, even with enriched messages, for example:

View on GitHub
GitHub Stars306
CategoryProduct
Updated13d ago
Forks32

Languages

TypeScript

Security Score

100/100

Audited on Mar 16, 2026

No findings