Feature Structure
We use a Domain-Driven Design (DDD) approach for folder organization.
Everything related to a specific business domain (e.g., Cameras) stays together in the features/ directory.
Anatomy of a Feature
Taking features/cameras as an example:
features/cameras/
├── api.ts # RAW API calls (fetch/axios wrappers)
├── types.ts # TypeScript interfaces & Enums
├── hooks.ts # React Query hooks (useCameras, useCamera)
├── components/ # UI Components specific to this feature
│ ├── camera-card.tsx
│ ├── camera-form.tsx
│ └── metrics-chart.tsx
└── index.ts # Public API (What is exposed to the rest of the app)
1. api.ts
Contains pure async functions that return Promises.
They use the global apiClient.
export const fetchCameras = async (params) => {
return apiClient.get("/cameras", { params });
};
2. hooks.ts
Connects the api.ts functions to TanStack Query.
This is where caching key strategies are defined.
export const useCameras = (filters) => {
return useQuery({
queryKey: ["cameras", filters],
queryFn: () => fetchCameras(filters),
});
};
3. components/
Contains the Views.
- Heavy Logic:
camera-form.tsx(Zod validation, API mutation). - Presentation:
camera-card.tsx(Displays a single camera). - Analytics:
agitation-trend-chart.tsx,metrics-chart.tsx.
Why this structure?
- Isolation: You can delete the
features/camerasfolder and only that feature disappears. - Discoverability: "Where is the code for the Camera Card?" ->
features/cameras/components. - Scalability: Adding a new feature doesn't clutter the global
componentsfolder.