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 🙏

© 2026 – Pkg Stats / Ryan Hefner

github-as-a-service

v1.1.0

Published

GitHub as a Service — Turn any GitHub repository into a zero-cost, version-controlled database

Readme

GaaS — GitHub as a Service

Turn any GitHub repository into a zero-cost, version-controlled database.

npm License: MIT Node.js

GaaS is an NPM package that turns any GitHub private (or public) repository into a lightweight database — with collections, key-value store, file storage, schema validation, encryption, and more. Perfect for MVPs, side projects, hackathons, and internal tools.

Zero cost. Zero infrastructure. Your data, your repo.


Quick Start

npm install github-as-a-service
import { GaaS } from 'github-as-a-service';

const db = new GaaS({
  token: 'ghp_xxxxxxxxxxxx',   // GitHub Personal Access Token
  repo: 'my-app-data',         // Auto-creates if doesn't exist
  owner: 'your-username',      // GitHub username or org
});

// Create a collection and add data
const users = db.collection('users');
await users.create({ id: 'u1', name: 'Vishwa', email: '[email protected]' });

// Read it back
const user = await users.findById('u1');
console.log(user); // { id: 'u1', name: 'Vishwa', email: '[email protected]' }

That's it. Your data is now stored as JSON files in a private GitHub repo with full git history.


Features

| Feature | Description | |---|---| | Collections | Firebase/MongoDB-style CRUD with queries, filtering, sorting | | Key-Value Store | Simple get/set for config, feature flags, counters | | File Storage | Upload/download images, PDFs, binary files | | Schema Validation | Built-in fluent validation (no Zod needed) | | Lifecycle Hooks | beforeCreate, afterUpdate, beforeDelete, etc. | | Batch Operations | Create/update/delete many records in a single commit | | Encryption | AES-256-GCM encryption for sensitive data | | Branch Environments | Use branch: 'dev' for dev data, branch: 'main' for prod | | Import/Export | Bulk import from JSON/CSV, export to JSON/CSV | | Debug Mode | See every API call, cache hit/miss, and timing | | Auto-retry | Exponential backoff on failures + SHA conflict resolution | | TypeScript | Full type safety with generics |


Configuration

Via constructor:

const db = new GaaS({
  token: 'ghp_xxxxxxxxxxxx',
  repo: 'my-app-data',
  owner: 'your-username',
  branch: 'main',              // default: 'main'
  debug: false,                 // default: false
  retry: {
    enabled: true,              // default: true
    maxRetries: 3,              // default: 3
    backoff: 'exponential',     // 'exponential' | 'linear' | 'fixed'
  },
  encryption: {
    enabled: false,             // default: false
    key: 'your-secret-key',     // AES-256-GCM
  },
});

Via environment variables:

# .env
GAAS_TOKEN=ghp_xxxxxxxxxxxx
GAAS_REPO=my-app-data
GAAS_OWNER=your-username
GAAS_BRANCH=main
GAAS_ENCRYPTION_KEY=your-secret-key
// No config needed — reads from env vars
const db = new GaaS();

Collections

CRUD Operations

interface User {
  id: string;
  name: string;
  email: string;
  plan: 'free' | 'pro';
}

const users = db.collection<User>('users');

// Create
const user = await users.create({
  name: 'Alice',
  email: '[email protected]',
  plan: 'free',
}); // Auto-generates id

// Read
const alice = await users.findById('abc123');
const allUsers = await users.findAll();

// Update (merge)
await users.update('abc123', { plan: 'pro' });

// Replace (full overwrite)
await users.replace('abc123', { id: 'abc123', name: 'Alice Pro', email: '[email protected]', plan: 'pro' });

// Delete
await users.delete('abc123');

// Check existence
const exists = await users.exists('abc123');

Querying

// Filter with where clause
const proUsers = await users.find({
  where: { plan: 'pro' },
  sort: { name: 'asc' },
  limit: 10,
  offset: 0,
});

// Find one
const admin = await users.findOne({ name: 'Admin' });

// Count
const proCount = await users.count({ where: { plan: 'pro' } });

Where Operators

await users.find({
  where: {
    age: { $gte: 18, $lt: 65 },
    plan: { $in: ['pro', 'enterprise'] },
    name: { $contains: 'alice' },
    email: { $endsWith: '@gmail.com' },
    deletedAt: { $exists: false },
  },
});

