DevelopmentJune 10, 2026· via DEV Community

Crafting a Robust Express + TypeScript Backend for Personal Finance Tracking

Crafting a Robust Express + TypeScript Backend for Personal Finance Tracking

Image : DEV Community

Publicité

When building a personal finance tracker, ensuring data integrity and system reliability is crucial. A single missing try/catch block or weak type definition can lead to server crashes or corrupted databases. To address these challenges, I've built a production-ready backend using Express, TypeScript, and Zod—a combination that offers robust security and flexibility.

The Weapon Against Boilerplate: asyncHandler HOC

To combat the repetitive nature of manually wrapping try/catch blocks around every controller handler, I developed an asynchronous Higher-Order Function (HOC) named asyncHandler. This function automatically handles errors by catching any rejected promises and routing them to a centralized error handler. Here’s how it works:

import { Request, Response, NextFunction, RequestHandler } from 'express'; export const asyncHandler = (fn: RequestHandler): RequestHandler => { return (req: Request, res: Response, next: NextFunction) => { Promise.resolve(fn(req, res, next)).catch(next); }; };

This solution ensures that even with a large codebase, error handling remains consistent and reduces the likelihood of human errors. Instead of cluttering controllers with try/catch blocks, developers can focus on business logic.

TypeScript Magic: Declaration Merging for Type Safety

Typically, casting req as any to access custom properties like userId or requestId violates type safety. To maintain strong typing without resorting to such unsafe approaches, I leveraged TypeScript’s declaration merging feature. By extending Express's internal Request interface and merging my custom metadata directly into it:

declare global { namespace Express { interface Request { auth: { userId: number; email: string }; requestId: string; validated?: { body?: unknown; query?: unknown; params?: unknown; }; } } }

This approach keeps the code clean and type-safe, ensuring that all custom properties are properly typed. The .d.ts files emitted during compilation provide compile-time confidence without increasing runtime overhead.

The Validation Pipeline: Middleware Factories with Zod

Financial APIs often require validating large payloads like transaction parameters or updates. Placing validation logic inside controller functions violates the Separation of Concerns principle. To address this, I created a generic validate middleware factory using Zod:

import { RequestHandler } from 'express'; import AppError from './utils/AppError'; type Source = 'params' | 'body' | 'query';

export default function validate(schema: T, source: Source): RequestHandler { return (req: Request, res: Response, next: NextFunction) => { const parsedValue = schema.safeParse(req[source]); if (!parsedValue.success) { return next(new AppError('Invalid request data', 400)); }

req.validated[schema.name] = parsedValue.data;
next();

}; }

This factory dynamically validates payloads based on Zod schemas and injects the validated data back into the req.validated object. Malformed requests are automatically blocked, ensuring only valid inputs reach backend controllers.


Source: DEV Community. AI-assisted editorial synthesis — TechnoExpress.

Read the original source on DEV Community →

← Back to home

Publicité