Create a Private NodeJS Module with TypeScript

In this article, we will learn how to create a NodeJS module with TypeScript and make it private. What we need here is a private package for the node module. Sadly, it is not available on a free account of npmjs, especially at this moment. So, we will use GitHub / GitLab to store it.

Code your module in TypeScript

First, we need to code our code, but we will use TypeScript instead of JavaScript. Let's say we are familiar with JavaScript, it might be a little challenging because we aren't familiar with TypeScript.

Create a folder, let's say your-module, and run npm init to create a package.json which might look something like this.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
  "name": "@your-group/your-module",
  "version": "0.1.0",
  "description": "Your module description here.",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "scripts": {
    "build": "tsc"
  },
  "keywords": [
    "key1",
    "key2",
    "key3"
  ],
  "author": "Your Name",
  "license": "ISC",
  "publishConfig": {
    "access": "restricted"
  },
  "files": [
    "dist/**/*",
    "src/**/*"
  ]
}

You can change name, description, keywords, and author. It's okay we started our library with 0.1.0. You can change it too, let's say 1.0.0 if you do not agree with 0.1.0. You might notice there are folders dist and src. Let me explain the folders.

your-module/
├── dist/
├── examples/
├── src/
├── tests/
├── .gitignore
├── package-lock.json
├── package.json
├── README.md
└── tsconfig.json

Here is the .gitignore file.

coverage/
dist/
node_modules/

We ignored the test coverage files and of course node modules and the dist/ folder.

Before we move on, let me remind you. The TypeScript is for development. We use JavaScript for the production. So, people will import it for development and compile it along with other TS files to JS files. Make sure your code works for the production too. Such as can be imported, etc. We will talk about it later.

Next, we install modules for development, npm install -D typescript, the most important one because we want to work with TypeScript. I don't talk about unit tests and linting here. You can add them on your own.

Run tsc --init or ./node_modules/typescript/bin/tsc --init to create the tsconfig.json. For the tsconfig.json, especially the compilerOptions, you can leave it default or set it on your own. The most important is you need to add "include": ["src/**/*"]. It is to specify the folder to compile.

{
  ...
  "include": ["src/**/*"]
}

Always create index.ts for any folder in the src/ folder to list exported components. Let's say below is your module.

your-module/
├── dist/
├── examples/
├── src/
│   ├── configs/
│   │   ├── index.ts
│   │   ├── YourConfigA.ts
│   │   └── YourConfigB.ts
│   ├── index.ts
│   ├── YourClass.ts
│   └── YourInterface.ts
├── tests/
├── .gitignore
├── package-lock.json
├── package.json
├── README.md
└── tsconfig.json

So, here is your index.ts for example.

1
2
3
4
import YourClass from "./YourClass"
import YourInterface from "./YourInterface"

export {YourClass, YourInterface}

Publish your module to GitHub or GitLab

Once your module is ready, it's time to publish it. We can use GitHub or GitLab, the same providers host our Git repositories.

Create an NPM token

We need an NPM token to publish or read the package/module. Go create a personal access token and make it into an environment variable. Eg; export NPM_TOKEN="your-token".

GitHub

Create a classic personal access token or go to https://github.com/settings/tokens/new with the write:packages (or only read:packages to read-only) scope.

GitLab

Create a personal access token or go to https://gitlab.com/-/profile/personal_access_tokens with the api (or only read_api to read-only) scope.

Create a .npmrc

Be careful. Do not put your actual token.

GitHub

//npm.pkg.github.com/:_authToken=${NPM_TOKEN}
@NAMESPACE:registry=https://npm.pkg.github.com

GitLab

@scope:registry=https://your_domain_name/api/v4/projects/your_project_id/packages/npm/
//your_domain_name/api/v4/projects/your_project_id/packages/npm/:_authToken="${NPM_TOKEN}"

Publish

Publish the private node module to the private package registry by using npm publish.

Automate publishing your module

We just published our module manually before. It might be better if we can make it automatically.

GitHub

In GitHub, go to the your repository, Actions, and configure the “Publish Node.js Package to GitHub Packages” workflow. Or visit https://github.com/YOUR-USERNAME/YOUR-REPOSITORY/new/master?filename=.github%2Fworkflows%2Fnpm-publish-github-packages.yml&workflow_template=ci%2Fnpm-publish-github-packages.

Copy and paste the YAML script below then commit change.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
name: Node.js Package

on:
  release:
    types: [created]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npm test

  publish-gpr:
    needs: build
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          registry-url: https://npm.pkg.github.com/
      - run: npm ci
      - run: npm publish
        env:
          NPM_TOKEN: ${{secrets.GITHUB_TOKEN}}

GitLab

In GitLab, we can create a CI/CD job. Include the script below in your existing .gitlab-ci.yml.

1
2
3
4
5
6
7
8
stages:
  - deploy

publish-npm:
  stage: deploy
  image: node:latest
  script:
    - NPM_TOKEN=$CI_JOB_TOKEN npm publish

Semantic Release

Even though we automated the publishing, we still created the version manually in the package.json. If you hate this job, you can try semantic-release.

Sorry, I can't give any real examples, because I made them private. Hehe. Feel free to comment for any feedback.

References

Related Articles