How to Update GitHub Content Using Node.js: Complete Guide with Octokit

Learn how to programmatically update GitHub repository content using Node.js and Octokit REST API. Complete guide with authentication, file operations, and automation examples.

GitHub API with Node.js and Octokit

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:


Prerequisites

Before we begin, you’ll need:


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:

  1. Go to GitHub Settings > Developer settings > Personal access tokens
  2. Click “Generate new token (classic)”
  3. Select the necessary scopes:
    • repo - Full control of private repositories
    • public_repo - Access public repositories only
  4. 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:

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:

Looking for a Full-Stack JavaScript Developer?

If you're hiring for a full-stack JavaScript developer position and found this article helpful, I'd love to contribute to your team! I specialize in modern JavaScript technologies including Node.js, NestJS, Angular, PostgreSQL, Docker, and Kubernetes, with extensive experience in RxJS, TypeScript, and building scalable web applications.

Let's connect:

  • 📧 Email: jobayer6735@gmail.com
  • Schedule a meeting
  • 💼 View my portfolio and other technical articles on this site

I'm passionate about writing clean, maintainable code and sharing knowledge with the developer community. If your company values technical excellence and continuous learning, I'd be excited to discuss how I can help your team succeed.

Jobayer

© 2025 Jobayer Ahmed. All rights are reserved.