The error handling module provides Express middleware and utilities for gracefully handling errors from FirestoreORM operations, converting them into appropriate HTTP responses.
Error Handler Middleware
errorHandler
Express middleware that maps repository errors to appropriate HTTP responses. Automatically handles ValidationError, NotFoundError, ConflictError, FirestoreIndexError, and generic errors.
function errorHandler(
err: any,
req: Request,
res: Response,
next: NextFunction
): void
Parameters
Error object thrown by repository or application code
Express next function for passing control to the next middleware
Error Mapping
The middleware maps errors to HTTP status codes as follows:
| Error Type | Status Code | Response Format |
|---|
| ValidationError | 400 | { error: "ValidationError", details: [...] } |
| NotFoundError | 404 | { error: "NotFoundError", message: "..." } |
| FirestoreIndexError | 404 | { error: "Query needs to be indexed", message: "...", url: "..." } |
| ConflictError | 409 | { error: "ConflictError", message: "..." } |
| Other Errors | 500 | { error: "InternalServerError", message: "Something went wrong" } |
Usage Examples
Register as Global Error Handler
import express from 'express';
import { errorHandler } from '@spacelabstech/firestoreorm' // ErrorHandler';
const app = express();
// ... your routes ...
// Register error handler as the last middleware
app.use(errorHandler);
app.listen(3000);
Use in Route Handlers
import { errorHandler } from '@spacelabstech/firestoreorm' // ErrorHandler';
app.post('/users', async (req, res, next) => {
try {
const user = await userRepo.create(req.body);
res.json(user);
} catch (error) {
next(error); // errorHandler will process this
}
});
ValidationError Response (400)
When validation fails, the middleware returns detailed error information:
{
"error": "ValidationError",
"details": [
{
"path": ["email"],
"message": "Invalid email"
},
{
"path": ["age"],
"message": "Must be positive"
}
]
}
NotFoundError Response (404)
When a document is not found:
{
"error": "NotFoundError",
"message": "Document with id user-123 not found"
}
ConflictError Response (409)
When there’s a conflict (e.g., duplicate unique field):
{
"error": "ConflictError",
"message": "Email already exists"
}
FirestoreIndexError Response (404)
When a query requires a Firestore index:
{
"error": "Query needs to be indexed",
"message": "The query requires an index...",
"url": "https://console.firebase.google.com/..."
}
Error Parser
parseFirestoreError
Parses Firestore errors and converts them into FirestoreORM-specific error types.
function parseFirestoreError(error: any): Error
Parameters
The error object from Firestore operations
Returns
Returns a FirestoreIndexError if the error is related to missing indexes, otherwise returns the original error.
Usage Example
import { parseFirestoreError } from '@spacelabstech/firestoreorm' // ErrorParser';
try {
// Perform Firestore operation
const results = await collection
.where('category', '==', 'electronics')
.where('price', '>', 100)
.orderBy('createdAt', 'desc')
.get();
} catch (error) {
const parsedError = parseFirestoreError(error);
if (parsedError instanceof FirestoreIndexError) {
console.log('Index URL:', parsedError.indexUrl);
console.log('Required fields:', parsedError.fields);
}
throw parsedError;
}
FirestoreIndexError Detection
The parser detects Firestore index errors by checking:
- Error code is 9 (FAILED_PRECONDITION)
- Error details contain “requires an index”
When detected, it extracts:
- Index URL: Direct link to create the required index in Firebase Console
- Field names: Array of fields that need to be indexed
Complete Example
Here’s a complete example integrating error handling in an Express application:
import express from 'express';
import { Repository } from '@spacelabstech/firestoreorm';
import { errorHandler } from '@spacelabstech/firestoreorm' // ErrorHandler';
import { Firestore } from '@google-cloud/firestore';
import { z } from 'zod';
import { makeValidator } from '@spacelabstech/firestoreorm' // Validation';
const app = express();
app.use(express.json());
const db = new Firestore();
// Define schema and validator
const userSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().positive()
});
const userValidator = makeValidator(userSchema);
// Create repository
interface User {
name: string;
email: string;
age: number;
}
const userRepo = new Repository<User>({
firestore: db,
collectionPath: 'users',
validator: userValidator
});
// Routes
app.post('/users', async (req, res, next) => {
try {
const user = await userRepo.create(req.body);
res.status(201).json(user);
} catch (error) {
next(error);
}
});
app.get('/users/:id', async (req, res, next) => {
try {
const user = await userRepo.findById(req.params.id);
res.json(user);
} catch (error) {
next(error);
}
});
app.patch('/users/:id', async (req, res, next) => {
try {
const user = await userRepo.update(req.params.id, req.body);
res.json(user);
} catch (error) {
next(error);
}
});
// Error handler must be registered last
app.use(errorHandler);
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Best Practices
- Always register errorHandler last - It should be the final middleware in your Express app
- Use next(error) in async handlers - Pass errors to the error handler using
next()
- Don’t expose internal errors - The middleware automatically sanitizes 500 errors to prevent leaking sensitive information
- Log errors before handling - Consider adding logging middleware before the error handler for debugging