Weeks 1-7 of the template upgrade plan: - pkg/api: typed HTTPError with sentinels, Wrap/WrapMiddleware, Bind, health probes, OpenAPI schema/param builders - skeleton/packages: ui (design tokens, components), layout (DashboardShell), auth (AuthProvider, ProtectedRoute), api-client - skeleton/pkg: httperror, app/handler, app/bind, app/health, auth (JWT/API key middleware) - components/app-nextjs: Next.js 14 App Router template with dashboard, server actions, auth - cookbooks/feature-development.md with test and validation scripts - Handler tests for components, project management, and woodpecker webhook - 3 rounds of code review fixes applied Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| README.md | ||
Dashboard App Example
This example demonstrates a full-stack composable monorepo project using rdev's enhanced templates with:
- Chassis patterns: Wrap, Bind, HTTPError, Health probes
- Design system: packages/ui, packages/layout
- Authentication: pkg/auth, packages/auth
- OpenAPI: Auto-generated TypeScript client
- Next.js 14: App Router with server actions
Creating This Example
# 1. Create the project
curl -X POST "$RDEV_API_URL/project" \
-H "X-API-Key: $RDEV_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "dashboard-app",
"description": "Example dashboard application"
}'
# 2. Add the API service
curl -X POST "$RDEV_API_URL/projects/dashboard-app/components" \
-H "X-API-Key: $RDEV_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "service",
"name": "api"
}'
# 3. Add the Next.js dashboard
curl -X POST "$RDEV_API_URL/projects/dashboard-app/components" \
-H "X-API-Key: $RDEV_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "app-nextjs",
"name": "dashboard"
}'
Project Structure
After creation, the project looks like:
dashboard-app/
├── CLAUDE.md # AI routing with guides
├── README.md # Project docs
├── go.work # Go workspace
├── pnpm-workspace.yaml # pnpm monorepo config
├── .woodpecker.yml # CI pipeline
│
├── pkg/ # Shared Go packages
│ ├── app/ # Wrap, Bind, Health
│ │ ├── app.go # NewApp, Run
│ │ ├── handler.go # Wrap pattern
│ │ ├── bind.go # BindAndValidate
│ │ └── health.go # Health probes
│ ├── httperror/ # Typed HTTP errors
│ │ └── error.go # Sentinels, factory functions
│ ├── httpresponse/ # JSON envelope
│ ├── httpvalidation/ # Validator wrapper
│ ├── middleware/ # HTTP middleware
│ └── auth/ # JWT + API key auth
│ ├── auth.go # Interface, types
│ ├── context.go # GetUser, SetUser
│ ├── jwt.go # JWTValidator
│ ├── apikey.go # APIKeyValidator
│ └── middleware.go # Auth middleware
│
├── packages/ # Shared TypeScript packages
│ ├── ui/ # Design system
│ │ ├── src/styles/tokens.css # CSS custom properties
│ │ ├── src/utils/cn.ts # clsx + tailwind-merge
│ │ └── src/components/ # Button, Card, Input, etc.
│ ├── layout/ # Shell components
│ │ ├── src/DashboardShell.tsx # Main layout
│ │ ├── src/Sidebar.tsx # Navigation
│ │ └── src/Header.tsx # Top bar
│ ├── auth/ # React auth
│ │ ├── src/AuthProvider.tsx # Context provider
│ │ ├── src/useAuth.ts # Hook
│ │ └── src/ProtectedRoute.tsx # Route guard
│ ├── logger/ # Structured logging
│ └── api-client/ # Generated client
│ └── src/schema.d.ts # TypeScript types
│
├── services/ # Backend services
│ └── api/
│ ├── cmd/server/main.go # Entry point
│ ├── internal/
│ │ ├── api/routes.go # Chi routes with Wrap
│ │ ├── config/config.go # Configuration
│ │ └── domain/ # Business models
│ ├── migrations/ # SQL migrations
│ ├── Dockerfile # Multi-stage Go build
│ └── component.yaml # Port, dependencies
│
└── apps/ # Frontend applications
└── dashboard/
├── src/app/
│ ├── layout.tsx # Root layout
│ ├── globals.css # Design tokens import
│ ├── (dashboard)/ # Protected route group
│ │ ├── layout.tsx # DashboardShell
│ │ ├── page.tsx # Home
│ │ └── settings/page.tsx
│ └── login/page.tsx # Public login
├── src/components/
│ └── providers.tsx # AuthProvider wrapper
├── src/actions/ # Server actions
├── Dockerfile # Next.js standalone
└── component.yaml
Development Workflow
Local Development
# Start infrastructure (Postgres, Redis)
docker-compose up -d
# Install dependencies
./scripts/install.sh
# Start all services
./scripts/dev.sh
Building a Feature
-
Create feature branch
git checkout -b feature/user-profile -
Add API endpoint using Wrap + Bind:
func (h *Handler) GetMe() http.HandlerFunc { return app.Wrap(func(w http.ResponseWriter, r *http.Request) error { user, ok := auth.GetUser(r.Context()) if !ok { return httperror.Unauthorized("not authenticated") } profile, err := h.svc.GetByID(r.Context(), user.ID) if err != nil { return httperror.WrapError(err, "failed to get profile") } httpresponse.JSON(w, http.StatusOK, profile) return nil }) } -
Regenerate TypeScript client:
./scripts/generate-client.sh -
Add frontend page using design system:
import { Card, CardHeader, CardTitle } from '@dashboard-app/ui'; import { useAuth } from '@dashboard-app/auth'; export default function ProfilePage() { const { user } = useAuth(); return ( <Card> <CardHeader> <CardTitle>Profile: {user?.name}</CardTitle> </CardHeader> </Card> ); } -
Run tests and quality checks:
./scripts/quality.sh -
Commit and push:
git add -A git commit -m "feat: add user profile" git push -u origin feature/user-profile
Key Patterns
Backend: Wrap Pattern
// Error-returning handlers become http.HandlerFunc
app.Wrap(func(w http.ResponseWriter, r *http.Request) error {
// Return errors - they become proper HTTP responses
if err := doThing(); err != nil {
return httperror.Internal("something went wrong")
}
httpresponse.JSON(w, http.StatusOK, data)
return nil
})
Backend: Bind Pattern
// Decode + validate in one call
var req CreateItemRequest
if err := app.BindAndValidate(r, &req); err != nil {
return err // Validation errors become 400 with details
}
Frontend: Auth Hook
const { user, isLoading, login, logout } = useAuth();
if (isLoading) return <Spinner />;
if (!user) return <LoginRedirect />;
Frontend: Design System
import { Button, Card, Input, Badge } from '@dashboard-app/ui';
import { DashboardShell, Sidebar } from '@dashboard-app/layout';
Deployment
CI/CD is automated via Woodpecker:
- Push triggers build
- Kaniko builds Docker images
- Images pushed to registry
- kubectl deploys to K8s
Access the deployed app:
open https://<slug>.threesix.ai
Cleanup
curl -X DELETE "$RDEV_API_URL/project/dashboard-app" \
-H "X-API-Key: $RDEV_API_KEY"