The dairy industry in India is largely unorganized. A dairy business owner manages hundreds of customers, tracks daily milk deliveries, handles varying fat content and pricing, manages payments - all in physical ledgers. One lost notebook means months of records gone.
I set out to bring this entire workflow online.
India has millions of small dairy businesses. Building a product for each is impossible. The SaaS model lets me:
I structured the project as a monorepo managed by TurboRepo with pnpm workspaces:
milko/
├── apps/
│ ├── api/ # Next.js API routes backend
│ ├── web/ # Next.js dashboard
│ └── mobile/ # Expo/React Native app
├── packages/
│ ├── database/ # Prisma schema & migrations
│ ├── ui/ # Shared UI components
│ ├── logic/ # Shared business logic
│ └── types/ # Shared TypeScript types
Why a monorepo? Dairy business logic is complex. Fat percentage calculations, rate cards, credit cycles - this logic needs to be consistent across web, mobile, and API. Shared packages eliminate duplication.
Each dairy business is a tenant. Data isolation is non-negotiable:
// Tenant middleware
@Injectable()
export class TenantMiddleware implements NestMiddleware {
async use(req: Request, res: Response, next: NextFunction) {
const tenantId = req.headers['x-tenant-id'];
if (!tenantId) {
throw new UnauthorizedException('Tenant context required');
}
// Set tenant context for all database queries
req.tenantContext = { id: tenantId };
next();
}
}
Every database query is scoped to the authenticated tenant. A dairy owner in Gujarat can't see data from a dairy in Rajasthan. Ever. I enforced this at the middleware level.
I built the super admin layer to manage the platform itself:
A typical dairy business day with Milko:
Rural dairy customers don't use apps. They use SMS. I integrated Milko with Indian bulk SMS providers to send:
SMS integration uses Indian bulk SMS providers with regional language support.
The Expo/React Native mobile app I built is for:
NativeWind (Tailwind for React Native) keeps the UI consistent with the web dashboard.
The logic package contains calculations that must be identical everywhere:
// Shared rate calculation
export function calculateMilkRate(
fatPercentage: number,
rateCard: RateCard
): number {
const baseFat = rateCard.baseFatPercentage; // e.g., 6.0%
const baseRate = rateCard.baseRate; // e.g., ₹40/liter
const perPointRate = rateCard.perPointRate; // e.g., ₹2 per 0.1%
const diff = (fatPercentage - baseFat) * 10; // Convert to points
return baseRate + (diff * perPointRate);
}
Fat percentage affects price. A liter of milk with 6.5% fat costs more than one with 5.8%. This calculation runs the same way on web, mobile, and API.
Comprehensive test suite across all packages:
Financial calculations can't have bugs. They directly affect people's livelihoods.
Built with excellence in mind
Optimized for speed & scalability
Battle-tested with real users
Responsive across all devices
Maintainable & well-documented
Explore other projects
I rebuilt my brass manufacturing corporate website from scratch — new brand identity, modernized color system, static export optimization, local product photography, and cleaner URL architecture.
I built a production-grade corporate website for a brass component manufacturer and exporter based in Jamnagar, Gujarat. Fully static, zero-backend architecture with a custom design token system, 55+ product catalog with detailed specifications
I built a premium fashion e-commerce template with a complete shopping experience — product catalog with 6-type filtering, cart with promo codes, 4-step checkout, wishlist, collections, quick view, recently viewed tracking, dark mode, and page transitions