How to Publish Your First npm Package (Step by Step)
Your code could be someone else's dependency. Here's how.
Step 1: Set Up the Project
mkdir my-awesome-package
cd my-awesome-package
npm init -y
# Creates package.json with defaults
{
"name": "@armorbreak/my-awesome-package",
"version": "1.0.0",
"description": "A brief description of what it does",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"type": "module", // ES modules!
"scripts": {
"build": "tsc",
"test": "vitest",
"lint": "eslint src/",
"prepublishOnly": "npm run build && npm test"
},
"keywords": ["utility", "javascript"],
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/yourname/my-awesome-package.git"
},
"files": [ // What gets published!
"dist",
"README.md"
]
}
Step 2: Write the Code
// src/index.ts
export interface Options {
prefix?: string;
separator?: string;
}
export function formatList(items: string[], options: Options = {}): string {
const { prefix = '', separator = ', ' } = options;
if (!Array.isArray(items)) {
throw new TypeError('Expected an array');
}
if (items.length === 0) return '';
if (items.length === 1) return `${prefix}${items[0]}`;
const allButLast = items.slice(0, -1).join(separator);
const last = items[items.length - 1];
return `${prefix}${allButLast} and ${last}`;
}
// Named export for tree-shaking
export { formatList as default };
Step 3: Add Tests
// src/index.test.ts
import { describe, it, expect } from 'vitest';
import { formatList } from './index';
describe('formatList', () => {
it('formats empty array', () => {
expect(formatList([])).toBe('');
});
it('formats single item', () => {
expect(formatList(['apple'])).toBe('apple');
});
it('formats two items', () => {
expect(formatList(['apple', 'banana'])).toBe('apple and banana');
});
it('formats multiple items with custom separator', () => {
expect(formatList(['a', 'b', 'c'], { separator: '; '}))
.toBe('a; b and c');
});
it('adds prefix when specified', () => {
expect(formatList(['x', 'y'], { prefix: 'Items: ' }))
.toBe('Items: x and y');
});
it('throws on non-array input', () => {
expect(() => formatList(null as any)).toThrow(TypeError);
});
});
Step 4: Build Configuration
// tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "node",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}
Step 5: Documentation
<!-- README.md -->
# My Awesome Package 🚀
A brief description of what your package does.
## Installation
\`\`\`bash
npm install @armorbreak/my-awesome-package
\`\`\`
## Usage
\`\`\`javascript
import { formatList } from '@armorbreak/my-awesome-package';
formatList(['apple', 'banana', 'cherry']);
// → "apple, banana and cherry"
formatList(['x', 'y'], { prefix: 'I like ', separator: ' & ' });
// → "I like x & y"
\`\`\`
## API
### \`formatList(items, options?)\`
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| \`items\` | \`string[]\` | required | Array of strings |
| \`options.prefix\` | \`string\` | \`''\` | Text before the list |
| \`options.separator\` | \`string\` | \`', '\` | Between items |
**Returns:** \`string\`
## License
MIT © [Your Name](https://github.com/yourname)
Step 6: Prepare for Publishing
# Login to npm (one-time)
npm login
# Build
npm run build
# Test locally first
npm link # Create global symlink
# In another project:
npm link @armorbreak/my-awesome-package # Use local version
# Dry run — see what would be published without actually publishing
npm publish --dry run
Step 7: Publish!
# First version
npm publish --access public # Required for scoped packages (@scope/name)
# Patch update (bug fix)
npm version patch # 1.0.0 → 1.0.1
npm publish # Auto-incremented
# Minor update (new feature)
npm version minor # 1.0.1 → 1.1.0
npm publish
# Major update (breaking change)
npm version major # 1.1.0 → 2.0.0
npm publish
Pro Tips
# Use .npmignore to exclude files from publishing
node_modules/
test/
*.ts
!.d.ts
.github/
.vscode/
# Two-factor auth (recommended!)
npm profile enable-2fa
# Automated releases with GitHub Actions
# .github/workflows/publish.yml triggers on version tag push
# Check if a name is taken
npm view package-name # Shows info if exists, 404 if available
Versioning Cheat Sheet
1.0.0
│ │ └── Patch: Bug fixes (no API changes)
│ └──── Minor: New features (backward compatible)
└────── Major: Breaking changes
Examples:
1.0.0 → 1.0.1 (Fixed typo in README)
1.0.1 → 1.1.0 (Added new option)
1.1.0 → 2.0.0 (Changed function signature)
Have you published an npm package? Share the link below!
Follow @armorbreak for more developer content.
Top comments (0)