DEV Community

Cover image for Stop paying for databases: I built a Type-safe Google Sheets ORM (Prisma-style)
Yodsavee Supachoktanasap
Yodsavee Supachoktanasap

Posted on

Stop paying for databases: I built a Type-safe Google Sheets ORM (Prisma-style)

The Problem: "I just need a simple database..."

We've all been there. You are building a side project, a prototype, or a simple CMS for a client. You need a database, but:

  1. Postgres/MySQL feels like overkill (and costs money/effort to host).
  2. SQLite is great but hard to share with non-tech clients.
  3. Google Sheets is perfect for clients, but the API... well, the API returns untyped arrays (row[0], row[1]), which is a nightmare to maintain.

I wanted the developer experience (DX) of Prisma but with Google Sheets as the backend.

So, I built TarangDB.

What is TarangDB?

TarangDB is a lightweight, type-safe ORM for Node.js and Bun. It turns your Google Sheet tabs into relational tables with a syntax you already know and love.

Key Features:

  • โœจ Prisma-like Syntax: findMany, create, where, include.
  • ๐Ÿ›ก๏ธ Type-safe: Auto-generated TypeScript interfaces from your schema.
  • ๐Ÿ”— Relationships: Supports hasMany, belongsTo (yes, joining sheets!).
  • ๐Ÿš€ Built-in Caching: Smart caching to handle Google API rate limits.
  • ๐Ÿง  Computed Columns: Supports Google Sheets formulas.

Show me the code!

Let's build a simple User & Post system.

1. Installation

npm install tarang-db
# or
bun add tarang-db
Enter fullscreen mode Exit fullscreen mode

2. Setup the Client

You just need your Google Service Account credentials (standard stuff).

import { TarangClient } from 'tarang-db';

const client = new TarangClient({
  spreadsheetId: 'YOUR_SHEET_ID',
  auth: {
    clientEmail: process.env.GOOGLE_EMAIL,
    privateKey: process.env.GOOGLE_KEY,
  },
  // Optional: Smart caching to save API quota
  cacheTTL: 60000, 
});
Enter fullscreen mode Exit fullscreen mode

3. Define Schema (The Magic Part ๐Ÿช„)

This is where TarangDB shines. You define a schema, and it handles the types for you.

import { Model, Schema, DataTypes, Infer } from 'tarang-db';

const UserSchema = new Schema({
  id: { type: DataTypes.UUID, unique: true }, // Auto-generated!
  name: DataTypes.String,
  email: { type: DataTypes.String, unique: true },
  age: DataTypes.Number,
  isActive: { type: DataTypes.Boolean, default: true },
  createdAt: DataTypes.Date.createdAt(), // Auto timestamp
  deletedAt: DataTypes.Date.deletedAt(), // Built-in Soft Delete!
});

// โœจ Automatically infer TypeScript type
type User = Infer<typeof UserSchema>;

const userModel = new Model<User>(client, {
  sheetName: 'Users',
  schema: UserSchema,
});
Enter fullscreen mode Exit fullscreen mode

4. Querying Data

Forget about values[i][j]. Just write readable code:

Find with Filters:

const adults = await userModel.findMany({ 
  age: { gte: 18 },
  isActive: true
}, {
  limit: 10,
  sortBy: 'createdAt',
  sortOrder: 'desc'
});
Enter fullscreen mode Exit fullscreen mode

Create Data:

await userModel.create({
  name: 'John Doe',
  email: 'john@example.com',
  age: 30
});
// The ID and createdAt are automatically handled!
Enter fullscreen mode Exit fullscreen mode

Update & Delete:

await userModel.update(
  { email: 'john@example.com' }, 
  { age: 31 }
);

// Performs a soft-delete (sets deletedAt) because we defined it in the schema
await userModel.delete({ email: 'john@example.com' });
Enter fullscreen mode Exit fullscreen mode

Relationships (Joining Sheets)

This is usually the hardest part with NoSQL-like stores, but TarangDB makes it easy.

// Define Relations
const userModel = new Model<User>(client, {
  sheetName: 'Users',
  schema: UserSchema,
  relations: {
    posts: {
      type: 'hasMany',
      targetModel: postModel,
      foreignKey: 'userId',
      localKey: 'id',
    },
  },
});

// Query with Eager Loading
const users = await userModel.findMany(
  { email: 'john@example.com' }, 
  { 
    include: { posts: true } // <--- Magic happens here
  }
);

console.log(users[0].posts); // Array of Post objects
Enter fullscreen mode Exit fullscreen mode

But what about API Limits?

Google Sheets API has rate limits (Requests per minute). TarangDB handles this out of the box with Smart Caching.

  • Read operations are cached in memory (configurable TTL).
  • Write operations automatically invalidate the cache for that specific sheet.

This ensures your app feels snappy and doesn't hit Google's 429 errors during normal usage.

Conclusion

TarangDB is open source and I built it to solve my own pain points. It's perfect for MVPs, internal tools, or CMSs where you want the client to have easy access to the data (via Sheets) but you want a solid developer experience.

I'd love for you to try it out and let me know what you think!

Happy coding! ๐Ÿš€

Top comments (0)