TypeScript • Node.js • Firestore

Type-Safe FirestoreORM for Node.js

A minimal, type-safe Firestore model helper with first-class integration for class-validator and class-transformer. Build faster with automatic validation and transformation.

npm install @dharayush07/fireclass

Installation & Setup

Get started with Fireclass in minutes with simple installation steps.

Step 1: Install Dependencies

npm

npm install @dharayush07/fireclass firebase-admin class-validator class-transformer

yarn

yarn add @dharayush07/fireclass firebase-admin class-validator class-transformer

Step 2: Update TypeScript Config

Edit your tsconfig.json to enable decorators:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "lib": ["ES2020"],
    "moduleResolution": "node",
    "strict": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "resolveJsonModule": true,
    "declaration": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Step 3: Initialize Firebase

Create or update your Firebase admin initialization file:

import admin from "firebase-admin";
import "dotenv/config";

admin.initializeApp({
  credential: admin.credential.cert({
    projectId: process.env.FIREBASE_PROJECT_ID,
    clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
    privateKey: process.env
      .FIREBASE_PRIVATE_KEY?.replace(/\n/g, "\n"),
  }),
});

import { getBaseModel } from 
  "@dharayush07/fireclass/core";
export const firestore = admin.firestore();
export const BaseModel = getBaseModel(firestore);

Requirements

  • Node.js ≥ 18
  • TypeScript ≥ 5
  • firebase-admin ^12.0.0
  • class-validator ^0.14.3
  • class-transformer ^0.5.1

Important Dependencies

You must install class-validator, class-transformer, and firebase-admin as peer dependencies. These are required for validation and Firestore operations.

Core Features

Everything you need for type-safe Firestore development with minimal overhead.

Type-Safe Models

Full TypeScript support with automatic type inference for your Firestore collections.

Declarative Mapping

Simple decorators for collection mapping and field validation with class-validator.

Automatic Validation

Built-in validation using class-validator ensures data integrity before saving.

Query Builder

Strong, type-safe query builders with support for filtering, sorting, and pagination.

Data Transformation

Seamless data transformation with class-transformer for nested objects.

Minimal Footprint

Lightweight library that plays well with existing Node.js and Firebase setup.

Code Examples

Comprehensive examples showing how to use Fireclass in your projects.

Define a Basic User Model

import { Collection } from "@dharayush07/fireclass/decorators";
import { 
  IsString, 
  IsEmail, 
  IsDate,
  Length,
  IsOptional,
  IsBoolean
} from "class-validator";
import { Type, Transform } from "class-transformer";
import { BaseModel } from "./firebase-init";

@Collection("users")
export class User extends BaseModel<User> {
  constructor(data?: Partial<User>) {
    super(data);
    Object.assign(this, data);
  }

  @IsString()
  @Length(2, 50)
  name!: string;

  @IsEmail()
  email!: string;

  @IsOptional()
  @IsString()
  @Transform(({ value }) => value?.trim())
  displayName?: string;

  @IsDate()
  @Type(() => Date)
  createdAt!: Date;

  @IsOptional()
  @IsBoolean()
  isActive?: boolean;
}

Create, Save & Query Records

import { User } from "./models/user";

// Create a new user instance
const user = new User({
  name: "Ada Lovelace",
  email: "ada@example.com",
  displayName: "Ada",
  createdAt: new Date(),
  isActive: true
});

// Automatically validates and saves to Firestore
const userId = await user.save();
console.log(`User saved with ID: ${userId}`);

// Find user by ID
const foundUser = await User.findById(userId);
console.log(`Found user: ${foundUser?.name}`);

// Update user
foundUser!.displayName = "Ada Ada";
await foundUser!.save();

// Query multiple users
const activeUsers = await User.findMany({
  where: {
    isActive: { equal: true },
    createdAt: { gte: new Date("2020-01-01") }
  },
  orderBy: { createdAt: "desc" },
  limit: 10
});

console.log(`Found ${activeUsers.length} active users`);

// Delete user
await foundUser!.delete();
console.log("User deleted successfully");

Advanced Model with Complex Types

import { Collection } from "@dharayush07/fireclass/decorators";
import {
  IsString,
  IsDate,
  IsArray,
  IsObject,
  IsBoolean,
  IsOptional,
  IsNumber,
  IsEmail
} from "class-validator";
import { Type } from "class-transformer";
import { BaseModel } from "./firebase-init";

// Nested interfaces for complex structures
interface TurfSlot {
  startTime: string;
  endTime: string;
  price: number;
}

interface PrizeBreakdown {
  amountPaid: number;
  totalAmount: number;
  amountPending: number;
  totalDiscount: number;
}

@Collection("turfBookings")
export class TurfBooking extends BaseModel<TurfBooking> {
  constructor(data?: Partial<TurfBooking>) {
    super(data);
    Object.assign(this, data);
  }

  @IsString()
  name!: string;

  @IsEmail()
  email!: string;

  @IsString()
  phoneNumber!: string;

  @IsDate()
  @Type(() => Date)
  bookingDate!: Date;

  @IsArray()
  @IsString({ each: true })
  paymentIds!: string[];

  @IsObject()
  prizeBreakdown!: PrizeBreakdown;

