Full-Stack Feature Development
Overview
Adding a complete feature from UI to database in Toygres.
Checklist
- [ ] Database migration (if new data)
- [ ] Activity (if durable operation needed)
- [ ] Orchestration (if multi-step workflow)
- [ ] API endpoint in
toygres-server/src/api.rs - [ ] TypeScript types in
toygres-ui/src/lib/types.ts - [ ] API function in
toygres-ui/src/lib/api.ts - [ ] UI component update
- [ ] Build both:
cargo build --workspace && cd toygres-ui && npm run build - [ ] Deploy:
./deploy/deploy-to-aks.sh --https
Image Type Notes
pg_durable is a built-in special mode — it is NOT a user-uploadable runtime image.
When adding runtime image support:
- Runtime images from ACR are always deployed in stock mode
- Only the built-in
pg_durableuses special env vars and pg_hba.conf configuration - Force
image_type = 'stock'when aruntime_image_idis provided
API Endpoint Pattern
// In toygres-server/src/api.rs
async fn my_endpoint(
State(state): State<AppState>,
Path(name): Path<String>,
) -> Result<Json<serde_json::Value>, AppError> {
// Your logic here
Ok(Json(serde_json::json!({
"success": true,
"message": "Operation completed"
})))
}
// Add route in create_router():
.route("/api/instances/:name/my-action", post(my_endpoint))
Frontend API Function
// In toygres-ui/src/lib/api.ts
async myAction(name: string): Promise<{ success: boolean; message: string }> {
const response = await this.fetch(`/api/instances/${encodeURIComponent(name)}/my-action`, {
method: 'POST',
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Action failed');
}
return response.json();
}
React Mutation Pattern
const myMutation = useMutation({
mutationFn: (name: string) => api.myAction(name),
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ['instance', name] });
showToast('success', data.message);
},
onError: (error: Error) => {
showToast('error', `Failed: ${error.message}`);
},
});
// In JSX:
<Button onClick={() => myMutation.mutate(name)} disabled={myMutation.isPending}>
{myMutation.isPending ? 'Working...' : 'Do Action'}
</Button>
Simple vs Durable Operations
Simple/Atomic (direct K8s or DB call):
- UI → API → K8s/Database
- Example: Stop instance (scale replicas to 0)
Durable (multi-step, needs retry/recovery):
- UI → API → Start Orchestration → Activities
- Example: Create instance (deploy + wait + test connection)
Catalog Entity Pattern
For adding a new "catalog" of entities (like runtime images):
- Migration: Create table with id, name, metadata columns
- API: Add list + register endpoints
- Types: Add TypeScript interface
- API Client: Add fetch functions
- UI: Create list/form component
- Sidebar: Add navigation entry
See database-changes skill for detailed SQL patterns.