Package Management


https://docs.npmjs.com/cli/v11/configuring-npm/package-json
https://docs.npmjs.com/cli/v11/using-npm/config
https://docs.npmjs.com/cli/v11/configuring-npm/npmrc
https://docs.npmjs.com/cli/v11/commands/npm-ci
https://docs.npmjs.com/cli/v11/commands/npm-publish
https://docs.npmjs.com/cli/v11/commands/npm-pack
https://nodejs.org/api/packages.html

1. Important Points#

TypeScript package management:
    npm is the default package manager in Node.js ecosystem
    package.json defines package metadata and scripts
    package-lock.json locks exact dependency versions
    npm ci should be used in CI/container builds
    dependencies and devDependencies must be separated
    private registry auth should not be committed

production rules:
    commit package-lock.json
    use npm ci
    use private registry or proxy registry in enterprise
    keep auth token in secret manager / CI secret
    do not bake .npmrc token into Docker image

2. package.json#

{
  "name": "@my-org/order-client",
  "version": "1.0.0",
  "type": "module",
  "private": false,
  "description": "Order API client",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js"
    }
  },
  "files": [
    "dist",
    "README.md"
  ],
  "scripts": {
    "build": "tsc",
    "typecheck": "tsc --noEmit",
    "prepack": "npm run build",
    "pack": "npm pack",
    "publish:dry": "npm publish --dry-run",
    "publish:registry": "npm publish --access restricted"
  },
  "dependencies": {
    "zod": "latest"
  },
  "devDependencies": {
    "typescript": "latest",
    "@types/node": "latest"
  },
  "engines": {
    "node": ">=20"
  },
  "publishConfig": {
    "access": "restricted",
    "registry": "https://registry.npmjs.org/"
  }
}
important fields:
    name:
        scoped package uses @org/name

    type:
        module means ESM package

    main:
        runtime entrypoint

    types:
        TypeScript declaration entrypoint

    exports:
        public package entrypoints

    files:
        package allowlist
        avoid publishing src / config / test accidentally

    prepack:
        runs before npm pack and npm publish

    publishConfig:
        default registry and access for publish

3. Install Dependencies#

npm install zod
npm install -D typescript @types/node
npm ci
npm install:
    local development
    updates package-lock.json when dependency changes

npm ci:
    CI / Docker build
    requires package-lock.json
    clean reproducible install

4. npm Registry#

default registry#

npm config get registry
npm config set registry https://registry.npmjs.org/

project .npmrc#

registry=https://registry.npmjs.org/

@my-org:registry=https://npm.pkg.github.com/
always-auth=true
project .npmrc:
    can be committed if it has no token
    can define registry routing
    should not contain real credentials

user .npmrc#

//npm.pkg.github.com/:_authToken=${NPM_TOKEN}
user .npmrc:
    usually stored at ~/.npmrc
    should contain personal or CI token
    should not be committed

5. Private Registry Auth#

npm token#

npm login --registry=https://registry.npmjs.org/
export NPM_TOKEN="***"
npm config set //registry.npmjs.org/:_authToken "$NPM_TOKEN"

scoped private registry#

@my-org:registry=https://npm.pkg.github.com/
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
always-auth=true

CI#

cat > .npmrc <<'EOF'
@my-org:registry=https://npm.pkg.github.com/
//npm.pkg.github.com/:_authToken=${NPM_TOKEN}
always-auth=true
EOF

npm ci
CI rule:
    token comes from CI secret
    create .npmrc during job
    delete it after install if artifact may be reused

6. TLS / CA#

npm CA config#

strict-ssl=true
cafile=/etc/ssl/private/company-ca.pem

environment variables#

export NODE_EXTRA_CA_CERTS=/etc/ssl/private/company-ca.pem
export npm_config_cafile=/etc/ssl/private/company-ca.pem
export npm_config_strict_ssl=true
when needed:
    private registry uses company CA
    corporate proxy intercepts TLS
    internal package registry has private certificate chain

do not:
    set strict-ssl=false in production
    set NODE_TLS_REJECT_UNAUTHORIZED=0 in production

Docker build with CA#

FROM node:22-bookworm-slim

COPY certs/company-ca.pem /usr/local/share/ca-certificates/company-ca.crt
RUN update-ca-certificates

ENV NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/company-ca.crt \
    npm_config_cafile=/usr/local/share/ca-certificates/company-ca.crt \
    npm_config_strict_ssl=true
note:
    certificate file is not secret if it is a public CA certificate
    private key must never be copied into image

7. Docker With Private Packages#

# syntax=docker/dockerfile:1

FROM node:22-bookworm-slim AS deps
WORKDIR /app

COPY package.json package-lock.json ./
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci
docker build \
  --secret id=npmrc,src="$HOME/.npmrc" \
  -t order-api:local .
why:
    npm token is available only during RUN
    token is not copied into image layer
    safer than COPY .npmrc

8. Create A Package#

source#

src
├── index.ts
└── OrderClient.ts
// src/OrderClient.ts
export type CreateOrderRequest = {
  userId: string;
  amount: number;
};

export type Order = {
  orderId: string;
  userId: string;
  amount: number;
  status: "PENDING" | "PAID" | "CANCELLED";
};

export class OrderClient {
  constructor(private readonly baseUrl: string) {}

  async createOrder(request: CreateOrderRequest): Promise<Order> {
    const response = await fetch(`${this.baseUrl}/orders`, {
      method: "POST",
      headers: {
        "content-type": "application/json"
      },
      body: JSON.stringify(request)
    });

    if (!response.ok) {
      throw new Error(`create order failed: ${response.status}`);
    }

    return response.json() as Promise<Order>;
  }
}
// src/index.ts
export { OrderClient } from "./OrderClient.js";
export type { CreateOrderRequest, Order } from "./OrderClient.js";

tsconfig for package#

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "rootDir": "src",
    "outDir": "dist",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "strict": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*.ts"]
}
declaration:
    generate .d.ts files for package consumers

exports:
    control public import path

9. Pack And Publish#

preview package#

npm run build
npm pack --dry-run
npm pack

install local tarball#

npm install ../order-client/my-org-order-client-1.0.0.tgz

publish#

npm publish --access restricted
publish checklist:
    version updated
    package name correct
    files allowlist reviewed
    dist generated
    .d.ts generated
    npm pack --dry-run reviewed
    registry and access reviewed

10. Monorepo#

{
  "name": "order-platform",
  "private": true,
  "workspaces": [
    "packages/*",
    "apps/*"
  ]
}
workspace:
    multiple packages in one repo
    shared lockfile
    local package linking

common structure:
    packages/order-client
    packages/config
    apps/order-api
    apps/order-worker

11. Production Checklist#

install:
    package-lock.json committed
    npm ci used in CI
    registry pinned if enterprise requires it
    token stored in secret manager

security:
    no token in committed .npmrc
    no token copied into Docker image
    strict-ssl enabled
    company CA configured when needed

publish:
    package has exports
    package has types
    files allowlist reviewed
    npm pack --dry-run reviewed
    publishConfig reviewed