npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

github-storage

v0.0.8

Published

A lightweight TypeScript library that lets you use a GitHub repository as a simple file database.

Readme

GitStorage

A lightweight TypeScript library that lets you use a GitHub repository as a simple file database — upload, update, delete, and fetch files through the GitHub REST API.


Table of Contents

  1. GitStorage
  2. Why GitStorage exists
  3. Installation
  4. Quick token setup
  5. Limitations
  6. Class: GitStorage
  7. Example: Angular File Manager with GitStorage
  8. Contributing

Why GitStorage exists

Sometimes you just need something online fast — for a quick test, school project, or small idea.
But most services now ask for payment methods, have limits, or need too much setup.

GitStorage is a simple trick: it uses your existing GitHub repo to store small files and data — totally free, no setup headaches.

It’s not built for production, but perfect for:

  • Quick tests and prototypes
  • Classroom or demo projects
  • Small tools or automations

Use it when you want something simple, free, and instant — just don’t expect real-time magic; GitHub might take a nap ☕.


Installation

npm i github-storage

Quick token setup

Go to https://github.com/settings/personal-access-tokens to create a new token.
When creating the token, select only the repository you need and set these permissions:

  • User permissions
    • This token does not have any user permissions.
  • Repository permissions
    • Read access to metadata
    • Read & Write access to code and issues

Limitations

  • Caching: GitHub may return cached responses for repository content. This means that after uploading or updating a file, immediate reloads may not always show the latest file version due to caching. You can inspect the HTTP headers (like etag and cache-control) from getRepoInfo() to check for caching and freshness.

  • Rate limits: GitHub enforces API rate limits, which are included in the response headers:

    • x-ratelimit-limit: Maximum requests per hour
    • x-ratelimit-remaining: Requests left in the current window
    • x-ratelimit-reset: UNIX timestamp when the limit resets You can use these headers to avoid hitting the API limit in heavy usage scenarios.

Class: GitStorage

The GitStorage class allows developers to use a GitHub repository (public or private) as a free lightweight database or file storage system. It provides methods to upload, update, delete, and list files in any GitHub repository using GitHub’s REST API.

Constructor

new GitStorage(username: string, repository: string, options?: { token?: string });

Parameter Type Description username string GitHub username or organization name repository string The name of the GitHub repository options.token string (optional) GitHub Personal Access Token (required for private repos) Example

const db = new GitStorage('56duong', 'my-storage-repo', {
  token: 'your_token_here',
});

listFiles(path: string): Promise<any[]>

Returns all files in the given folder path.

const files = await db.listFiles('files');
console.log(files);

downloadFile(path: string): Promise<{ content: string }>

Downloads a file from the repository and returns its Base64-encoded content.

const file = await db.downloadFile('files/example.png');
console.log(file.content);

saveFile(base64: string, path: string, message?: string): Promise

Uploads or updates a file in the repository. If the file exists, it will be replaced.

const uploaded = await db.saveFile(base64, 'files/photo.png', 'Upload photo');

deleteFile(path: string): Promise<{ deleted: boolean }>

Deletes a file from the repository.

const deleted = await db.deleteFile('files/photo.png');
if (deleted.deleted) console.log('File removed!');

getRepoInfo(): Promise

Fetches basic repository information such as visibility and size.

const info = await db.getRepoInfo();
console.log(info.data.private ? 'Private repo' : 'Public repo');

fileToBase64(file: File): Promise

Converts a File object into a Base64 string.

const base64 = await db.fileToBase64(selectedFile);

base64ToBlob(base64: string): Blob

Converts a Base64 string into a downloadable Blob.

const blob = db.base64ToBlob(base64Data);

getPathFromDownloadUrl(url: string): string | null

Extracts the file path inside the repository from a GitHub raw download URL.

const db = new GitStorage('56duong', 'myrepotest');
const path = db.getPathFromDownloadUrl('https://raw.githubusercontent.com/56duong/myrepotest/main/files/inside/banner.jpg?token=ASL99LVKQQQVMS4FGWSM2M9JEBSPP');
console.log(path); // 'files/inside/banner.jpg'

generateUuid(version: 'v1' | 'v3' | 'v4' | 'v5', name?: string, namespace?: string): string

Generates a UUID string using the specified version.

  • 'v1': Timestamp-based UUID
  • 'v3': Namespace-based (MD5)
  • 'v4': Random UUID
  • 'v5': Namespace-based (SHA-1)
db.generateUuid('v1'); // e.g. '92e9c320-c6e1-11f0-9896-67ef8894be42'
db.generateUuid('v3', 'name-string', '6ba7b810-9dad-11d1-80b4-00c04fd430c8'); // v3, needs name and namespace
db.generateUuid('v4'); // e.g. 'dd38293a-7564-5e22-898b-03cae4f0f459'
db.generateUuid('v5', 'name-string', '6ba7b810-9dad-11d1-80b4-00c04fd430c8'); // v5, needs name and namespace

Example: Angular File Manager with GitStorage

Below is a complete example showing how to use GitStorage inside an Angular component
to upload, update, download, and delete files in a GitHub repository.

file-manager.component.html

<div class="file-manager-toolbar">
  <label>
    <input type="checkbox" [(ngModel)]="isPublic" (ngModelChange)="refreshFiles()" />
    Public Repository
  </label>

  <br><br>

  Folder: <input type="text" [(ngModel)]="folder" placeholder="Folder Name" />

  <br><br>

  <button type="button" (click)="refreshFiles()">Reload All Files</button>
  <input id="uploadInput" type="file" (change)="upload($event)" hidden #uploadInput />
  <button type="button" (click)="uploadInput?.click()">Upload New File</button>
