Adding a New Feature
Step-by-step guide to add a new feature to HiveForge.
1. Plan the Feature
Before coding, define:
- User requirements
- Database schema changes
- API endpoints needed
- UI components required
- Permissions/access control
2. Database Schema
Create migration:
cd supabase/migrations
touch $(date +%Y%m%d%H%M%S)_add_projects.sqlAdd schema:
-- Create projects table
CREATE TABLE projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
name TEXT NOT NULL,
description TEXT,
created_by UUID NOT NULL REFERENCES profiles(id),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- RLS policies
CREATE POLICY "Users can view org projects"
ON projects FOR SELECT
USING (
organization_id IN (
SELECT organization_id FROM organization_members
WHERE user_id = auth.uid()
)
);
CREATE POLICY "Members can create projects"
ON projects FOR INSERT
WITH CHECK (
organization_id IN (
SELECT organization_id FROM organization_members
WHERE user_id = auth.uid()
)
);
-- Indexes
CREATE INDEX idx_projects_org ON projects(organization_id);Apply migration:
pnpm db:migrate3. Backend API
Create router:
# apps/api/app/routers/projects.py
from fastapi import APIRouter, Depends, HTTPException
from app.schemas.project import ProjectCreate, ProjectUpdate, ProjectResponse
from app.services.projects import ProjectService
from app.core.dependencies import get_current_user
router = APIRouter(prefix="/api/projects", tags=["projects"])
@router.get("/", response_model=list[ProjectResponse])
async def list_projects(
organization_id: str,
user = Depends(get_current_user)
):
return await ProjectService.list(organization_id, user.id)
@router.post("/", response_model=ProjectResponse)
async def create_project(
data: ProjectCreate,
user = Depends(get_current_user)
):
return await ProjectService.create(data, user.id)
@router.put("/{project_id}", response_model=ProjectResponse)
async def update_project(
project_id: str,
data: ProjectUpdate,
user = Depends(get_current_user)
):
return await ProjectService.update(project_id, data, user.id)
@router.delete("/{project_id}")
async def delete_project(
project_id: str,
user = Depends(get_current_user)
):
await ProjectService.delete(project_id, user.id)
return {"status": "deleted"}Create service:
# apps/api/app/services/projects.py
from app.core.database import get_db
class ProjectService:
@staticmethod
async def create(data: ProjectCreate, user_id: str):
db = get_db()
result = await db.from_("projects").insert({
"organization_id": data.organization_id,
"name": data.name,
"description": data.description,
"created_by": user_id,
}).execute()
return result.data[0]
@staticmethod
async def list(organization_id: str, user_id: str):
db = get_db()
result = await db.from_("projects")\
.select("*")\
.eq("organization_id", organization_id)\
.execute()
return result.dataRegister router:
# apps/api/app/main.py
from app.routers import projects
app.include_router(projects.router)4. Frontend Components
Create types:
// packages/types/src/project.ts
export interface Project {
id: string
organization_id: string
name: string
description: string | null
created_by: string
created_at: string
updated_at: string
}
export interface CreateProjectInput {
organization_id: string
name: string
description?: string
}Create API client:
// apps/web/src/lib/api/projects.ts
import { apiClient } from './client'
import type { Project, CreateProjectInput } from '@hiveforge/types'
export async function getProjects(organizationId: string): Promise<Project[]> {
return apiClient.get(`/api/projects?organization_id=${organizationId}`)
}
export async function createProject(data: CreateProjectInput): Promise<Project> {
return apiClient.post('/api/projects', data)
}
export async function updateProject(id: string, data: Partial<Project>): Promise<Project> {
return apiClient.put(`/api/projects/${id}`, data)
}
export async function deleteProject(id: string): Promise<void> {
return apiClient.delete(`/api/projects/${id}`)
}Create components:
// apps/web/src/components/projects/ProjectList.tsx
'use client'
import { useEffect, useState } from 'react'
import { getProjects } from '@/lib/api/projects'
import { useOrganization } from '@/hooks/useOrganization'
import type { Project } from '@hiveforge/types'
export function ProjectList() {
const { organization } = useOrganization()
const [projects, setProjects] = useState<Project[]>([])
const [loading, setLoading] = useState(true)
useEffect(() => {
if (organization) {
loadProjects()
}
}, [organization])
async function loadProjects() {
try {
const data = await getProjects(organization.id)
setProjects(data)
} catch (error) {
console.error('Failed to load projects:', error)
} finally {
setLoading(false)
}
}
if (loading) return <div>Loading...</div>
return (
<div>
<h2>Projects</h2>
<ul>
{projects.map(project => (
<li key={project.id}>{project.name}</li>
))}
</ul>
</div>
)
}5. Add Routes
// apps/web/src/app/(dashboard)/projects/page.tsx
import { ProjectList } from '@/components/projects/ProjectList'
export default function ProjectsPage() {
return (
<div>
<h1>Projects</h1>
<ProjectList />
</div>
)
}6. Add Tests
Frontend tests:
// apps/web/src/components/projects/__tests__/ProjectList.test.tsx
import { render, screen, waitFor } from '@testing-library/react'
import { ProjectList } from '../ProjectList'
jest.mock('@/lib/api/projects', () => ({
getProjects: jest.fn(() => Promise.resolve([
{ id: '1', name: 'Test Project', organization_id: 'org1' }
])),
}))
test('renders project list', async () => {
render(<ProjectList />)
await waitFor(() => {
expect(screen.getByText('Test Project')).toBeInTheDocument()
})
})Backend tests:
# apps/api/tests/test_projects.py
import pytest
from httpx import AsyncClient
@pytest.mark.asyncio
async def test_create_project(client: AsyncClient, auth_headers):
response = await client.post(
"/api/projects",
headers=auth_headers,
json={
"organization_id": "org-id",
"name": "Test Project",
"description": "A test project"
}
)
assert response.status_code == 200
data = response.json()
assert data["name"] == "Test Project"7. Add Documentation
Update docs:
# docs/features/projects.mdx
# Projects
Create and manage projects within organizations...8. Deploy
# Build and test
pnpm build
pnpm test
# Deploy
git add .
git commit -m "feat: Add projects feature"
git push origin mainBest Practices
- Type Safety: Use TypeScript/Pydantic types
- Testing: Add unit and integration tests
- Documentation: Document API endpoints
- Permissions: Check user permissions
- Error Handling: Handle all error cases
- Validation: Validate input data
- Migrations: Version control database changes