| Operator | Description | |---|---| | $eq | Equal to | | $ne | Not equal to | | $gt / $gte | Greater than / Greater than or equal | | $lt / $lte | Less than / Less than or equal | | $in / $nin | In array / Not in array | | $contains | String contains | | $startsWith | String starts with | | $endsWith | String ends with | | $exists | Field exists (not null/undefined) |

Batch Operations

// Create many (single commit)
await users.createMany([
  { name: 'Bob', email: '[email protected]', plan: 'free' },
  { name: 'Charlie', email: '[email protected]', plan: 'pro' },
]);

// Update many (single commit)
await users.updateMany([
  { id: 'u1', changes: { plan: 'pro' } },
  { id: 'u2', changes: { plan: 'pro' } },
]);

// Delete many (single commit)
await users.deleteMany(['u1', 'u2', 'u3']);

// Clear all records
await users.clear();

Lifecycle Hooks

const users = db.collection('users', {
  hooks: {
    beforeCreate: (data) => {
      data.createdAt = new Date().toISOString();
      return data;
    },
    afterCreate: (record) => {
      console.log(`User created: ${record.name}`);
    },
    beforeUpdate: (id, changes) => {
      changes.updatedAt = new Date().toISOString();
      return changes;
    },
    beforeDelete: (id) => {
      console.log(`Deleting user: ${id}`);
    },
  },
});

Schema Validation

import { GaaS, Schema } from 'github-as-a-service';

const users = db.collection('users', {
  schema: {
    name: Schema.string().required().minLength(2),
    email: Schema.string().email().required(),
    age: Schema.number().min(13).optional(),
    plan: Schema.enum(['free', 'pro', 'enterprise']).default('free'),
    website: Schema.string().url().optional(),
    tags: Schema.array().optional(),
  },
});

// Throws ValidationError with detailed field-level errors
await users.create({ name: 'A', email: 'not-an-email' });

Key-Value Store

const kv = db.kv();

// Basic operations
await kv.set('app:version', '2.1.0');
await kv.set('feature:dark_mode', true);
await kv.set('config', { maxUsers: 100, theme: 'dark' });

const version = await kv.get('app:version');     // '2.1.0'
const exists = await kv.has('app:version');       // true
await kv.delete('app:version');

// Batch operations
await kv.setMany({ key1: 'value1', key2: 'value2' });
await kv.deleteMany(['key1', 'key2']);

// Utility methods
await kv.increment('stats:visits');               // 1, 2, 3, ...
await kv.increment('stats:visits', 5);            // +5
await kv.toggle('feature:maintenance');            // true → false → true

// Inspection
const allKeys = await kv.keys();
const allData = await kv.getAll();
const count = await kv.size();
await kv.clear();

File Storage

const storage = db.storage();

// Upload from file path
await storage.upload('avatars/user.png', './local-image.png');

// Upload from Buffer
await storage.upload('docs/report.pdf', someBuffer);

// Upload text content
await storage.upload('config/settings.yaml', 'key: value');

// Download
const buffer = await storage.download('avatars/user.png');

// Get URL (works for public repos)
const url = storage.getUrl('avatars/user.png');

// List files
const files = await storage.list('avatars/');

// Check existence
const exists = await storage.exists('avatars/user.png');

// Get metadata
const info = await storage.info('avatars/user.png');

// Delete
await storage.delete('avatars/user.png');

Branch Environments

Use git branches to separate dev/staging/production data:

// Development
const devDb = new GaaS({
  token: 'ghp_xxx',
  repo: 'my-app-data',
  owner: 'username',
  branch: 'dev',
});

// Production
const prodDb = new GaaS({
  token: 'ghp_xxx',
  repo: 'my-app-data',
  owner: 'username',
  branch: 'main',
});

Encryption

Enable AES-256-GCM encryption to encrypt all data before writing to GitHub:

const db = new GaaS({
  token: 'ghp_xxx',
  repo: 'my-data',
  owner: 'username',
  encryption: {
    enabled: true,
    key: process.env.GAAS_ENCRYPTION_KEY,
  },
});

// All data is now encrypted at rest in your GitHub repo

Import / Export