</div>

<table class="file-manager-table">
  <thead>
    <tr>
      <th>Image</th>
      <th>File Name</th>
      <th style="width: 220px">Actions</th>
    </tr>
  </thead>
  <tbody>
    <tr *ngFor="let file of files; let i = index">
      <td class="file-name">
        <img *ngIf="isPublic" [src]="file?.download_url" alt="file" style="max-width:48px;max-height:48px;"/>
        <img *ngIf="!isPublic" [src]="file?.private_download_url" alt="file" style="max-width:48px;max-height:48px;"/>
      </td>
      <td class="file-name">{{ file?.path }}</td>
      <td class="actions">
        <input type="file" id="update-{{i}}" (change)="update(file.path, $event)" hidden #updateInput />
        <button type="button" (click)="updateInput.click()">Update</button>
        <button type="button" (click)="delete(file.path)">Delete</button>
        <button type="button" (click)="download(file)">Download</button>
      </td>
    </tr>
    <tr *ngIf="files?.length === 0">
      <td colspan="2" class="empty">No files found</td>
    </tr>
  </tbody>
</table>

file-manager.component.scss

* {
  font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
}

.file-manager-toolbar {
  margin-bottom: 12px;
  button { margin-right: 8px; }
}

.file-manager-table {
  width: 100%;
  border-collapse: collapse;

  th, td {
    border: 1px solid #ddd;
    padding: 8px;
  }

  th {
    text-align: left;
    background: #f5f5f5;
  }

  .file-name {
    max-width: 480px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .actions {
    button { margin-right: 8px; }
  }

  .empty {
    text-align: center;
    color: #666;
  }
}

file-manager.component.ts

import { Component } from '@angular/core';
import { GitStorage } from 'github-storage';

@Component({
  selector: 'app-file-manager',
  templateUrl: './file-manager.component.html',
  styleUrls: ['./file-manager.component.scss']
})
export class FileManagerComponent {
  
  // Initialize the GitHub database connection
  private db = new GitStorage(
    '56duong',
    'my-storage-repo',
    {
      token: 'YOUR_GITHUB_TOKEN_HERE',
    }
  );

  /** Folder in the repository to manage files */
  folder = 'files';
  /** List of files fetched from GitHub */
  files: any[] = [];
  /** Whether the repository is public or private */
  isPublic = true;

  ngOnInit() {
    // Retrieve repository info (and check if it's private)
    this.db.getRepoInfo().then(info => {
      console.log('Repo info:', info);
      this.isPublic = !info.data.private;
      this.refreshFiles();
    });
  }

  /** Refresh the list of files from the repository */
  async refreshFiles() {
    try {
      this.files = await this.db.listFiles(this.folder);
      console.log('files', this.files);
      // For private repos, convert files to base64 data URLs
      if (!this.isPublic) {
        this.files.map(async file => {
          const base64 = await this.db.downloadFile(file.path);
          file.private_download_url = `data:application/octet-stream;base64,${base64.content}`;
          return file;
        });
      }
    } catch (err) {
      console.error('Failed to list files', err);
      this.files = [];
    }
  }

  /** Helper: Convert a filename or path into a full folder path */
  private toPath(nameOrPath: string) {
    return nameOrPath.includes('/') ? nameOrPath : `${this.folder}/${nameOrPath}`;
  }

  /** Upload a new file to the repository */
  async upload(event: any) {
    const file: File = event?.target?.files?.[0];
    if (!file) return;
    try {
      const base64 = await this.db.fileToBase64(file);
      const path = `${this.folder}/${file.name}`;
      const uploaded = await this.db.saveFile(base64, path, `Upload ${file.name}`);
      console.log('Uploaded', uploaded);
      this.files.push(uploaded.content);
    } catch (err) {
      console.error('Upload failed', err);
    } finally {
      if (event?.target) event.target.value = '';
    }
  }

  /** Update an existing file */
  async update(pathOrName: string, event: any) {
    const file: File = event?.target?.files?.[0];
    if (!file) return;
    try {
      const base64 = await this.db.fileToBase64(file);
      const path = this.toPath(pathOrName);
      const updated = await this.db.saveFile(base64, pathOrName, `Update ${file.name}`);
      console.log('Updated', updated);
    } catch (err) {
      console.error('Update failed', err);
    } finally {
      if (event?.target) event.target.value = '';
    }
  }

  /** Delete a file from the repository */
  async delete(filenameOrPath: string) {
    const path = this.toPath(filenameOrPath);
    if (!confirm(`Delete ${path}?`)) return;
    try {
      const deleted = await this.db.deleteFile(path);
      if (deleted.deleted) {
        this.files = this.files.filter(f => f.path !== path);
      }
    } catch (err) {
      console.error('Delete failed', err);
    }
  }

  /** Download and preview a file */
  async download(file: any) {
    if (!file?.path) return;
    try {
      const data = await this.db.downloadFile(file.path);
      if (!data?.content) return;

      const blob = this.db.base64ToBlob(data.content);
      const url = URL.createObjectURL(blob);

      const a = document.createElement('a');
      a.href = url;
      a.download = file.name;
      a.click();
      URL.revokeObjectURL(url);
    } catch (err) {
      console.error('Download failed', err);
    }
  }
}

Contributing

Pull requests are welcome!
If you find a bug or want to add new features, feel free to:

  1. Fork the repo
  2. Create a new branch (feature/my-update)
  3. Submit a pull request 🚀