I Published My First npm Package — Here's Everything I Wish I Knew
From idea to published package in one guide.
Before You Start
# Check if your package name is taken
npm view your-package-name
# If it returns 404, the name is available!
# Or search:
npm search your-package-name
Project Setup
mkdir my-awesome-package && cd my-awesome-package
npm init -y
# Essential files to create:
touch README.md
touch .gitignore
touch .npmignore
package.json — The Most Important File
{
"name": "@username/my-awesome-package", // Scoped packages are recommended!
"version": "1.0.0",
"description": "A brief description of what this does",
"main": "./dist/index.js", // Entry point
"types": ./dist/index.d.ts", // TypeScript types (important!)
"module": "./dist/index.mjs", // ESM support
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"files": ["dist"], // Only publish dist folder
"scripts": {
"build": "tsc",
"prepublishOnly": "npm run build", // Build before publishing!
"test": "jest",
"lint": "eslint src/"
},
"keywords": ["utility", "javascript"],
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/your-username/my-awesome-package.git"
},
"engines": {
"node": ">=16.0.0"
},
"peerDependencies": {
"react": ">=17.0.0" // Don't install, just require
},
"devDependencies": {
"typescript": "^5.0.0",
"jest": "^29.0.0"
}
}
.npmignore — What NOT to Publish
# .npmignore
src/
test/
*.ts
!*.d.ts
.github/
.vscode/
.env
npm-debug.log*
.DS_Store
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 (!items.length) return '';
if (items.length === 1) return `${prefix}${items[0]}`;
const last = items.pop();
const joined = items.join(separator);
return `${prefix}${joined} and ${last}`;
}
export function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
TypeScript Config
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020"],
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "bundler"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "test"]
}
Tests (Don't Skip This!)
// test/index.test.js
const { formatList, capitalize } = require('../dist');
describe('formatList', () => {
test('returns empty for empty array', () => {
expect(formatList([])).toBe('');
});
test('returns single item', () => {
expect(formatList(['apple'])).toBe('apple');
});
test('joins with separator', () => {
expect(formatList(['apple', 'banana'])).toBe('apple and banana');
});
test('handles three+ items', () => {
expect(formatList(['a', 'b', 'c'])).toBe('a, b and c');
});
test('respects prefix option', () => {
expect(formatList(['x'], { prefix: '- ' })).toBe('- x');
});
});
describe('capitalize', () => {
test('capitalizes first letter', () => {
expect(capitalize('hello')).toBe('Hello');
});
});
README.md Template
# My Awesome Package
> One-line description of what it does
[](https://www.npmjs.com/package/my-awesome-package)
[](LICENSE)
## Installation
\`\`\`bash
npm install my-awesome-package
# or
yarn add my-awesome-package
\`\`\`
## Usage
\`\`\`javascript
import { formatList } from 'my-awesome-package';
const result = formatList(['apple', 'banana', 'cherry']);
// 'apple, banana and cherry'
\`\`\`
## API
### \`formatList(items, options?)\`
Formats an array of strings into a readable list.
| Parameter | Type | Default |
|-----------|------|---------|
| \`items\` | \`string[]\` | Required |
| \`options.prefix\` | \`string\` | \`''\` |
| \`options.separator\` | \`string\` | \`', '\` |
## License
MIT © [Your Name](https://github.com/your-username)
Publishing Steps
# 1. Build
npm run build
# 2. Test
npm test
# 3. Dry run (see what would be published)
npm pack --dry-run
# 4. Login (first time only)
npm login
# 5. Publish!
npm publish --access public
# For scoped packages (@username/pkg), use --access public
# For beta/pre-release: npm publish --tag next
Versioning (Semantic Versioning)
# Patch: Bug fixes (no breaking changes)
npm version patch # 1.0.0 → 1.0.1
# Minor: New features (backward compatible)
npm version minor # 1.0.0 → 1.1.0
# Major: Breaking changes
npm version major # 1.0.0 → 2.0.0
# Each command also commits + tags in git!
After Publishing
# Your package is live at:
# https://www.npmjs.com/package/my-awesome-package
# Install it anywhere:
npm install my-awesome-package
# Update later:
npm update my-awesome-package
Pro Tips
-
Use scoped packages:
@username/package-nameavoids naming conflicts - Always include TypeScript types: Makes your package usable by TS developers
-
Support both ESM and CJS: Use
exportsfield in package.json - Write tests: Even basic tests catch regressions
-
Use
prepublishOnly: Ensures code is built before every publish -
Keep
filesminimal: Only publish what consumers need (dist/) - Add CI/CD: Auto-publish on git tag push
- Document everything: README is your landing page on npm
Have you published an npm package? Share your experience below!
Follow @armorbreak for more developer content.
Top comments (0)