Building Developer Tools: Creating Internal Tools That Teams Love
The best code you write might not be in your main application—it might be the tools that help your team work better. After building internal tools that saved hundreds of hours, I've learned what makes tools that teams actually use.
Why Build Internal Tools?
Benefits
- Time savings - Automate repetitive tasks
- Consistency - Same process every time
- Reduced errors - Less manual work = fewer mistakes
- Knowledge sharing - Tools document processes
- Team productivity - Focus on important work
Types of Internal Tools
1. CLI Tools
Command-line tools for common tasks:
#!/usr/bin/env node
// scripts/create-user.js
const readline = require('readline');
const { createUser } = require('../services/userService');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
async function createUserCLI() {
const email = await question('Email: ');
const name = await question('Name: ');
const role = await question('Role (admin/user): ');
try {
const user = await createUser({ email, name, role });
console.log(`✅ User created: ${user.id}`);
} catch (error) {
console.error(`❌ Error: ${error.message}`);
}
rl.close();
}
function question(prompt) {
return new Promise(resolve => {
rl.question(prompt, resolve);
});
}
createUserCLI();
2. Admin Dashboards
Web interfaces for managing data:
// Admin dashboard
app.get('/admin/users', requireAdmin, async (req, res) => {
const users = await getUsers();
res.render('admin/users', { users });
});
app.post('/admin/users/:id/activate', requireAdmin, async (req, res) => {
await activateUser(req.params.id);
res.redirect('/admin/users');
});
3. Data Migration Tools
Tools for moving/transforming data:
// Migration tool
async function migrateUsers(sourceDB, targetDB) {
const users = await sourceDB.query('SELECT * FROM users');
for (const user of users) {
try {
await targetDB.query(
'INSERT INTO users (id, email, name) VALUES ($1, $2, $3)',
[user.id, user.email, user.name]
);
console.log(`✅ Migrated user: ${user.email}`);
} catch (error) {
console.error(`❌ Failed to migrate ${user.email}: ${error.message}`);
}
}
}
4. Monitoring Tools
Tools for system health:
// Health check dashboard
app.get('/admin/health', async (req, res) => {
const health = {
database: await checkDatabase(),
redis: await checkRedis(),
api: await checkAPI(),
timestamp: new Date().toISOString()
};
res.json(health);
});
Building Effective Tools
1. Solve Real Problems
Build tools for problems your team actually has:
// Problem: Manually checking deployment status
// Solution: Deployment status tool
async function checkDeployments() {
const deployments = await getDeployments();
deployments.forEach(deploy => {
const status = deploy.status === 'success' ? '✅' : '❌';
console.log(`${status} ${deploy.service} - ${deploy.environment}`);
});
}
2. Make It Easy to Use
// Good: Simple, clear interface
$ npm run create-user
Email: user@example.com
Name: John Doe
✅ User created
// Bad: Complex, unclear
$ node scripts/user-management/create-new-user.js --email user@example.com --name "John Doe" --role user --send-email true
3. Provide Feedback
// Show progress
async function processOrders(orders) {
let processed = 0;
for (const order of orders) {
await processOrder(order);
processed++;
// Progress feedback
process.stdout.write(`\rProcessed: ${processed}/${orders.length}`);
}
console.log('\n✅ All orders processed');
}
4. Handle Errors Gracefully
async function bulkOperation(items) {
const results = { success: 0, failed: 0, errors: [] };
for (const item of items) {
try {
await processItem(item);
results.success++;
} catch (error) {
results.failed++;
results.errors.push({ item, error: error.message });
console.error(`❌ Failed: ${item.id} - ${error.message}`);
}
}
console.log(`\n✅ Success: ${results.success}`);
console.log(`❌ Failed: ${results.failed}`);
if (results.errors.length > 0) {
console.log('\nErrors:');
results.errors.forEach(({ item, error }) => {
console.log(` ${item.id}: ${error}`);
});
}
}
Common Internal Tools
1. Database Management
// Database admin tool
class DatabaseAdmin {
async backupDatabase() {
console.log('Creating backup...');
await exec('pg_dump database > backup.sql');
console.log('✅ Backup created');
}
async restoreDatabase(backupFile) {
console.log('Restoring from backup...');
await exec(`psql database < ${backupFile}`);
console.log('✅ Database restored');
}
async runQuery(query) {
const result = await db.query(query);
console.table(result.rows);
}
}
2. User Management
// User management tool
async function manageUsers() {
const action = await question('Action (create/list/delete): ');
switch (action) {
case 'create':
await createUser();
break;
case 'list':
await listUsers();
break;
case 'delete':
await deleteUser();
break;
}
}
3. Deployment Tools
// Deployment tool
async function deploy(environment, service) {
console.log(`Deploying ${service} to ${environment}...`);
// Build
console.log('Building...');
await exec('npm run build');
// Test
console.log('Running tests...');
await exec('npm test');
// Deploy
console.log('Deploying...');
await exec(`kubectl set image deployment/${service} ${service}=image:latest`);
// Verify
console.log('Verifying deployment...');
await waitForDeployment(service);
console.log('✅ Deployment successful');
}
Best Practices
1. Document Everything
/**
* Creates a new user in the system
*
* Usage:
* create-user --email user@example.com --name "John Doe"
*
* Options:
* --email User email (required)
* --name User name (required)
* --role User role (default: user)
*/
2. Make It Safe
// Confirm destructive operations
async function deleteUser(userId) {
const user = await getUser(userId);
const confirm = await question(
`Delete user ${user.email}? (yes/no): `
);
if (confirm !== 'yes') {
console.log('Cancelled');
return;
}
await deleteUserById(userId);
console.log('✅ User deleted');
}
3. Version Control
// Track tool versions
const TOOL_VERSION = '1.2.3';
function showVersion() {
console.log(`Tool version: ${TOOL_VERSION}`);
}
4. Logging
// Log tool usage
const logger = require('./logger');
async function createUser(data) {
logger.info('Creating user', { email: data.email });
try {
const user = await userService.create(data);
logger.info('User created', { userId: user.id });
return user;
} catch (error) {
logger.error('Failed to create user', { error, email: data.email });
throw error;
}
}
Real-World Example
Problem: Team spending hours manually checking deployment statuses, user data, and system health.
Solution: Built admin dashboard with:
- Deployment status - See all deployments at a glance
- User management - Search, filter, manage users
- System health - Database, Redis, API status
- Quick actions - One-click common operations
Result:
- Time saved: 5 hours/week
- Fewer errors: Automated processes
- Better visibility: Know system state instantly
Conclusion
Internal tools are force multipliers. A good tool can save hours every week and make your team more productive. The key is to:
- Solve real problems - Build what's needed
- Keep it simple - Easy to use
- Provide feedback - Show what's happening
- Document well - Others need to use it
Remember: The best tools are the ones people actually use. Build tools that make life easier, and your team will thank you.
What internal tools have you built? What problems did they solve?