Hosting Your Private NPM Packages on GitLab's Private NPM Registry

Last updated on July 21, 2021

You've got some shared front-end code that you would like to share between different projects. You want to abstract this shared code out into a library, thereby making it easy to install, share, or even pick-and-choose which JS modules to import.

Sounds enticing, right? Yes, it most certainly is. There are many benefits of doing so, such as separation of concerns, code organization, etc… So you're convinced. You're sold. You head to the GitLab NPM registry documentation page and immediately, you're discouraged. How in the world are you going to get through that wall of text?!?

If only someone wrote a quickstart guide that GitLab missed out…

Pre-requisites

You will first need:

  1. a GitLab project under a Group Namespace.
  2. (optional) a GitLab Runner. Using GitLab's shared runners are fine.
  3. working JS code within the project that you can use and verify that it works. You should also have your build script, if any.

Add a Build Stage to Your .gitlab-ci.yml File

image: node:14

# Cache our node_modules between stages so that it runs faster.
cache:
  paths:
    - node_modules/

# declare our stages. If you do not need tests, remove the tests line.
stages:
  - test
  - dist

# This runs before each stage.
before_script:
  - npm i

# Optional, if you have tests that you want to run
test:
  stage: test
  script:
    - npm run test

# Required. Runs a build script and sets up the npmrc to point to the GitLab project.
dist:
  stage: dist
  script:
    - npm run build
    - touch .npmrc
    - |
      {
        echo "@${CI_PROJECT_ROOT_NAMESPACE}:registry=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/npm/"
        echo "${CI_API_V4_URL#https?}/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=\${CI_JOB_TOKEN}"
      } | tee --append .npmrc
    - npm publish
  rules:
    - if: $CI_COMMIT_BRANCH == "master"
  artifacts:
    paths:
      - dist/

The dist stage populates a local .npmrc file in the build job, pointing NPM to your project's package registry. Note that there is not for instance-level registry publishing.

The build script essentially appends 2 lines to the .npmrc file. The first line will ensure that the @your-project namespace points to the GitLab project's registry endpoint. The second line will ensure that authentication is set up for your build job, allow it to connect to the registry through the GitLab job.

Update Your package.json

This step is important. If not done, npm will attempt to publish to the public registry, which is not what we are trying to do.

{
  "name": "@my-project/core",
  "version": "0.1.1",
  "description": "",
  ...
}

What we are doing is setting the scope to @my-project, with a package core under the package scope. Refer to the scope naming conventions documentation for specifics on how to map your nested GitLab project to the proper package name format.

Push Push Push

Now, push your code to your master branch and watch the build logs. Voila, a private npm package for you to use!

Consume Your Package

Adding the package requires a little work with the GitLab settings. Firstly, create a deploy token in your project settings. Ensure that it has the read_package_registry and optionally the write_package_registry permission.

Thereafter, add in the following two lines to your ~/.npmrc file located in your home folder.

@my-project:registry=https://gitlab.com/api/v4/packages/npm/
//gitlab.com/api/v4/packages/npm/:_authToken=YOUR_DEPLOY_TOKEN_KEY

Replace YOUR_DEPLOY_TOKEN_KEY with your actual deploy token key. Note that this is the instance-level package registry. For some reason, GitLab allows you to access your package via this endpoint as well.

Then, do a npm install @my-project/core and you're good to go!