Microservices: Why You Need To Use npm Workspaces
Tags: node, npm, microservices, javascript
Published: 2023-10-06 17:24:47

If you are using microservice architecture to organize your project, you most likely will be having common helper functions which are copied across all microservices. Helper functions likes date utils, error handlers, customized loggers, response normalization, etc. This leads to code duplication. Maintaining, testing and versioning such codebase becomes a nightmare if not handled well 

This problem is typically solved by either using `npm link` or by publishing private packages on Package Managers like npm.

1) `npm link` works by creating symbolic links between packages. This can cause issues with certain project setups or operating systems. Also, these are more cumbersome to setup and maintain

2) Publishing to, and maintaining, a private package repository requires additional overhead – both in terms of time (publishing, updating) and infrastructure (maintaining the repository, ensuring its security, backup, etc.). Handling authentication, permissions, and access for a private npm registry can introduce complexities. Maintaining a private package repository might involve costs – both in terms of infrastructure and potential licensing fees, depending on the solution.

All such issues and more are solved efficiently by npm workspace

### npm Workspaces

npm workspace is a feature introduced in npm v7 that allows for managing multiple packages within a single top-level, root package. It enables you to work on interdependent packages as a single unit, making it easier to develop and test them together. This can be particularly useful in monorepo setups or projects with complex dependency structures.

With npm workspace, you can:

1. **Manage Interdependent Packages**: npm workspace allows you to work on multiple packages simultaneously, making it easier to develop and test them together. For example, you can make changes to a shared library used by multiple projects and test those changes in the context of each project.
2. **Share Dependencies**: npm workspace enables you to share dependencies between packages. This means that if multiple packages in your project require the same dependency, you can install it once at the workspace level and have it shared across all packages. This helps to optimize disk space and simplify dependency management.
3. **Run Commands Across Packages**: npm workspace allows you to run commands across all packages in your project. For instance, you can run a build command or a test command that will be executed in each package, ensuring consistency and efficiency in your project's development process.
4. **Manage Versioning**: npm workspace provides a unified versioning approach for packages within the workspace. You can easily update versions for all packages at once, ensuring compatibility and consistency across the project.

These are just a few examples of what can be done with npm workspace. It provides a powerful toolset for managing multiple packages within a project, promoting collaboration, modularity, and efficiency.

### Code Walkthrough:

1. **Setup Project**
    
    `mkdir` or `cd` into your project
    
    ```json
    mkdir test-project && cd test-project && npm init -y
    ```
    
2. **Parent Workspace Configuration (root package.json)**:
Add `workspaces` key which will be array of packages. This is the configuration that you'll set up at the root of your workspace to include both of the packages.

```json
{
  "name": "test-project",
  "workspaces": [
    "libraries/*"
  ],
	"scripts": {
    "install": "npm install --ignore-scripts",
    "test": "npm run test:libraries",
    "test:libraries": "npm run test --workspaces"
  }
}
```

In this example, the `"workspaces"` field specifies the directory pattern for the packages within the workspace. In this case, it is set to `"packages/*"`, which means that all packages within the `packages` directory will be part of the workspace.

The `"scripts"` section includes two example scripts. The `"install"` script is used to install dependencies across all packages in the workspace, while ignoring any individual package scripts. The `"test:packages"` script is used to run tests in each package within the workspace. The `--workspaces` flag is added to the `"test"` script to ensure that the test command is executed in each package.

The directory structure will look like:

```bash
/test-project
|-- /libraries
|   |-- /package-a
|   |-- /package-b
|-- package.json
```

**Package A**:

```json
//file: /libraries/package-a/package.json

{
  "name": "package-a",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: run tests from root\" && exit 1"
  }
}
```

```jsx
//file: /libraries/package-a/index.js

module.exports = () => {
  console.log('Hello from Package A!');
};
```

**Package B**:

```json
//file: /packages/package-b/package.json

{
  "name": "package-b",
  "version": "1.0.0",
  "main": "index.js",
  "dependencies": {
    "package-a": "1.0.0"
  },
  "scripts": {
    "test": "echo \"Error: run tests from root\" && exit 1"
  }
}
```

```jsx
//file: /packages/package-b/index.js

const packageA = require('package-a');

module.exports = () => {
  packageA();
  console.log('Hello from Package B!');
};

```

With the above setup, **`package-b`** depends on **`package-a`**. When using npm workspaces, you can use local versions of your packages within the workspace without having to publish them to npm.

After creating this structure, you can run **`npm install`** in the root directory, and npm will install all the dependencies for all packages, linking them where appropriate.

Remember that the actual code in each package is minimal for illustration purposes, and you can extend them as needed for your projects.

### Helpful Commands for Reference:

1. **Installing Workspace Dependencies**:
    
    This will install dependencies for all the packages defined in your workspace configuration.
    
    ```
    npm install
    ```
    
2. **Running Scripts Across Workspaces**:
    
    If you want to run a specific script defined in a `package.json` script section of one of your workspaces, you can use the `-w` or `--workspace` flag.
    
    For instance, to run the test script for a workspace named `package-a`:
    
    ```
    npm run test -w package-a
    ```
    
    To run the test script for all workspaces:
    
    ```
    npm run test --workspaces
    ```
    
3. **Listing Dependencies Across Workspaces**:
    
    You can list dependencies across all workspaces using the `ls` or `list` command.
    
    ```
    npm ls
    ```
    
**Conclusion:**
npm workspaces are designed to make managing monorepos easier. This means that while you can manage dependencies, scripts, and other tasks at the individual workspace level, it's often beneficial to handle shared tasks at the root level to maintain consistency and simplicity.