@dharayush7/fireclass
v0.2.12
Published
A minimal, type-safe Firestore model helper** for Node.js with first-class integration** for class-validator and class-transformer.Provides base models, typed CRUD, declarative collection mapping, and strong query builders
Maintainers
Readme
Fireclass
A minimal, type-safe Firestore model helper for Node.js with first-class integration for class-validator and class-transformer.
Provides base models, typed CRUD, declarative collection mapping, and strong query builders.
🔗 Links:
- GitHub: https://github.com/dharayush7/fireclass
- Website: fireclass.ayushdhar.com
Installation
# npm
npm install @dharayush07/fireclass firebase-admin class-validator class-transformer
# yarn
yarn add @dharayush07/fireclass firebase-admin class-validator class-transformerImportant: You must install
class-validatorandclass-transformeras peer dependencies. These are required for validation and transformation decorators to work properly.firebase-adminis also required for Firestore operations.Required Dependencies:
class-validator- For field validation decorators (e.g.,@IsString(),@IsDate(),@IsArray(), etc.)class-transformer- For data transformation decorators (e.g.,@Type(),@Transform())
Requirements
Node.js ≥ 18
Firestore from
firebase-adminTypeScript ≥ 5 (for typed generics)
Fireclass tested with:
class-validator@^0.14.3class-transformer@^0.5.1firebase-admin@^12.0.0
Firestore Admin Setup
Fireclass is designed for server-side Node.js apps using firebase-admin.
Initialize Firestore Admin
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);Ensure your
.envkeys are correctly formatted;privateKeymust replace literal\nwith newline.
Basic Usage
Create a model mapped to a Firestore collection. Every extended class must include a constructor that calls super(data) and assigns the data:
import { Collection } from "@dharayush07/fireclass/decorators";
import { IsString, IsEmail, IsOptional, IsDate, IsArray, IsObject, IsBoolean, Length } from "class-validator";
import { Transform, Type } from "class-transformer";
@Collection("users")
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;
}Complete Example with All Field Types
Here's a comprehensive example showing all field types with proper class-validator decorators:
import { Collection } from "@dharayush07/fireclass/decorators";
import {
IsString,
IsEmail,
IsDate,
IsArray,
IsObject,
IsBoolean,
IsOptional,
IsNumber
} from "class-validator";
import { Type } from "class-transformer";
interface TurfSlot {
startTime: string;
endTime: string;
}
@Collection("turfBookings")
export class TurfBooking extends BaseModel<TurfBooking> {
constructor(data?: Partial<TurfBooking>) {
super(data);
Object.assign(this, data);
}
@IsDate()
@Type(() => Date)
bookingDate!: Date;
@IsDate()
@Type(() => Date)
createdAt!: Date;
@IsString()
name!: string;
@IsString()
email!: string;
@IsString()
phoneNumber!: string;
@IsArray()
paymentId!: string[];
@IsObject()
prizeBreakThrough!: {
amountPaid: number;
totalAmount: number;
amountPending: number;
amountToBePaid: number;
totalDiscount: number;
};
@IsBoolean()
status!: boolean;
@IsString()
uid!: string;
@IsArray()
slots!: (TurfSlot & { id: string })[];
@IsOptional()
@IsBoolean()
isPartially?: boolean;
}Optional Fields
Optional fields should be marked with the @IsOptional() decorator from class-validator. This allows the field to be undefined or omitted during validation:
@Collection("products")
class Product extends BaseModel<Product> {
constructor(data?: Partial<Product>) {
super(data);
Object.assign(this, data);
}
@IsString()
name!: string;
@IsOptional()
@IsString()
description?: string; // Optional field
@IsOptional()
@IsNumber()
discount?: number; // Optional field
@IsOptional()
@IsBoolean()
isActive?: boolean; // Optional field
}Key Points for Optional Fields:
- Use
?in TypeScript to mark the property as optional - Always include
@IsOptional()decorator before other validation decorators - Optional fields can be
undefinedor omitted when creating instances - If provided, optional fields will still be validated according to their type decorators
Create / Save
const user = new User({ name: "Ada Lovelace", email: "[email protected]" });
await user.save(); // validates automatically before writingUpdate
user.displayName = "Ada";
await user.save();Read by ID
const found = await User.findById(user.id!);Query Many
const users = await User.findMany({
where: {
name: { equal: "Ada" },
createdAt: { gte: new Date("2020-01-01") },
},
orderBy: { createdAt: "desc" },
limit: 10,
});Delete
await user.delete();Class-Validator Integration
- Enforces constraints before saving.
- Throws
ValidationError[]if invalid.
try {
const invalidUser = new User({ name: "A", email: "bad-email" });
await invalidUser.save();
} catch (err) {
console.error(err); // array of ValidationError objects
}Class-Transformer Integration
- Normalize and coerce data before validation / save.
import { IsString, IsNumber } from "class-validator";
class Address {
@IsString()
@Transform(({ value }) => value?.trim())
street!: string;
@IsString()
city!: string;
}
@Collection("profiles")
class Profile extends BaseModel<Profile> {
constructor(data?: Partial<Profile>) {
super(data);
Object.assign(this, data);
}
@IsObject()
@Type(() => Address)
address!: Address;
@IsNumber()
@Transform(({ value }) => Number(value))
reputation!: number;
}Query Options
export type WhereFilter<T> = {
[K in keyof T]?: {
equal?: T[K];
gt?: T[K];
gte?: T[K];
lt?: T[K];
lte?: T[K];
};
};
export type OrderBy<T> = {
[K in keyof T]?: "asc" | "desc";
};
export interface QueryOptions<T> {
where?: WhereFilter<T>;
orderBy?: OrderBy<T>;
limit?: number;
}Example Query
const todos = await Todo.findMany({
where: {
name: { equal: "Shopping" },
isDone: { equal: false },
},
orderBy: { name: "asc" },
limit: 20,
});Full Firestore Admin Example
import express from "express";
import admin from "firebase-admin";
import { getBaseModel } from "@dharayush07/fireclass/core";
import { Collection } from "@dharayush07/fireclass/decorators";
import { IsString } from "class-validator";
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"),
}),
});
const firestore = admin.firestore();
const BaseModel = getBaseModel(firestore);
@Collection("posts")
class Post extends BaseModel<Post> {
constructor(data?: Partial<Post>) {
super(data);
Object.assign(this, data);
}
@IsString()
title!: string;
}
const app = express();
app.use(express.json());
app.post("/posts", async (req, res) => {
try {
const post = new Post(req.body);
const id = await post.save();
res.status(201).json({ id });
} catch (err) {
res.status(400).json({ errors: err });
}
});
app.listen(3000, () => console.log("Server running on port 3000"));Cloud Functions Example
import * as functions from "firebase-functions";
import admin from "firebase-admin";
import { getBaseModel } from "@dharayush07/fireclass/core";
import { Collection } from "@dharayush07/fireclass/decorators";
import { IsEmail } from "class-validator";
admin.initializeApp();
const firestore = admin.firestore();
const BaseModel = getBaseModel(firestore);
@Collection("subscribers")
class Subscriber extends BaseModel<Subscriber> {
constructor(data?: Partial<Subscriber>) {
super(data);
Object.assign(this, data);
}
@IsEmail()
email!: string;
}
export const subscribe = functions.https.onRequest(async (req, res) => {
try {
const s = new Subscriber({ email: req.body.email });
const id = await s.save();
res.json({ id });
} catch (err) {
res.status(400).json({ errors: err });
}
});Error Handling
ValidationError[]fromsave()for invalid fields.delete()throws ifidis missing.findById()returnsnullif not found.findMany()supportswhere,orderBy,limitwith proper type safety.
Contact & Support
Developed by: Ayush Dhar
- GitHub: https://github.com/dharayush7/fireclass
- Website: fireclass.ayushdhar.com
- Portfolio: ayushdhar.com
- Email: [email protected]
- GitHub Profile: @dharayush07
License
MIT © Ayush Dhar