// Import from local file
const count = await db.import('users', './data/users.json');
const count2 = await db.import('contacts', './data/contacts.csv', {
  clear: true,      // clear existing records first
  idField: 'userId', // use 'userId' as the ID field
});

// Export to local file
await db.export('users', './backup/users.json');
await db.export('users', './backup/users.csv', { format: 'csv' });

// Export with filtering
await db.export('users', './pro-users.json', {
  where: { plan: 'pro' },
});

Health & Diagnostics

// Health check
const health = await db.health();
// {
//   connected: true,
//   repo: 'my-data',
//   owner: 'username',
//   branch: 'main',
//   rateLimit: { remaining: 4500, limit: 5000, used: 500, resetsAt: '...' }
// }

// Rate limit check
const limits = await db.rateLimit();

// Clear cache (if data was modified outside GaaS)
db.clearCache();

// Get safe config info (no token exposed)
const info = db.info();

Error Handling

import {
  GaaSError,
  RateLimitError,
  ConflictError,
  NotFoundError,
  ValidationError,
  AuthenticationError,
} from 'github-as-a-service';

try {
  await users.create(data);
} catch (error) {
  if (error instanceof RateLimitError) {
    console.log(`Rate limited. Retry after ${error.retryAfter}s`);
  }
  if (error instanceof ConflictError) {
    console.log(`Conflict on ${error.filePath}`);
  }
  if (error instanceof ValidationError) {
    error.errors.forEach(e => console.log(`${e.field}: ${e.message}`));
  }
  if (error instanceof NotFoundError) {
    console.log(`Not found: ${error.resource}`);
  }
}

Debug Mode

const db = new GaaS({
  token: 'ghp_xxx',
  repo: 'my-data',
  owner: 'username',
  debug: true,
});

// Console output:
// [GaaS] GaaS initialized for username/my-data (branch: main)
// [GaaS] Repository "username/my-data" exists
// [GaaS] GET collections/users/u1.json (cache MISS) 234ms
// [GaaS] Cache SET: file:collections/users/u1.json
// [GaaS] PUT collections/users/u1.json (SHA: abc1234) 456ms
// [GaaS] Rate limit: 4892/5000 remaining (resets 2026-04-25T02:00:00.000Z)

How Data is Stored

Your GitHub repo looks like this:

my-app-data/
├── collections/
│   ├── users/
│   │   ├── abc123.json     ← one file per record
│   │   └── def456.json
│   └── posts/
│       └── post1.json
├── kv/
│   └── store.json          ← all key-value pairs
└── storage/
    ├── avatars/
    │   └── user.png
    └── docs/
        └── report.pdf

API Rate Limit Optimization

GaaS aggressively minimizes GitHub API usage under the hood:

| Operation | API Calls | |---|---| | create() / update() | 1 | | findById() | 1 (0 if cached) | | findAll() (50 records) | 2 + uncached records (2 if all cached) | | createMany(10) | ~14 (parallelized blob creation) | | deleteMany(50) | 4 (tree reconstruction) | | clear() (100 records) | 6 (tree reconstruction) | | storage.exists() | 1 (dir listing, not file download) |

How it works:

  • Batch operations use the Git Trees API to create/update/delete any number of files in a single commit
  • Deletes are free — the tree is rebuilt without deleted paths (no per-file API calls)
  • Reads use recursive tree — fetch all file metadata in 1 call, then only fetch content for uncached files
  • In-memory cache with TTL tracks file content + SHA to avoid redundant reads
  • Network errors throw immediately — never triggers retry/fallback floods that exhaust rate limits

Limitations

| Limit | Detail | |---|---| | Rate limit | 5,000 GitHub API calls per hour | | File size | Max 100MB per file | | Repo size | Keep under 1GB | | Latency | ~200-500ms per API call (cached reads are instant) | | Concurrency | Use batch operations to avoid conflicts |

GaaS is perfect for MVPs, side projects, hackathons, and internal tools. When you outgrow it, your data is just JSON files — easy to migrate to any database.


GitHub Token

Create a Personal Access Token (PAT) at github.com/settings/tokens:

  • Classic token: Select the repo scope
  • Fine-grained token: Grant Contents (Read & Write) permission

Author

VishwaGauravIn


License

MIT