Introduction

In this article we will discuss about this error we face when we are adding dependency to our node js package.

Root-cause

There are two steps adding a new dependency in to your project.

  1. Installing the dependency package correctly.
  2. Importing the correct path of the dependency classes you want to use.

If one of the two steps are incorrect, this will result in the error. It means that NodeJs is not able to find the dependency module or its derivatives (classes) in the ‘node_modules‘ folder.

Use-case

Let’s see the use case to better understand the solution. In my use case, I had to create a lambda functions in multiple AWS accounts for multiple services. Let’s say there are 3 services – A, B, C. All these services are in different AWS accounts. Each service wants to deploy a lambda function using CDK.

One option is that, I write a lambda CDK code in A and copy paste it B and C. If in future, a service D comes, we will have to copy and paste the same CDK code in D also. This is not scalable and also involves duplication of code.

Better solution is to create a common dependency CDK package which deploys a lambda function and integrate this package in A, B, C. This avoids duplication with less coding and is also scalable.

I created a CDK typescript package dependency package named CommonConstruct. This construct deploys a lambda function. The path of the package looked like this –

// CommonConstruct package directory structure
--- root 
      |--- dist
      |--- build
      |--- node_modules
      |--- lib
           |--- common_construct.ts // main file containing the lambda deployment code
      |--- package.json
      |--- package-lock.json

the common_construct.ts file looked like this –

// path of this class - root/lib/common_construct.ts
export CommonConstruct {
  // constructor code
  constructor(CLIENT_SERVICE_NAME: string){
      // deploy code
      getLambda(CLIENT_SERVICE_NAME);
  }
  getLambda(){
    const lambda = new Function(this, 'id', {
       functionName: `${CLIENT_SERVICE_NAME}`,
        code: // code path
        handler: // handler,
        runtime: Runtime.JAVA_17,
        // .. other properties
    });  
    return lambda;
  }
}

Now adding this package in service A, we installed the package by running the following command on terminal inside ServiceA root directory-

~ ServiceA % npm install CommonConstruct

The package was added in package.json and also in node_modules of Service A. Now we will try calling the CommonConstruct class in Service A.

import { CommonConstruct } from 'CommonConstructs/lib/common_construct';

// .... other code

const lambdaFunction = new CommonConstruct("ServiceA"); // passing the service name

This was throwing the error –

Cannot find module ‘CommonConstructs/lib/common_construct’
code: ‘MODULE_NOT_FOUND’, requireStack: [] – javascript/typescript
.

Solution

First, let’s see how to install the dependency correctly. Whenever you install a new dependency package

  • It will get added in the node_modules folder in the root directory.
  • It will show in package_json under dependencies or devDependencies.
~ % npm install <dependency name>

After you have installed the package correctly, try cleaning and building the project first without importing the dependency.

~ % rm -rf node_modules package-lock.json

Install the dependencies.

~ % npm install

Now in our use case, the import path is incorrect –

// Wrong path because it is accessing a common_construct class with a relative path. Instead
// allow the serviceA to consume the class CommonConstruct directly from the package name itself. 
import { CommonConstruct } from 'CommonConstructs/lib/common_construct';

because it is accessing a common_construct class with a relative path. Instead allow the serviceA to consume the class CommonConstruct directly from the package name.

To do that we need ‘index.ts’ file to be defined in CommonConstruct dependency package. Using the index.ts file we expose all our classes directly to the client service package. We define the index.ts file in the root directory like this –

// CommonConstruct package directory structure
--- root 
      |--- dist
      |--- build
      |--- node_modules
      |--- lib
           |--- common_construct.ts // main file containing the lambda deployment code
      |--- package.json
      |--- package-lock.json
      |--- index.ts // this class defines all the exports required by the client packages

the code of index.ts file –

export * from '/lib/common_constructs.ts';

Now, we update the package.json with the “main” and “types” fields.

Once you build the package with npm build, you will see the directory ‘dist’ getting created with index.js and index.d.ts file. Add paths of those two files in main and types fields below respectively.

{  
  "name": "CommonConstructs",
  "version": "0.1.0",
  "description": "Common construct package to deploy lambda",
  "main": "dist/index.js", // -- here
  "types": "dist/types/index.d.ts", // -- and here
  "author": "",
  "license": "UNLICENSED",
  "scripts" : {
    "build" : "npm build"
  }
  // other fields.
}
  

Now build the package again in service A and this time import the class like this.

// Now able to find the module directly instead of relative path.
import { CommonConstruct } from 'CommonConstructs';

This resolved the issue as the path is correctly defined in this case.

References

[1] – https://timmousk.com/blog/typescript-cannot-find-module/
[2] – https://sentry.io/answers/how-do-i-resolve-cannot-find-module-error-using-node-js/
[3] – https://www.npmjs.com/package/@aws-cdk/pipelines?activeTab=code

Leave a Reply

Your email address will not be published. Required fields are marked *