Links#
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 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
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
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