DEV Community

Abdelrahman Mohamed
Abdelrahman Mohamed

Posted on

I spent 7 years fighting Firestore's worst problems. So I built a tool to fix them.

If you've shipped anything beyond a todo app with Firestore, you know the feeling. You start a project and everything is fast, flexible, magical. Six months later you're staring at a security rules file that's 400 lines of unmaintainable custom syntax, your production database has three different shapes of "user" documents, and every new collection means writing the same converter boilerplate all over again.

I've been building with Firestore for over seven years. I love it — the real-time sync, the scaling, the developer experience for getting an MVP shipped fast. But the tooling around data modeling, type safety, and schema management? It never caught up.

So I built Firemap.

The problems nobody warns you about

Firestore's flexibility is a double-edged sword. There's no schema enforcement, which means your app code is the only thing standing between you and data chaos. Here's what that looks like in practice:

Schema drift is silent and deadly. Your users collection starts clean — { name, email, createdAt }. Then someone adds a profile object. Then a different service writes profile as a string instead of an object. Six months in, you've got documents with four different shapes in the same collection and no way to know without manually inspecting them. As one developer put it: "Schema drift in Firestore is a pain because there's no enforcement layer by default — you're basically hoping your app code is the schema guardian, which breaks the moment multiple services write to the same collections."

Security rules become spaghetti. Firebase's rules language looks simple in the docs. In production, it's a nightmare. You can't loop, you can't debug meaningfully (permission-denied errors are intentionally vague), and you can't reuse logic across collections without copy-pasting. I've seen teams ship reasonable rules that quietly became wrong after a product pivot added new access patterns nobody updated the rules for.

There's no real type safety. Writing doc.data() as User isn't type safety — it's a prayer. Firestore's SDK returns DocumentData, and the withConverter() escape hatch has known gaps (the update() method accepts {[fieldPath: string]: any}, completely bypassing your types). The fact that at least five community libraries exist solely to solve this problem tells you everything.

Boilerplate multiplies with every collection. Each new collection means a new converter, new validation logic, new serializer. The same pattern, repeated endlessly. And if your data model requires denormalization (it will — this is Firestore), you need Cloud Functions triggers to keep duplicated data in sync, which means even more hand-written CRUD code for every collection.

What Firemap actually does

Firemap is a visual development platform for Firestore that treats your schema as a first-class citizen. It has three layers: a visual designer, an open-source TypeScript ODM library, and a code generation engine.

Design your schema visually, generate everything else

The core of Firemap is a drag-and-drop canvas where you design your Firestore schema visually — collections, documents, fields, subcollections, relationships. Instead of holding your data model in your head (or in a stale Notion doc nobody updates), you design it interactively and Firemap generates the code from it.

This isn't just a diagramming tool. The visual schema is the source of truth that drives everything downstream: your TypeScript models, your security rules, and your Cloud Functions.

Detect schema drift before it breaks things

Firemap includes a crawler that connects to your live Firestore database, scans your collections, and compares what's actually in production against your intended schema. It flags documents with missing fields, wrong types, unexpected properties — the drift that accumulates silently over months.

Instead of discovering at 2 AM that half your user documents are missing the subscription field your new feature depends on, you catch it in a dashboard before it ships.

A TypeScript ODM that actually validates at runtime

The Firemap ODM is an open-source TypeScript library that uses decorators for model definitions with built-in runtime validation — not just compile-time types. Here's what it looks like:

import { Collection, Field, SubCollection, Validate } from '@firemap/odm';

@Collection('users')
class User {
  @Field() 
  name!: string;

  @Field() 
  email!: string;

  @Field({ defaultValue: 'free' })
  plan!: 'free' | 'pro' | 'enterprise';

  @Validate({ min: 0, max: 150 })
  @Field({ optional: true })
  age?: number;

  @Field({ timestamp: true, autoCreate: true })
  createdAt!: Date;

  @SubCollection(() => Post)
  posts!: Post[];
}

@Collection('posts')
class Post {
  @Field()
  title!: string;

  @Field({ min: 1, max: 10000 })
  content!: string;

  @Field({ reference: () => User })
  authorRef!: string;
}
Enter fullscreen mode Exit fullscreen mode

Those decorators aren't just metadata for TypeScript — they drive runtime validation when data is read from or written to Firestore. If a document comes back from Firestore with age: "twenty-five", you get a clear validation error instead of a silent type mismatch that breaks something three layers up the call stack.

CRUD operations use a clean, typed API:

import { getRepository } from '@firemap/odm';

const users = getRepository(User);

// Create — validates before writing
const user = await users.create({
  name: 'Sarah Chen',
  email: 'sarah@example.com',
  plan: 'pro',
});

// Query — returns typed results
const proUsers = await users
  .where('plan', '==', 'pro')
  .orderBy('createdAt', 'desc')
  .limit(10)
  .find();

// Update — partial updates are type-checked
await users.update(user.id, { plan: 'enterprise' });

// Subcollection access
const userPosts = await users.subcollection(user.id, Post).find();
Enter fullscreen mode Exit fullscreen mode

No as User casts. No manual converters. No separate validation layer you have to keep in sync.

Security rules you never have to write by hand

Firemap generates Firestore security rules directly from your schema definitions. The field types, validation constraints, and access patterns you define in your models become properly structured rules — automatically.

// Auto-generated by Firemap from your schema
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow read: if request.auth != null;
      allow create: if request.auth != null
        && request.resource.data.keys().hasAll(['name', 'email', 'plan'])
        && request.resource.data.name is string
        && request.resource.data.email is string
        && request.resource.data.plan in ['free', 'pro', 'enterprise'];
      allow update: if request.auth.uid == userId
        && (!('age' in request.resource.data) 
            || (request.resource.data.age is int 
                && request.resource.data.age >= 0 
                && request.resource.data.age <= 150));
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Your schema changes, you regenerate. No more hand-editing a growing rules file and hoping you didn't break access for an edge case you forgot about.

Cloud Functions generation

Define your data model, and Firemap generates typed Cloud Functions for common patterns — CRUD endpoints, denormalization triggers, data validation hooks. The generated functions match your schema types end-to-end, so when you add a field to a model, the functions that touch that collection update accordingly.

The open-source piece

The TypeScript ODM library (@firemap/odm) is fully open-source. You can use it standalone without the visual designer or the platform — it's a decorator-based, runtime-validated ODM for Firestore that works with both the client SDK and the Admin SDK. The visual designer, crawler, and code generation features are part of the Firemap platform at firemap.dev.

What I'd love to hear from you

I've been building this because these problems drove me crazy on my own projects. But I know every Firestore project has its own flavor of pain.

What's the Firestore problem that wastes the most of your time? Is it the ones I covered here, or something else entirely? Drop a comment — I genuinely want to know, and it directly shapes what I build next.

🔗 firemap.dev
🔗 GitHub: [https://github.com/abdo400/firemap]

Top comments (0)