Conventional Commits Demo
A demonstration project showcasing conventional commit practices with a simple Express.js API.
Features
- RESTful user API
- Express.js server
- Conventional commit history
- Comprehensive documentation
Getting Started
-
Install dependencies:
bashnpm install -
Start the server:
bashnpm start -
Access the API at http://localhost:3000
API Documentation
See API.md for detailed endpoint documentation.
Development
This project follows Conventional Commits specification for commit messages. EOF
git add README.md git commit -m "docs(readme): enhance project documentation
Add comprehensive setup instructions, feature list, and development guidelines to improve project onboarding."
### Testing Examples
```bash
# Add test file
cat > test.js << EOF
const request = require('supertest');
const app = require('./index');
function runTests() {
console.log('🧪 Running API tests...');
// Note: This is a simplified test example
// In real projects, use proper testing frameworks like Jest or Mocha
const tests = [
{
name: 'GET / should return API info',
test: async () => {
// Simplified test - in real scenario use supertest
console.log('✓ Root endpoint test passed');
return true;
}
},
{
name: 'GET /api/users should return users array',
test: async () => {
console.log('✓ Users endpoint test passed');
return true;
}
}
];
let passed = 0;
let failed = 0;
tests.forEach(async ({ name, test }) => {
try {
await test();
console.log(`✓ ${name}`);
passed++;
} catch (error) {
console.log(`✗ ${name}: ${error.message}`);
failed++;
}
});
console.log(`\nTest Results: ${passed} passed, ${failed} failed`);
if (failed > 0) {
process.exit(1);
}
}
if (require.main === module) {
runTests();
}
module.exports = { runTests };
EOF
git add test.js
git commit -m "test(api): add basic API endpoint tests
Implement test suite for API endpoints including:
- Root endpoint validation
- User endpoints testing
- Test result reporting
Sets foundation for comprehensive test coverage."
Refactoring Examples
# Extract configuration
mkdir config
cat > config/server.js << EOF
module.exports = {
port: process.env.PORT || 3000,
env: process.env.NODE_ENV || 'development',
api: {
version: '1.0.0',
prefix: '/api'
}
};
EOF
# Update main server file
cat > index.js << EOF
const express = require('express');
const config = require('./config/server');
const userRoutes = require('./routes/users');
const app = express();
app.use(express.json());
app.use(`${config.api.prefix}/users`, userRoutes);
app.get('/', (req, res) => {
res.json({
message: 'API Server',
version: config.api.version,
environment: config.env,
endpoints: {
users: `${config.api.prefix}/users`
}
});
});
app.listen(config.port, () => {
console.log(`Server running on port ${config.port} in ${config.env} mode`);
});
module.exports = app;
EOF
git add config/ index.js
git commit -m "refactor(config): extract server configuration
Move server configuration to dedicated config module
for better maintainability and environment management.
Improves code organization and makes configuration
easier to modify across different environments."
Performance Examples
# Add caching middleware
cat > middleware/cache.js << EOF
const cache = new Map();
function cacheMiddleware(duration = 300000) { // 5 minutes default
return (req, res, next) => {
const key = req.originalUrl;
const cached = cache.get(key);
if (cached && Date.now() - cached.timestamp < duration) {
console.log(`Cache hit for ${key}`);
return res.json(cached.data);
}
// Override res.json to cache the response
const originalJson = res.json;
res.json = function(data) {
cache.set(key, {
data: data,
timestamp: Date.now()
});
console.log(`Cache set for ${key}`);
return originalJson.call(this, data);
};
next();
};
}
module.exports = cacheMiddleware;
EOF
# Update user routes to use caching
cat > routes/users.js << EOF
const express = require('express');
const cacheMiddleware = require('../middleware/cache');
const router = express.Router();
// Mock user data
const users = [
{ id: 1, name: 'John Doe', email: 'john@example.com' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com' }
];
// GET /users with caching
router.get('/', cacheMiddleware(60000), (req, res) => {
res.json(users);
});
// GET /users/:id
router.get('/:id', (req, res) => {
const userId = parseInt(req.params.id);
if (isNaN(userId) || userId < 1) {
return res.status(400).json({ error: 'Invalid user ID' });
}
const user = users.find(u => u.id === userId);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
});
module.exports = router;
EOF
git add middleware/ routes/users.js
git commit -m "perf(api): add response caching middleware
Implement in-memory caching for user list endpoint
to reduce response time for frequently accessed data.
Improves API performance by caching responses for 1 minute,
reducing unnecessary data processing on repeated requests."
Breaking Changes
# Make a breaking change to API response format
cat > routes/users.js << EOF
const express = require('express');
const cacheMiddleware = require('../middleware/cache');
const router = express.Router();
// Mock user data
const users = [
{ id: 1, name: 'John Doe', email: 'john@example.com', createdAt: '2024-01-01' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', createdAt: '2024-01-02' }
];
// GET /users with new response format
router.get('/', cacheMiddleware(60000), (req, res) => {
res.json({
success: true,
data: users,
meta: {
total: users.length,
version: '2.0.0'
}
});
});
// GET /users/:id with new response format
router.get('/:id', (req, res) => {
const userId = parseInt(req.params.id);
if (isNaN(userId) || userId < 1) {
return res.status(400).json({
success: false,
error: 'Invalid user ID'
});
}
const user = users.find(u => u.id === userId);
if (!user) {
return res.status(404).json({
success: false,
error: 'User not found'
});
}
res.json({
success: true,
data: user
});
});
module.exports = router;
EOF
git add routes/users.js
git commit -m "feat!: change API response format to include metadata
BREAKING CHANGE: API responses now include success flag and metadata.
Before:
```json
[{"id": 1, "name": "John"}]
After:
{
"success": true,
"data": [{ "id": 1, "name": "John" }],
"meta": { "total": 1, "version": "2.0.0" }
}
This change improves API consistency and provides additional metadata for client applications.
Migration guide available in MIGRATION.md"
## Advanced Conventional Commits
### Scopes
Scopes provide additional context about the change:
```bash
# Component-based scopes
feat(auth): add OAuth2 integration
fix(payment): resolve credit card validation
test(user): add profile update tests
# Layer-based scopes
feat(api): add new endpoint
fix(ui): resolve button alignment
perf(db): optimize query performance
# Feature-based scopes
feat(shopping-cart): add item quantity controls
fix(checkout): resolve payment processing error
Multiple Scopes
# Multiple related scopes
feat(auth,api): add JWT token validation
fix(ui,ux): improve form validation feedback
refactor(db,api): optimize user data queries
Body and Footer
# Detailed commit with body and footer
git commit -m "feat(api): add user profile image upload
Implement multipart form data handling for user profile images.
Includes image validation, resizing, and secure storage.
Supported formats: JPEG, PNG, WebP
Maximum file size: 5MB
Automatic resizing to 300x300 pixels
Closes #45
Reviewed-by: @johndoe
Tested-by: @janedoe"
Co-authored Commits
# Pair programming commit
git commit -m "feat(search): implement full-text search functionality
Add Elasticsearch integration for advanced search capabilities.
Includes fuzzy matching, filters, and result highlighting.
Co-authored-by: Jane Doe <jane@example.com>
Co-authored-by: Bob Smith <bob@example.com>"
Automation and Tooling
Commitizen Setup
# Install commitizen globally
npm install -g commitizen
npm install -g cz-conventional-changelog
# Configure commitizen
echo '{ "path": "cz-conventional-changelog" }' > ~/.czrc
# Or configure per project
npm install --save-dev commitizen cz-conventional-changelog
echo '{ "path": "./node_modules/cz-conventional-changelog" }' > .czrc
# Add to package.json
cat > package.json << EOF
{
"name": "conventional-commits-demo",
"version": "1.0.0",
"scripts": {
"commit": "cz"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"devDependencies": {
"commitizen": "^4.3.0",
"cz-conventional-changelog": "^3.3.0"
}
}
EOF
# Use commitizen for commits
npm run commit
# or
git cz
Commit Linting
# Install commitlint
npm install --save-dev @commitlint/cli @commitlint/config-conventional
# Create commitlint config
cat > .commitlintrc.js << EOF
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
[
'feat',
'fix',
'docs',
'style',
'refactor',
'test',
'chore',
'perf',
'ci',
'build',
'revert'
]
],
'subject-case': [2, 'always', 'lower-case'],
'subject-empty': [2, 'never'],
'subject-full-stop': [2, 'never', '.'],
'header-max-length': [2, 'always', 72]
}
};
EOF
# Add to package.json scripts
"scripts": {
"commitlint": "commitlint --from HEAD~1 --to HEAD --verbose"
}
# Test commitlint
echo "invalid commit message" | npx commitlint
Husky Integration
# Install husky
npm install --save-dev husky
# Initialize husky
npx husky install
# Add commit-msg hook
npx husky add .husky/commit-msg 'npx commitlint --edit $1'
# Add prepare-commit-msg hook for commitizen
npx husky add .husky/prepare-commit-msg 'exec < /dev/tty && node_modules/.bin/cz --hook || true'
# Update package.json
"scripts": {
"prepare": "husky install"
}
Semantic Release
# Install semantic-release
npm install --save-dev semantic-release
# Create release config
cat > .releaserc.json << EOF
{
"branches": ["main"],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
"@semantic-release/npm",
"@semantic-release/github"
]
}
EOF
# Add to package.json
"scripts": {
"semantic-release": "semantic-release"
}
Best Practices
1. Commit Message Guidelines
# DO: Clear, concise, imperative mood
feat: add user authentication
fix: resolve memory leak in parser
docs: update API documentation
# DON'T: Unclear, past tense, too verbose
Added some stuff
Fixed a bug
Updated documentation and also refactored some code and fixed tests
2. Scope Usage
# DO: Consistent, meaningful scopes
feat(auth): add login functionality
feat(auth): add logout functionality
fix(auth): resolve token expiration
# DON'T: Inconsistent or meaningless scopes
feat(stuff): add things
fix(misc): fix issue
feat(component1): add feature
3. Breaking Changes
# DO: Clear breaking change indication
feat!: change API response format
feat(api)!: remove deprecated endpoints
# With detailed explanation
feat!: change user authentication method
BREAKING CHANGE: Replace session-based auth with JWT tokens.
Clients must now include Authorization header with Bearer token.
Migration:
- Update client to use JWT tokens
- Remove session cookie handling
- Add Authorization header to requests
4. Commit Frequency
# DO: Logical, atomic commits
feat: add user model
feat: add user controller
feat: add user routes
test: add user API tests
# DON'T: Massive commits or micro-commits
feat: add entire user management system
fix: typo
fix: another typo
fix: one more typo
Integration with Development Workflow
Feature Branch Workflow
# Start feature branch
git checkout -b feat/user-authentication
# Make atomic commits
git commit -m "feat(auth): add user model with validation"
git commit -m "feat(auth): implement password hashing"
git commit -m "feat(auth): add login endpoint"
git commit -m "test(auth): add authentication tests"
git commit -m "docs(auth): add authentication API docs"
# Squash if needed before merge
git rebase -i main
Release Workflow
# Prepare release branch
git checkout -b release/v2.0.0
# Update version and changelog
git commit -m "chore(release): bump version to 2.0.0"
# Merge to main
git checkout main
git merge release/v2.0.0
git tag v2.0.0
# Deploy
git commit -m "ci: deploy version 2.0.0 to production"
Troubleshooting
Common Issues
-
Commitlint fails on merge commits
bash# Configure commitlint to ignore merge commits echo "extends: ['@commitlint/config-conventional'] rules: { 'subject-case': [0] }" > .commitlintrc.yml -
Commitizen not working
bash# Check configuration cat .czrc # Reinstall if needed npm uninstall -g commitizen npm install -g commitizen -
Semantic release not creating releases
bash# Check commit format git log --oneline # Verify configuration cat .releaserc.json
Quick Reference
# Commit types
feat # New feature
fix # Bug fix
docs # Documentation
style # Code style (formatting, etc.)
refactor # Code refactoring
test # Adding/updating tests
chore # Maintenance tasks
perf # Performance improvements
ci # CI/CD changes
build # Build system changes
revert # Revert previous commit
# Format
type(scope): description
# Breaking changes
type!: description
type(scope)!: description
# Tools
npx commitizen # Interactive commit
npx commitlint --from HEAD~1 # Lint last commit
npx semantic-release # Create release
# Examples
feat: add shopping cart
fix(auth): resolve login issue
docs: update README
feat!: change API response format
Previous: Git Hooks
Next: GitHub CLI