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/fireclassInstallation & 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-transformeryarn
yarn add @dharayush07/fireclass firebase-admin class-validator class-transformerStep 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!