  @IsArray()
  slots!: (TurfSlot & { id: string })[];

  @IsBoolean()
  status!: boolean;

  @IsString()
  uid!: string;

  @IsOptional()
  @IsBoolean()
  isPartially?: boolean;

  @IsOptional()
  @IsString()
  notes?: string;
}

Complete CRUD Operations with Validation

import { User } from "./models/user";

// CREATE - with automatic validation
async function createUser(userData: Partial<User>) {
  try {
    const user = new User({
      ...userData,
      createdAt: new Date()
    });
    
    const id = await user.save();
    console.log(`✓ User created: ${id}`);
    return id;
  } catch (err) {
    console.error("✗ Validation failed:", err);
    throw err;
  }
}

// READ - single and multiple
async function getUser(userId: string) {
  const user = await User.findById(userId);
  if (!user) {
    console.log("✗ User not found");
    return null;
  }
  return user;
}

async function getRecentUsers(limit = 20) {
  const users = await User.findMany({
    where: {
      isActive: { equal: true }
    },
    orderBy: { createdAt: "desc" },
    limit
  });
  
  console.log(`Found ${users.length} active users`);
  return users;
}

// UPDATE - modify and save
async function updateUser(userId: string, updates: Partial<User>) {
  const user = await User.findById(userId);
  if (!user) throw new Error("User not found");
  
  Object.assign(user, updates);
  await user.save();
  console.log("✓ User updated");
  return user;
}

// DELETE - remove completely
async function deleteUser(userId: string) {
  const user = await User.findById(userId);
  if (!user) throw new Error("User not found");
  
  await user.delete();
  console.log("✓ User deleted");
}

// Usage examples
await createUser({
  name: "John Doe",
  email: "john@example.com",
  displayName: "John"
});

await updateUser("user-id-123", {
  displayName: "John Doe Updated",
  isActive: false
});

const users = await getRecentUsers(50);
users.forEach(u => console.log(`- ${u.name}: ${u.email}`));

Query Filters & Sorting

import { User } from "./models/user";

// Filter by equality
const johnUsers = await User.findMany({
  where: {
    name: { equal: "John" }
  }
});

// Combine multiple filters
const recentActiveUsers = await User.findMany({
  where: {
    isActive: { equal: true },
    createdAt: { gte: new Date("2024-01-01") }
  }
});

// Range queries
const dateRange = await User.findMany({
  where: {
    createdAt: {
      gte: new Date("2024-01-01"),
      lte: new Date("2024-12-31")
    }
  }
});

// Sorting - ascending
const sortedAsc = await User.findMany({
  orderBy: { name: "asc" },
  limit: 10
});

// Sorting - descending
const sortedDesc = await User.findMany({
  orderBy: { createdAt: "desc" },
  limit: 20
});

// Pagination
const page1 = await User.findMany({
  where: { isActive: { equal: true } },
  orderBy: { createdAt: "desc" },
  limit: 10
});

const page2 = await User.findMany({
  where: { isActive: { equal: true } },
  orderBy: { createdAt: "desc" },
  limit: 10
});

// Complex queries
const complexQuery = await User.findMany({
  where: {
    isActive: { equal: true },
    createdAt: {
      gte: new Date("2024-01-01"),
      lt: new Date("2024-12-31")
    }
  },
  orderBy: { name: "asc" },
  limit: 50
});

console.log(`Found ${complexQuery.length} matching users`);

Error Handling & Validation

import { User } from "./models/user";
import { ValidationError } from "class-validator";

async function handleUserOperations() {
  try {
    // This will fail validation - email is invalid
    const invalidUser = new User({
      name: "A", // Too short - needs min length 2
      email: "not-an-email", // Invalid email format
      createdAt: new Date()
    });

    await invalidUser.save();
  } catch (err) {
    if (Array.isArray(err)) {
      // Handle validation errors
      const validationErrors = err as ValidationError[];
      validationErrors.forEach(error => {
        console.error(`Field: ${error.property}`);
        if (error.constraints) {
          Object.entries(error.constraints).forEach(
            ([rule, message]) => {
              console.error(`  - ${rule}: ${message}`);
            }
          );
        }
      });
    } else {
      console.error("Unexpected error:", err);
    }
  }

  try {
    // Correct user creation
    const validUser = new User({
      name: "Ada Lovelace",
      email: "ada@example.com",
      displayName: "Ada",
      createdAt: new Date(),
      isActive: true
    });

    const userId = await validUser.save();
    console.log("✓ User saved successfully:", userId);
  } catch (err) {
    console.error("Failed to save user:", err);
  }

  try {
    // Update with validation
    const user = await User.findById("user-123");
    if (!user) {
      throw new Error("User not found");
    }

    // Update with invalid data
    user.name = ""; // Fails validation
    await user.save();
  } catch (err) {
    if (err instanceof Error) {
      console.error("Error:", err.message);
    }
  }
}

handleUserOperations();

Get in Touch

Have questions or feedback? We'd love to hear from you.

Send a Message

Contact Information

Developed by Ayush Dhar

Fireclass is a passion project created to simplify Firestore development with TypeScript. Join the community and contribute on GitHub!