Building and Publishing an NPM Package
Creating your own NPM package allows you to share reusable code with the world, contribute to the open-source ecosystem, and establish your presence as a developer. This comprehensive guide will walk you through every step of building a professional-grade NPM package.
Why Create an NPM Package?
Benefits of Publishing Packages
- Code Reusability: Use your code across multiple projects
- Community Contribution: Help other developers solve problems
- Portfolio Building: Showcase your skills to potential employers
- Version Control: Maintain and update code systematically
- Collaboration: Allow others to contribute and improve your work
When to Create a Package
Create a package when you:
- Have code you use repeatedly across projects
- Solve a problem that others might face
- Want to open-source a tool or utility
- Need to share internal libraries across teams
- Want to learn about package development
Planning Your Package
Choosing a Package Name
Your package name must be:
- Unique: Check availability on npmjs.com
- Descriptive: Clearly indicate what the package does
- Lowercase: NPM package names are case-insensitive
- URL-safe: No spaces, only hyphens allowed
# Check if name is available
npm view package-name-here
# If not found, it's available!
# Error: npm ERR! 404 'package-name-here' is not in this registry.
Naming Strategies
// Good names (descriptive, clear)
'date-formatter'
'json-validator'
'log-colorizer'
'express-rate-limiter'
// Scoped packages (for personal/organization use)
'@username/utilities'
'@company/api-client'
'@myorg/design-system'
Scoped packages (starting with @) allow you to namespace your packages and avoid naming conflicts. They’re free for public packages!
Defining Package Requirements
Before coding, define:
- Purpose: What problem does it solve?
- Target Audience: Who will use it?
- Dependencies: What external packages are needed?
- API Design: How will users interact with it?
- Compatibility: Which Node versions will it support?
Project Structure
Basic Package Structure
my-awesome-package/
├── src/ # Source code
│ ├── index.js # Main entry point
│ ├── utils/ # Utility functions
│ └── validators/ # Validation logic
├── test/ # Test files
│ ├── index.test.js
│ └── utils.test.js
├── examples/ # Usage examples
│ └── basic-usage.js
├── .gitignore # Git ignore rules
├── .npmignore # NPM ignore rules
├── LICENSE # License file
├── README.md # Documentation
├── CHANGELOG.md # Version history
└── package.json # Package manifest
Advanced Package Structure
my-library/
├── src/
│ ├── index.js # Public API
│ ├── core/ # Core functionality
│ │ ├── parser.js
│ │ └── validator.js
│ ├── utils/ # Helper functions
│ │ ├── formatters.js
│ │ └── constants.js
│ └── types/ # TypeScript definitions
│ └── index.d.ts
├── dist/ # Compiled/bundled output
│ ├── index.js # CommonJS
│ ├── index.esm.js # ES Module
│ └── index.d.ts # Type definitions
├── test/
│ ├── unit/ # Unit tests
│ ├── integration/ # Integration tests
│ └── fixtures/ # Test data
├── docs/ # Additional documentation
│ ├── api.md
│ └── examples.md
├── scripts/ # Build/utility scripts
│ ├── build.js
│ └── release.js
├── .github/ # GitHub configuration
│ ├── workflows/
│ │ └── ci.yml
│ └── CONTRIBUTING.md
├── .eslintrc.json # ESLint config
├── .prettierrc # Prettier config
├── jest.config.js # Jest config
├── tsconfig.json # TypeScript config
├── rollup.config.js # Bundler config
└── package.json
Initializing Your Package
Step 1: Create Project Directory
mkdir string-manipulator
cd string-manipulator
Step 2: Initialize package.json
You’ll be prompted for:
- package name: string-manipulator
- version: 1.0.0
- description: A lightweight utility for advanced string manipulation
- entry point: index.js
- test command: jest
- git repository: https://github.com/yourusername/string-manipulator
- keywords: string, utility, manipulation, text
- author: Your Name
your.email@example.com
- license: MIT
Step 3: Complete package.json
{
"name": "string-manipulator",
"version": "1.0.0",
"description": "A lightweight utility for advanced string manipulation",
"main": "index.js",
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix",
"format": "prettier --write \"src/**/*.js\"",
"prepublishOnly": "npm test && npm run lint"
},
"keywords": [
"string",
"utility",
"manipulation",
"text",
"format"
],
"author": "Your Name <your.email@example.com>",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/yourusername/string-manipulator"
},
"bugs": {
"url": "https://github.com/yourusername/string-manipulator/issues"
},
"homepage": "https://github.com/yourusername/string-manipulator#readme",
"engines": {
"node": ">=14.0.0"
},
"files": [
"index.js",
"src/",
"README.md",
"LICENSE"
],
"devDependencies": {
"eslint": "^8.50.0",
"jest": "^29.7.0",
"prettier": "^3.0.3"
},
"dependencies": {}
}
Writing Package Code
Example: String Manipulator Package
src/index.js (Main Entry Point)
/**
* String Manipulator - A utility library for advanced string operations
* @module string-manipulator
*/
/**
* Converts a string to title case
* @param {string} str - The input string
* @returns {string} The title-cased string
* @example
* toTitleCase('hello world') // 'Hello World'
*/
function toTitleCase(str) {
if (typeof str !== 'string') {
throw new TypeError('Expected a string');
}
return str
.toLowerCase()
.split(' ')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}
/**
* Converts a string to camelCase
* @param {string} str - The input string
* @returns {string} The camelCased string
* @example
* toCamelCase('hello world') // 'helloWorld'
*/
function toCamelCase(str) {
if (typeof str !== 'string') {
throw new TypeError('Expected a string');
}
return str
.toLowerCase()
.replace(/[^a-zA-Z0-9]+(.)/g, (_, char) => char.toUpperCase());
}
/**
* Converts a string to snake_case
* @param {string} str - The input string
* @returns {string} The snake_cased string
* @example
* toSnakeCase('Hello World') // 'hello_world'
*/
function toSnakeCase(str) {
if (typeof str !== 'string') {
throw new TypeError('Expected a string');
}
return str
.replace(/([A-Z])/g, '_$1')
.toLowerCase()
.replace(/\s+/g, '_')
.replace(/^_/, '');
}
/**
* Converts a string to kebab-case
* @param {string} str - The input string
* @returns {string} The kebab-cased string
* @example
* toKebabCase('Hello World') // 'hello-world'
*/
function toKebabCase(str) {
if (typeof str !== 'string') {
throw new TypeError('Expected a string');
}
return str
.replace(/([A-Z])/g, '-$1')
.toLowerCase()
.replace(/\s+/g, '-')
.replace(/^-/, '');
}
/**
* Truncates a string to a specified length
* @param {string} str - The input string
* @param {number} length - Maximum length
* @param {string} [suffix='...'] - Suffix to append if truncated
* @returns {string} The truncated string
* @example
* truncate('Hello World', 8) // 'Hello...'
*/
function truncate(str, length, suffix = '...') {
if (typeof str !== 'string') {
throw new TypeError('Expected a string');
}
if (str.length <= length) {
return str;
}
return str.slice(0, length - suffix.length) + suffix;
}
/**
* Reverses a string
* @param {string} str - The input string
* @returns {string} The reversed string
* @example
* reverse('hello') // 'olleh'
*/
function reverse(str) {
if (typeof str !== 'string') {
throw new TypeError('Expected a string');
}
return str.split('').reverse().join('');
}
/**
* Counts the words in a string
* @param {string} str - The input string
* @returns {number} The word count
* @example
* wordCount('Hello world') // 2
*/
function wordCount(str) {
if (typeof str !== 'string') {
throw new TypeError('Expected a string');
}
return str.trim().split(/\s+/).filter(Boolean).length;
}
/**
* Capitalizes the first letter of a string
* @param {string} str - The input string
* @returns {string} The capitalized string
* @example
* capitalize('hello') // 'Hello'
*/
function capitalize(str) {
if (typeof str !== 'string') {
throw new TypeError('Expected a string');
}
return str.charAt(0).toUpperCase() + str.slice(1);
}
// Export all functions
module.exports = {
toTitleCase,
toCamelCase,
toSnakeCase,
toKebabCase,
truncate,
reverse,
wordCount,
capitalize
};
For maximum compatibility, support both CommonJS and ES Modules:
index.js (Dual Export)
// Your functions here...
// CommonJS export
module.exports = {
toTitleCase,
toCamelCase,
// ... other functions
};
// ES Module export (if supported)
if (typeof exports !== 'undefined') {
exports.toTitleCase = toTitleCase;
exports.toCamelCase = toCamelCase;
// ... other exports
}
package.json (ES Module Support)
{
"name": "string-manipulator",
"version": "1.0.0",
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"exports": {
".": {
"require": "./dist/index.cjs",
"import": "./dist/index.mjs",
"types": "./dist/index.d.ts"
}
},
"type": "module"
}
Testing Your Package
Setting Up Jest
npm install --save-dev jest
test/index.test.js
const {
toTitleCase,
toCamelCase,
toSnakeCase,
toKebabCase,
truncate,
reverse,
wordCount,
capitalize
} = require('../src/index');
describe('String Manipulator', () => {
describe('toTitleCase', () => {
it('should convert string to title case', () => {
expect(toTitleCase('hello world')).toBe('Hello World');
expect(toTitleCase('THE QUICK BROWN FOX')).toBe('The Quick Brown Fox');
});
it('should handle single word', () => {
expect(toTitleCase('hello')).toBe('Hello');
});
it('should throw error for non-string input', () => {
expect(() => toTitleCase(123)).toThrow(TypeError);
expect(() => toTitleCase(null)).toThrow(TypeError);
});
});
describe('toCamelCase', () => {
it('should convert string to camelCase', () => {
expect(toCamelCase('hello world')).toBe('helloWorld');
expect(toCamelCase('the-quick-brown-fox')).toBe('theQuickBrownFox');
});
it('should handle already camelCased strings', () => {
expect(toCamelCase('helloWorld')).toBe('helloworld');
});
});
describe('toSnakeCase', () => {
it('should convert string to snake_case', () => {
expect(toSnakeCase('Hello World')).toBe('hello_world');
expect(toSnakeCase('theQuickBrownFox')).toBe('the_quick_brown_fox');
});
});
describe('toKebabCase', () => {
it('should convert string to kebab-case', () => {
expect(toKebabCase('Hello World')).toBe('hello-world');
expect(toKebabCase('theQuickBrownFox')).toBe('the-quick-brown-fox');
});
});
describe('truncate', () => {
it('should truncate string to specified length', () => {
expect(truncate('Hello World', 8)).toBe('Hello...');
expect(truncate('Hello', 10)).toBe('Hello');
});
it('should use custom suffix', () => {
expect(truncate('Hello World', 8, '…')).toBe('Hello W…');
});
});
describe('reverse', () => {
it('should reverse a string', () => {
expect(reverse('hello')).toBe('olleh');
expect(reverse('12345')).toBe('54321');
});
});
describe('wordCount', () => {
it('should count words in a string', () => {
expect(wordCount('Hello world')).toBe(2);
expect(wordCount('The quick brown fox')).toBe(4);
});
it('should handle extra spaces', () => {
expect(wordCount(' hello world ')).toBe(2);
});
});
describe('capitalize', () => {
it('should capitalize first letter', () => {
expect(capitalize('hello')).toBe('Hello');
expect(capitalize('world')).toBe('World');
});
});
});
Running Tests
# Run tests once
npm test
# Run tests in watch mode
npm run test:watch
# Generate coverage report
npm run test:coverage
Coverage Report Example
-----------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
index.js | 100 | 100 | 100 | 100 |
-----------------|---------|----------|---------|---------|-------------------
Documentation
README.md Structure
# String Manipulator
> A lightweight, zero-dependency utility library for advanced string manipulation in Node.js and browsers.
[](https://www.npmjs.com/package/string-manipulator)
[](https://github.com/yourusername/string-manipulator/actions)
[](https://codecov.io/gh/yourusername/string-manipulator)
[](LICENSE)
## Features
✅ Zero dependencies
✅ Lightweight (< 2KB gzipped)
✅ TypeScript support
✅ Full test coverage
✅ Works in Node.js and browsers
✅ ESM and CommonJS support
## Installation
```bash
npm install string-manipulator
Or with Yarn:
yarn add string-manipulator
Usage
const { toTitleCase, toCamelCase, truncate } = require('string-manipulator');
// Title Case
toTitleCase('hello world'); // 'Hello World'
// Camel Case
toCamelCase('hello world'); // 'helloWorld'
// Truncate
truncate('Hello World', 8); // 'Hello...'
API
toTitleCase(str)
Converts a string to Title Case.
Parameters:
str (string): The input string
Returns: string
Example:
toTitleCase('hello world'); // 'Hello World'
toCamelCase(str)
Converts a string to camelCase.
Parameters:
str (string): The input string
Returns: string
Example:
toCamelCase('hello world'); // 'helloWorld'
[… continue with all other functions …]
Browser Usage
<script src="https://unpkg.com/string-manipulator"></script>
<script>
console.log(StringManipulator.toTitleCase('hello world'));
</script>
TypeScript
import { toTitleCase, toCamelCase } from 'string-manipulator';
const title: string = toTitleCase('hello world');
Contributing
Contributions are welcome! Please read CONTRIBUTING.md for details.
License
MIT © Your Name
Changelog
See CHANGELOG.md for version history.
### CHANGELOG.md
```markdown
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [1.0.0] - 2024-01-15
### Added
- Initial release
- `toTitleCase()` function
- `toCamelCase()` function
- `toSnakeCase()` function
- `toKebabCase()` function
- `truncate()` function
- `reverse()` function
- `wordCount()` function
- `capitalize()` function
### Changed
- Nothing
### Fixed
- Nothing
## [0.1.0] - 2024-01-10
### Added
- Beta release
- Basic string manipulation functions
Configuring Package Files
.gitignore
# Dependencies
node_modules/
# Test coverage
coverage/
# Build output
dist/
*.log
# Environment
.env
.env.local
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
.npmignore
# Source files (if you build to dist/)
src/
test/
# Development files
.github/
.vscode/
coverage/
*.test.js
jest.config.js
.eslintrc.json
.prettierrc
# Documentation (keep README!)
docs/
examples/
# Git
.git/
.gitignore
If .npmignore exists, it overrides .gitignore. Make sure not to accidentally exclude important files!
LICENSE (MIT Example)
MIT License
Copyright (c) 2024 Your Name
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Testing Locally
Using npm link
Test your package locally before publishing:
# In your package directory
npm link
# In your test project
npm link string-manipulator
# Now you can require it
const { toTitleCase } = require('string-manipulator');
Using Local Path
# In your test project
npm install ../path/to/string-manipulator
# Or in package.json
{
"dependencies": {
"string-manipulator": "file:../string-manipulator"
}
}
Test in Isolation
Create a test directory to verify your package works as expected:
mkdir test-package
cd test-package
npm init -y
npm install ../string-manipulator
// test.js
const { toTitleCase } = require('string-manipulator');
console.log(toTitleCase('hello world')); // Should output: Hello World
Version Management
Semantic Versioning (SemVer)
Format: MAJOR.MINOR.PATCH
- MAJOR: Breaking changes (1.0.0 → 2.0.0)
- MINOR: New features, backward-compatible (1.0.0 → 1.1.0)
- PATCH: Bug fixes, backward-compatible (1.0.0 → 1.0.1)
Updating Versions
# Patch version (1.0.0 → 1.0.1)
npm version patch
# Minor version (1.0.0 → 1.1.0)
npm version minor
# Major version (1.0.0 → 2.0.0)
npm version major
# Specific version
npm version 1.2.3
# Pre-release versions
npm version prepatch # 1.0.0 → 1.0.1-0
npm version preminor # 1.0.0 → 1.1.0-0
npm version premajor # 1.0.0 → 2.0.0-0
Version Lifecycle Scripts
{
"scripts": {
"preversion": "npm test",
"version": "npm run build && git add -A dist",
"postversion": "git push && git push --tags"
}
}
Publishing Your Package
Step 1: Create NPM Account
# Sign up at npmjs.com first, then:
npm login
# Verify login
npm whoami
Step 2: Prepare for Publishing
# Run tests
npm test
# Check what will be published
npm pack --dry-run
# Or create actual tarball
npm pack
Step 3: Publish
# Public package
npm publish
# Scoped package (public)
npm publish --access public
# Test run (doesn't actually publish)
npm publish --dry-run
Step 4: Verify Publication
# Check package info
npm view string-manipulator
# Install and test
npm install string-manipulator
Publishing Scoped Packages
Public Scoped Package
# Package name: @username/string-manipulator
npm publish --access public
Private Scoped Package
# Requires paid NPM account
npm publish --access restricted
package.json for Scoped Package
{
"name": "@yourusername/string-manipulator",
"version": "1.0.0",
"publishConfig": {
"access": "public"
}
}
Continuous Integration
GitHub Actions Workflow
Create .github/workflows/ci.yml:
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x, 16.x, 18.x, 20.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests
run: npm test
- name: Generate coverage
run: npm run test:coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: ./coverage/coverage-final.json
Automated Publishing
Create .github/workflows/publish.yml:
name: Publish Package
on:
release:
types: [created]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18.x'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm test
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Advanced Package Features
Adding TypeScript Definitions
Even if your package is written in JavaScript, provide TypeScript definitions:
index.d.ts
/**
* Converts a string to title case
*/
export function toTitleCase(str: string): string;
/**
* Converts a string to camelCase
*/
export function toCamelCase(str: string): string;
/**
* Converts a string to snake_case
*/
export function toSnakeCase(str: string): string;
/**
* Converts a string to kebab-case
*/
export function toKebabCase(str: string): string;
/**
* Truncates a string to specified length
*/
export function truncate(str: string, length: number, suffix?: string): string;
/**
* Reverses a string
*/
export function reverse(str: string): string;
/**
* Counts words in a string
*/
export function wordCount(str: string): number;
/**
* Capitalizes first letter
*/
export function capitalize(str: string): string;
package.json
{
"types": "./index.d.ts",
"typings": "./index.d.ts"
}
CLI Support
Make your package executable from command line:
bin/cli.js
#!/usr/bin/env node
const { toTitleCase, toCamelCase } = require('../src/index');
const args = process.argv.slice(2);
if (args.length === 0) {
console.log('Usage: string-manipulator <command> <text>');
console.log('Commands: title, camel, snake, kebab');
process.exit(1);
}
const [command, ...textParts] = args;
const text = textParts.join(' ');
switch (command) {
case 'title':
console.log(toTitleCase(text));
break;
case 'camel':
console.log(toCamelCase(text));
break;
default:
console.log('Unknown command:', command);
process.exit(1);
}
package.json
{
"bin": {
"string-manipulator": "./bin/cli.js"
}
}
Usage
# After npm install -g string-manipulator
string-manipulator title "hello world"
# Output: Hello World
Peer Dependencies
For plugins or extensions that require a host package:
{
"peerDependencies": {
"express": ">=4.0.0"
},
"peerDependenciesMeta": {
"express": {
"optional": false
}
}
}
Optional Dependencies
For packages that enhance functionality but aren’t required:
{
"optionalDependencies": {
"colors": "^1.4.0"
}
}
Package Maintenance
Updating Your Package
# Make changes to code
# Update tests
npm test
# Update version
npm version patch
# Publish update
npm publish
Deprecating Versions
# Deprecate specific version
npm deprecate string-manipulator@1.0.0 "Please upgrade to 1.0.1"
# Deprecate all versions
npm deprecate string-manipulator "Package is no longer maintained"
Unpublishing Packages
# Unpublish specific version (within 72 hours)
npm unpublish string-manipulator@1.0.0
# Unpublish entire package (within 72 hours, if no dependents)
npm unpublish string-manipulator --force
Unpublishing is discouraged as it breaks projects depending on your package. Use deprecation instead.
Security Best Practices
function toTitleCase(str) {
// Always validate input
if (typeof str !== 'string') {
throw new TypeError('Expected a string');
}
// Sanitize if needed
str = str.trim();
// Implement function logic
// ...
}
2. Avoid eval() and Function()
// BAD - Never do this
function execute(code) {
eval(code);
}
// GOOD - Use safe alternatives
function execute(data) {
return JSON.parse(data);
}
3. Keep Dependencies Minimal
# Regularly audit dependencies
npm audit
# Fix vulnerabilities automatically
npm audit fix
# Check for outdated packages
npm outdated
4. Use .npmignore
Prevent sensitive files from being published:
.env
.env.*
config/secrets.json
private/
*.key
*.pem
5. Enable 2FA on NPM
# Enable two-factor authentication
npm profile enable-2fa auth-and-writes
Bundle Size Optimization
// Use tree-shaking friendly exports
export function toTitleCase(str) { /* ... */ }
export function toCamelCase(str) { /* ... */ }
// Instead of
module.exports = { toTitleCase, toCamelCase };
Minimize Dependencies
{
"dependencies": {
// Keep this list as small as possible
},
"devDependencies": {
// Tools only needed for development
}
}
Use Lazy Loading
// Load heavy dependencies only when needed
function complexOperation() {
const heavyLib = require('heavy-library');
return heavyLib.process();
}
Marketing Your Package
1. Write Great Documentation
- Clear README with examples
- API documentation
- Usage examples
- Troubleshooting guide
2. Add Badges
[](https://npmjs.com/package/string-manipulator)
[](https://npmjs.com/package/string-manipulator)
[](https://github.com/user/repo/actions)
[](https://codecov.io/gh/user/repo)
3. Choose Good Keywords
{
"keywords": [
"string",
"text",
"manipulation",
"utility",
"format",
"convert",
"camelcase",
"kebabcase"
]
}
4. Create Examples
examples/
├── basic-usage.js
├── advanced-usage.js
└── browser-usage.html
- Tweet about your package
- Post on Reddit (r/javascript, r/node)
- Write blog post
- Create demo on CodeSandbox
Common Pitfalls
1. Not Testing Before Publishing
Always run tests before publishing:
{
"scripts": {
"prepublishOnly": "npm test && npm run lint"
}
}
2. Including Unnecessary Files
Use files field or .npmignore:
{
"files": [
"index.js",
"src/",
"README.md",
"LICENSE"
]
}
3. Breaking Changes Without Major Version
Follow semantic versioning strictly.
4. No TypeScript Definitions
Always provide TypeScript definitions for better DX.
5. Poor Error Messages
// BAD
throw new Error('Invalid');
// GOOD
throw new TypeError(`Expected string, received ${typeof input}`);
Real-World Example: Complete Package
Let’s look at a complete, production-ready package structure:
Final Directory Structure
string-manipulator/
├── src/
│ ├── index.js
│ ├── converters.js
│ └── validators.js
├── test/
│ ├── index.test.js
│ ├── converters.test.js
│ └── validators.test.js
├── examples/
│ ├── basic.js
│ └── advanced.js
├── bin/
│ └── cli.js
├── .github/
│ └── workflows/
│ ├── ci.yml
│ └── publish.yml
├── index.d.ts
├── .gitignore
├── .npmignore
├── .eslintrc.json
├── jest.config.js
├── LICENSE
├── README.md
├── CHANGELOG.md
├── CONTRIBUTING.md
└── package.json
Complete package.json
{
"name": "string-manipulator",
"version": "1.0.0",
"description": "A lightweight utility for advanced string manipulation",
"main": "src/index.js",
"types": "index.d.ts",
"bin": {
"string-manipulator": "./bin/cli.js"
},
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"lint": "eslint src/ test/",
"lint:fix": "eslint src/ test/ --fix",
"format": "prettier --write \"**/*.{js,json,md}\"",
"prepublishOnly": "npm test && npm run lint",
"preversion": "npm test",
"postversion": "git push && git push --tags"
},
"keywords": [
"string",
"text",
"manipulation",
"utility",
"format",
"camelcase",
"kebabcase",
"snakecase"
],
"author": "Your Name <your.email@example.com>",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/yourusername/string-manipulator.git"
},
"bugs": {
"url": "https://github.com/yourusername/string-manipulator/issues"
},
"homepage": "https://github.com/yourusername/string-manipulator#readme",
"engines": {
"node": ">=14.0.0"
},
"files": [
"src/",
"bin/",
"index.d.ts",
"README.md",
"LICENSE"
],
"devDependencies": {
"@types/node": "^20.8.0",
"eslint": "^8.50.0",
"jest": "^29.7.0",
"prettier": "^3.0.3"
},
"dependencies": {}
}
Summary
Building an NPM package involves:
- Planning: Define purpose, name, and API
- Structure: Organize code logically
- Development: Write clean, tested code
- Documentation: Create comprehensive README
- Testing: Achieve high test coverage
- Configuration: Set up package.json correctly
- Publishing: Share with the community
- Maintenance: Keep package updated and secure
Start small, iterate, and gather feedback. Your first package doesn’t need to be perfect—shipping is more important than perfection!
Next Steps
After publishing your package:
- Monitor GitHub issues and respond to users
- Keep dependencies updated
- Release patches for bugs promptly
- Consider feature requests carefully
- Build a community around your package
- Document breaking changes clearly
- Celebrate your contribution to open source! 🎉