
How to Update GitHub Content Using Node.js: Complete Guide with Octokit REST API
GitHub’s REST API provides powerful capabilities for automating repository operations, and the Octokit/rest library makes it incredibly easy to interact with GitHub programmatically using Node.js. Whether you’re building automation tools, CI/CD pipelines, or content management systems, this guide will show you how to update GitHub repository content efficiently.
In this comprehensive tutorial, we’ll cover:
- Setting up Octokit with authentication
- Updating existing files
- Creating new files
- Handling errors and edge cases
- Best practices for production use
Prerequisites
Before we begin, you’ll need:
- Node.js installed on your system
- A GitHub account
- A GitHub Personal Access Token (PAT)
Step 1: Install and Setup Octokit
First, install the Octokit/rest library:
npm install @octokit/rest
Then, set up the Octokit client with authentication:
import { Octokit } from '@octokit/rest';
const octokit = new Octokit({
auth: 'YOUR_GITHUB_TOKEN'
});
Step 2: Generate GitHub Personal Access Token
To interact with GitHub’s API, you need a Personal Access Token:
- Go to GitHub Settings > Developer settings > Personal access tokens
- Click “Generate new token (classic)”
- Select the necessary scopes:
repo
- Full control of private repositoriespublic_repo
- Access public repositories only
- Copy the generated token and store it securely
Security Note: Never commit your token to version control. Use environment variables:
const octokit = new Octokit({
auth: process.env.GITHUB_TOKEN
});
Step 3: Update Existing Files
Here’s how to update an existing file in a GitHub repository:
async function updateFile(owner, repo, path, content, message) {
try {
// First, get the current file to obtain its SHA
const { data: currentFile } = await octokit.repos.getContent({
owner,
repo,
path
});
// Update the file
const response = await octokit.repos.createOrUpdateFileContents({
owner,
repo,
path,
message,
content: Buffer.from(content).toString('base64'),
sha: currentFile.sha
});
console.log('File updated successfully:', response.data.commit.html_url);
return response.data;
} catch (error) {
console.error('Error updating file:', error.message);
throw error;
}
}
// Usage example
updateFile(
'your-username',
'your-repo',
'src/config.json',
JSON.stringify({ version: '2.0.0', updated: new Date().toISOString() }, null, 2),
'Update configuration file'
);
Step 4: Create New Files
To create a new file in a repository:
async function createFile(owner, repo, path, content, message) {
try {
const response = await octokit.repos.createOrUpdateFileContents({
owner,
repo,
path,
message,
content: Buffer.from(content).toString('base64')
});
console.log('File created successfully:', response.data.commit.html_url);
return response.data;
} catch (error) {
console.error('Error creating file:', error.message);
throw error;
}
}
// Usage example
createFile(
'your-username',
'your-repo',
'docs/README.md',
'# New Documentation\n\nThis is a new file created via GitHub API.',
'Add new documentation file'
);
Step 5: Complete Example with Error Handling
Here’s a comprehensive example that handles both file creation and updates:
import { Octokit } from '@octokit/rest';
class GitHubFileManager {
constructor(token) {
this.octokit = new Octokit({ auth: token });
}
async updateOrCreateFile(owner, repo, path, content, message) {
try {
// Try to get the current file first
const { data: currentFile } = await this.octokit.repos.getContent({
owner,
repo,
path
});
// File exists, update it
return await this.updateFile(owner, repo, path, content, message, currentFile.sha);
} catch (error) {
if (error.status === 404) {
// File doesn't exist, create it
return await this.createFile(owner, repo, path, content, message);
}
throw error;
}
}
async updateFile(owner, repo, path, content, message, sha) {
const response = await this.octokit.repos.createOrUpdateFileContents({
owner,
repo,
path,
message,
content: Buffer.from(content).toString('base64'),
sha
});
console.log(`✅ File updated: ${response.data.commit.html_url}`);
return response.data;
}
async createFile(owner, repo, path, content, message) {
const response = await this.octokit.repos.createOrUpdateFileContents({
owner,
repo,
path,
message,
content: Buffer.from(content).toString('base64')
});
console.log(`✅ File created: ${response.data.commit.html_url}`);
return response.data;
}
async deleteFile(owner, repo, path, message) {
try {
const { data: currentFile } = await this.octokit.repos.getContent({
owner,
repo,
path
});
await this.octokit.repos.deleteFile({
owner,
repo,
path,
message,
sha: currentFile.sha
});
console.log(`✅ File deleted: ${path}`);
} catch (error) {
console.error('Error deleting file:', error.message);
throw error;
}
}
}
// Usage
const fileManager = new GitHubFileManager(process.env.GITHUB_TOKEN);
// Update or create a file
fileManager.updateOrCreateFile(
'your-username',
'your-repo',
'src/data.json',
JSON.stringify({ users: [], lastUpdated: new Date().toISOString() }, null, 2),
'Update user data'
);
Step 6: Advanced Operations
Batch File Operations
async function batchUpdateFiles(owner, repo, files) {
const results = [];
for (const file of files) {
try {
const result = await fileManager.updateOrCreateFile(
owner,
repo,
file.path,
file.content,
file.message
);
results.push({ success: true, file: file.path, result });
} catch (error) {
results.push({ success: false, file: file.path, error: error.message });
}
}
return results;
}
// Usage
const filesToUpdate = [
{
path: 'src/config.json',
content: JSON.stringify({ version: '1.0.0' }),
message: 'Update version'
},
{
path: 'docs/API.md',
content: '# API Documentation\n\nUpdated documentation.',
message: 'Update API docs'
}
];
batchUpdateFiles('your-username', 'your-repo', filesToUpdate);
Working with Different File Types
// JSON files
async function updateJsonFile(owner, repo, path, data, message) {
const content = JSON.stringify(data, null, 2);
return await fileManager.updateOrCreateFile(owner, repo, path, content, message);
}
// Markdown files
async function updateMarkdownFile(owner, repo, path, content, message) {
return await fileManager.updateOrCreateFile(owner, repo, path, content, message);
}
// Binary files (images, etc.)
async function updateBinaryFile(owner, repo, path, buffer, message) {
const content = buffer.toString('base64');
return await fileManager.updateOrCreateFile(owner, repo, path, content, message);
}
Step 7: Environment Variables Setup
Create a .env
file for your project:
# .env
GITHUB_TOKEN=your_github_token_here
GITHUB_OWNER=your_username
GITHUB_REPO=your_repository_name
Load environment variables:
import dotenv from 'dotenv';
dotenv.config();
const fileManager = new GitHubFileManager(process.env.GITHUB_TOKEN);
Step 8: Integration with CI/CD
Here’s how to integrate GitHub file updates into your CI/CD pipeline:
# .github/workflows/update-content.yml
name: Update Content
on:
schedule:
- cron: '0 0 * * *' # Daily at midnight
workflow_dispatch: # Manual trigger
jobs:
update-content:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm install
- name: Update GitHub content
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: node scripts/update-content.js
Best Practices
1. Error Handling
try {
await fileManager.updateOrCreateFile(owner, repo, path, content, message);
} catch (error) {
if (error.status === 403) {
console.error('Permission denied. Check your token permissions.');
} else if (error.status === 404) {
console.error('Repository or file not found.');
} else {
console.error('Unexpected error:', error.message);
}
}
2. Rate Limiting
// Check rate limits
const { data: rateLimit } = await octokit.rateLimit.get();
console.log(`Remaining requests: ${rateLimit.resources.core.remaining}`);
3. Content Validation
function validateContent(content, maxSize = 100 * 1024) { // 100KB limit
if (Buffer.byteLength(content, 'utf8') > maxSize) {
throw new Error('Content too large for GitHub API');
}
return true;
}
Common Issues and Solutions
Issue 1: “Not Found” Error
Problem: File or repository not found Solution: Verify the repository name, owner, and file path
Issue 2: “Bad Credentials” Error
Problem: Invalid or expired token Solution: Generate a new Personal Access Token
Issue 3: “Resource not accessible by integration” Error
Problem: Insufficient permissions Solution: Check token scopes and repository permissions
Issue 4: Large File Error
Problem: File exceeds GitHub’s size limit Solution: Use Git LFS or split the file
Performance Optimization
// Use Promise.all for concurrent operations
async function updateMultipleFiles(owner, repo, files) {
const promises = files.map(file =>
fileManager.updateOrCreateFile(owner, repo, file.path, file.content, file.message)
);
return await Promise.all(promises);
}
Conclusion
Updating GitHub content programmatically using Node.js and Octokit is a powerful way to automate repository management. By following this guide, you can:
- ✅ Update existing files with proper SHA handling
- ✅ Create new files in repositories
- ✅ Handle errors gracefully
- ✅ Integrate with CI/CD pipelines
- ✅ Scale operations for multiple files
The key to success is proper authentication, error handling, and understanding GitHub’s API limitations. With these tools, you can build robust automation systems that keep your repositories up-to-date automatically.
Ready to automate your GitHub workflow? Start with the examples above and scale them to your specific needs!
References: