🔧 Extending the System
This guide explains how to add new features to the Dhanam Finance platform, including new API modules, dashboard pages, and mobile app screens.
Adding a New Backend Module
Step 1: Create the Module Structure
src/modules/your-module/
├── route.ts # Route definitions
├── controller.ts # Request handlers
├── validation.ts # Zod schemas (optional)
└── service.ts # Business logic (optional)
Step 2: Define Routes
// src/modules/your-module/route.ts
import { FastifyInstance } from "fastify";
import { getItems, createItem } from "./controller.js";
export default async function yourModuleRoutes(fastify: FastifyInstance) {
// List items
fastify.get(
"/",
{ preHandler: [fastify.authenticateAdmin, fastify.hasPermission("your-module:view")] },
getItems
);
// Create item
fastify.post(
"/",
{ preHandler: [fastify.authenticateAdmin, fastify.hasPermission("your-module:create")] },
createItem
);
}
Step 3: Implement Controller
// src/modules/your-module/controller.ts
import { FastifyRequest, FastifyReply } from "fastify";
export async function getItems(request: FastifyRequest, reply: FastifyReply) {
const db = request.server.mongo.db;
const items = await db.collection("your_items").find({}).toArray();
return reply.send({ success: true, data: items });
}
export async function createItem(request: FastifyRequest, reply: FastifyReply) {
const db = request.server.mongo.db;
const body = request.body as any;
const result = await db.collection("your_items").insertOne({
...body,
createdAt: new Date(),
createdBy: request.user.userId
});
return reply.status(201).send({
success: true,
message: "Item created",
id: result.insertedId
});
}
Step 4: Register in Server
// src/server.ts
import yourModuleRoutes from "./modules/your-module/route.js";
// ... other imports
server.register(yourModuleRoutes, { prefix: "/admin/your-module" });
Step 5: Add Permissions
Update the roles in the database to include new permissions:
// Add to admin role permissions array
"your-module:view",
"your-module:create",
"your-module:edit",
"your-module:delete"
Adding a New Dashboard Page
Step 1: Create Page Component
// app/(main)/your-module/page.tsx
"use client";
import { useEffect, useState } from "react";
import { PageTitle } from "@/components/page-title";
import { api } from "@/lib/api";
export default function YourModulePage() {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchData() {
try {
const response = await api.get("/admin/your-module");
setItems(response.data.data);
} finally {
setLoading(false);
}
}
fetchData();
}, []);
if (loading) return <div>Loading...</div>;
return (
<div className="p-6">
<PageTitle title="Your Module" />
{/* Your content here */}
</div>
);
}
Step 2: Add to Navigation
// components/nav-admin.tsx
// Add to the navigation items array
{
title: "Your Module",
url: "/your-module",
icon: IconName,
permission: "your-module:view"
}
Step 3: Add API Function
// lib/your-module-api.ts
import { api } from "./api";
export async function getItems() {
const response = await api.get("/admin/your-module");
return response.data;
}
export async function createItem(data: any) {
const response = await api.post("/admin/your-module", data);
return response.data;
}
Adding a New Mobile Screen
Step 1: Create Screen Widget
// lib/screens/your_screen.dart
import 'package:flutter/material.dart';
import '../services/api_service.dart';
class YourScreen extends StatefulWidget {
const YourScreen({super.key});
@override
State<YourScreen> createState() => _YourScreenState();
}
class _YourScreenState extends State<YourScreen> {
List<dynamic> items = [];
bool isLoading = true;
@override
void initState() {
super.initState();
_loadData();
}
Future<void> _loadData() async {
try {
final response = await ApiService.getData('/mobile/your-endpoint');
setState(() {
items = response['data'];
isLoading = false;
});
} catch (e) {
setState(() => isLoading = false);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Your Screen')),
body: isLoading
? const Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(title: Text(items[index]['name']));
},
),
);
}
}
Step 2: Add Navigation
// In home_screen.dart or navigation
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const YourScreen()),
);
Adding a Database Collection
Step 1: Define Indexes in db.ts
// src/plugins/db.ts
// Add after other index definitions
try {
const db = fastify.mongo.db;
if (db) {
const yourCollection = db.collection("your_items");
await yourCollection.createIndex({ code: 1 }, { unique: true, background: true });
await yourCollection.createIndex({ status: 1 }, { background: true });
await yourCollection.createIndex({ createdAt: -1 }, { background: true });
fastify.log.info("Ensured indexes on your_items collection");
}
} catch (err) {
fastify.log.error({ err }, "Failed to ensure your_items indexes");
}
New Feature Checklist
| Task | Backend | Dashboard | Mobile |
|---|---|---|---|
| Create route/controller | ✅ | - | - |
| Add validation schema | ✅ | ✅ | ✅ |
| Register in server | ✅ | - | - |
| Add permissions | ✅ | ✅ | - |
| Create UI components | - | ✅ | ✅ |
| Add to navigation | - | ✅ | ✅ |
| Add database indexes | ✅ | - | - |
| Add audit logging | ✅ | - | - |
| Write tests | ✅ | ✅ | ✅ |
| Update documentation | ✅ | ✅ | ✅ |
✅ Best Practices
- Always add audit logging for critical operations
- Use Zod for request validation
- Follow existing code patterns
- Add appropriate permissions for each endpoint
- Update this documentation when adding features