rdev/cookbooks/composable-app.md
jordan 8282d60c69 feat: implement composable monorepo template system with component architecture
Adds the composable monorepo template system that generates project skeletons
with pluggable components (service, worker, app-react, app-astro, cli).

Key changes:
- Monorepo skeleton templates with shared pkg/, scripts/, and git hooks
- Component templates (service, worker, app-react, app-astro, cli) with
  Dockerfiles, CI steps, and component.yaml manifests
- Component domain model with validation and dependency resolution
- Component handler endpoints for CRUD and composition
- Template provider extended with BuildComposableProject and component assembly
- Deployer extended with composable project deployment support
- Handler timeout constants (TimeoutFastLookup through TimeoutLongRunning)
- envutil package for centralized env var reads with defaults
- api.DecodeJSON helper for standardized request body decoding
- Standardized response helpers (WriteBadRequest, WriteNotFound, etc.)
- Replaced fullstack-app cookbook with composable-app cookbook
- Hardened handler timeouts, logging, and error responses across all handlers

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 19:11:42 -07:00

424 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Composable App Cookbook
> Deploy a full-stack application with multiple components using composable monorepo templates.
## Overview
This cookbook creates a full-stack application by composing components:
```
POST /projects
Creates: Monorepo skeleton with shared packages
POST /projects/{id}/components (service)
Adds: Go API backend with CI step
POST /projects/{id}/components (app)
Adds: React/Astro frontend with CI step
POST /projects/{id}/deploy
Deploys all components to K8s
```
**Composable. Template-driven. CI auto-configured.**
---
## Prerequisites
### API Access
```bash
export RDEV_API_URL="https://rdev.masq-ops.orchard9.ai"
export RDEV_API_KEY="<your-api-key>"
```
### Infrastructure Required
- rdev-api running with embedded worker
- Gitea at https://git.threesix.ai
- Woodpecker CI at https://ci.threesix.ai
---
## Step 1: Create Project (Monorepo Skeleton)
```bash
curl -X POST "$RDEV_API_URL/projects" \
-H "X-API-Key: $RDEV_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "taskapp",
"description": "Task management application"
}'
```
**Response:**
```json
{
"data": {
"project_id": "taskapp",
"name": "taskapp",
"domain": "xyz789ab.threesix.ai",
"url": "https://xyz789ab.threesix.ai",
"git": {
"owner": "jordan",
"name": "taskapp",
"html_url": "https://git.threesix.ai/jordan/taskapp"
}
}
}
```
This creates:
```
taskapp/
├── CLAUDE.md # AI routing
├── README.md # Project docs
├── Procfile # Local dev (empty)
├── docker-compose.yml # Postgres, Redis
├── go.work # Go workspace
├── .woodpecker.yml # CI pipeline (template-provided)
├── .golangci.yml # Go linting
├── scripts/ # Discovery scripts
│ ├── discover.sh
│ ├── install.sh
│ ├── quality.sh
│ └── dev.sh
├── pkg/ # Shared Go packages
│ ├── app/ # Service bootstrapper
│ ├── middleware/ # HTTP middleware
│ ├── httpresponse/ # JSON responses
│ └── ...
├── services/ # (empty, ready for components)
├── apps/ # (empty, ready for components)
├── workers/ # (empty, ready for components)
└── cli/ # (empty, ready for components)
```
---
## Step 2: Add Backend Service
```bash
curl -X POST "$RDEV_API_URL/projects/taskapp/components" \
-H "X-API-Key: $RDEV_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "service",
"name": "api",
"template": "service"
}'
```
**Response:**
```json
{
"data": {
"type": "service",
"name": "api",
"path": "services/api",
"port": 8001,
"template": "service"
}
}
```
This adds:
```
services/api/
├── cmd/server/main.go # Entry point using pkg/app
├── internal/
│ ├── api/routes.go # Chi router setup
│ ├── api/handlers/health.go # Health endpoints
│ └── config/config.go # Configuration
├── migrations/ # Database migrations
├── Makefile # Build targets
├── Dockerfile # Multi-stage Go build
├── component.yaml # Port, dependencies
└── .env.example # Environment template
```
And updates:
- `.woodpecker.yml` - adds `build-api` step
- `Procfile` - adds `api: make -C services/api run`
- `go.work` - adds `use ./services/api`
---
## Step 3: Add Frontend App
```bash
curl -X POST "$RDEV_API_URL/projects/taskapp/components" \
-H "X-API-Key: $RDEV_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "app",
"name": "dashboard",
"template": "app-react"
}'
```
**Response:**
```json
{
"data": {
"type": "app",
"name": "dashboard",
"path": "apps/dashboard",
"port": 3001,
"template": "app-react"
}
}
```
This adds:
```
apps/dashboard/
├── src/
│ ├── App.tsx
│ ├── main.tsx
│ └── components/
├── package.json
├── vite.config.ts
├── tsconfig.json
├── Dockerfile # Multi-stage Node build
└── component.yaml # Port, dependencies
```
---
## Step 4: Customize with Claude (Optional)
Submit a build task to customize the components:
```bash
curl -X POST "$RDEV_API_URL/projects/taskapp/builds" \
-H "X-API-Key: $RDEV_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Implement a task management system:\n\nBACKEND (services/api):\n- Add Task model with id, title, description, status, created_at\n- Endpoints: GET /api/tasks, POST /api/tasks, PATCH /api/tasks/{id}, DELETE /api/tasks/{id}\n- In-memory storage for now\n\nFRONTEND (apps/dashboard):\n- Task list with status badges\n- Add task form\n- Mark complete/delete buttons\n- Dark theme with Tailwind\n- Fetch from /api/tasks",
"auto_commit": true,
"auto_push": true
}'
```
Monitor the build:
```bash
curl -s "$RDEV_API_URL/builds/{task_id}" \
-H "X-API-Key: $RDEV_API_KEY" | jq '.data.status'
```
---
## Step 5: Deploy All Components
```bash
curl -X POST "$RDEV_API_URL/projects/taskapp/deploy" \
-H "X-API-Key: $RDEV_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
```
This deploys all components to K8s, or deploy a single component:
```bash
curl -X POST "$RDEV_API_URL/projects/taskapp/deploy" \
-H "X-API-Key: $RDEV_API_KEY" \
-H "Content-Type: application/json" \
-d '{"component": "services/api"}'
```
---
## Step 6: Monitor CI Pipeline
```bash
curl -s "$RDEV_API_URL/projects/taskapp/pipelines" \
-H "X-API-Key: $RDEV_API_KEY" | jq '.data[0]'
```
The pipeline builds both components in parallel then deploys.
---
## Step 7: Verify Deployment
```bash
# Check site is live
curl -I https://xyz789ab.threesix.ai
# Test API
curl https://xyz789ab.threesix.ai/api/tasks | jq .
# View the app
open https://xyz789ab.threesix.ai
```
---
## Adding More Components
### Add a Background Worker
```bash
curl -X POST "$RDEV_API_URL/projects/taskapp/components" \
-H "X-API-Key: $RDEV_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "worker",
"name": "notifications",
"template": "worker"
}'
```
### Add a CLI Tool
```bash
curl -X POST "$RDEV_API_URL/projects/taskapp/components" \
-H "X-API-Key: $RDEV_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "cli",
"name": "taskctl",
"template": "cli"
}'
```
### List All Components
```bash
curl -s "$RDEV_API_URL/projects/taskapp/components" \
-H "X-API-Key: $RDEV_API_KEY" | jq '.data'
```
---
## Component Types
| Type | Directory | Templates | Default Port |
|------|-----------|-----------|--------------|
| service | `services/` | service | 8001+ |
| worker | `workers/` | worker | N/A |
| app | `apps/` | app-astro, app-react | 3001+ |
| cli | `cli/` | cli | N/A |
Ports auto-increment as you add components of the same type.
---
## Teardown
```bash
curl -X DELETE "$RDEV_API_URL/projects/taskapp" \
-H "X-API-Key: $RDEV_API_KEY"
```
Removes: DNS records, K8s deployments, project metadata. Gitea repo preserved.
---
## E2E Test Script
Run the full flow:
```bash
./cookbooks/scripts/composable-test.sh run my-test-app
```
Check status:
```bash
./cookbooks/scripts/composable-test.sh status my-test-app
```
Cleanup:
```bash
./cookbooks/scripts/composable-test.sh teardown my-test-app
```
---
## Architecture
```
┌─────────────────────────────────────────────────────────────────────┐
│ Composable Full-Stack Deployment │
│ │
│ POST /projects │
│ │ │
│ └──► Creates monorepo skeleton with: │
│ - Shared pkg/ (8 packages) │
│ - .woodpecker.yml (base CI) │
│ - Discovery scripts │
│ │
│ POST /projects/{id}/components (× N) │
│ │ │
│ └──► For each component: │
│ - Renders template to services/|apps/|workers/|cli/ │
│ - Inserts CI step into .woodpecker.yml │
│ - Updates Procfile, go.work │
│ │
│ POST /projects/{id}/builds (optional) │
│ │ │
│ └──► Claude customizes existing components │
│ │
│ Git push → Woodpecker CI: │
│ ├──► build-api (Kaniko → registry) │
│ ├──► build-dashboard (Kaniko → registry) │
│ └──► deploy (kubectl set image) │
│ │
│ Components live at https://{slug}.threesix.ai │
│ ├──► /api/* → services/api │
│ └──► /* → apps/dashboard │
└─────────────────────────────────────────────────────────────────────┘
```
---
## Troubleshooting
### Component addition fails
```bash
# Check project exists
curl -s "$RDEV_API_URL/projects/taskapp" \
-H "X-API-Key: $RDEV_API_KEY" | jq '.data'
# Check available templates
curl -s "$RDEV_API_URL/templates/components" \
-H "X-API-Key: $RDEV_API_KEY" | jq '.data'
```
### Build stuck in pending
```bash
# Check worker status
curl -s "$RDEV_API_URL/workers" -H "X-API-Key: $RDEV_API_KEY" | jq '.data.summary'
```
### Pipeline fails
```bash
# Get pipeline details
curl -s "$RDEV_API_URL/projects/taskapp/pipelines/1" \
-H "X-API-Key: $RDEV_API_KEY" | jq '.data'
# Check Woodpecker UI
open https://ci.threesix.ai/jordan/taskapp
```
### Deployment not updating
```bash
# Check K8s pods
kubectl get pods -n projects -l app=taskapp
# Check deployment events
kubectl describe deployment taskapp-api -n projects
```
---
## Related
- [Landing Page Cookbook](./landing-page.md) - Simple single-component site
- [Composable Monorepo Guide](../.claude/guides/services/composable-monorepo.md)
- [Component Templates](../.claude/guides/services/templates.md)