Touring what got generated

Read every file the generator dropped and connect them mentally.

8 minmedium

Eight files appeared. Now we walk through each one so you understand what the generator did — and how to extend it when the default isn't enough.

1. The model

apps/api/internal/models/product.go
type Product struct {
ID uuid.UUID `gorm:"type:uuid;primaryKey" json:"id"`
Name string `gorm:"not null" json:"name"`
Price decimal.Decimal `gorm:"type:decimal(10,2);not null" json:"price"`
StockQuantity int `gorm:"default:0" json:"stock_quantity"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

Standard GORM struct. The tags encode the column type, constraints, and JSON serialization. UUID primary key (you can't guess them; great for IDOR defence).

2. The service

apps/api/internal/services/product.go
type ProductService struct { db *gorm.DB }
func (s *ProductService) Create(in CreateProductInput) (*Product, error) {
p := &Product{Name: in.Name, Price: in.Price, StockQuantity: in.StockQuantity}
if err := s.db.Create(p).Error; err != nil {
return nil, fmt.Errorf("creating product: %w", err)
}
return p, nil
}
// List, GetByID, Update, Delete — similar pattern.

3. The handler

apps/api/internal/handlers/product.go
func (h *ProductHandler) Create(c *gin.Context) {
var in services.CreateProductInput
if err := c.ShouldBindJSON(&in); err != nil {
respond.Error(c, 422, "VALIDATION_ERROR", err)
return
}
p, err := h.svc.Create(in)
if err != nil {
respond.Error(c, 500, "INTERNAL_ERROR", err)
return
}
respond.Created(c, p, "Product created")
}

Thin handler, calls service, shapes response. Exactly the convention you learnt.

4. The routes injection

The generator edits routes.go to mount the new handler:

apps/api/internal/routes/routes.go (excerpt)
products := api.Group("/products")
products.Use(middleware.Auth(...))
{
products.GET("", productHandler.List)
products.POST("", productHandler.Create)
products.GET("/:id", productHandler.GetByID)
products.PUT("/:id", productHandler.Update)
products.DELETE("/:id", productHandler.Delete)
}

5 + 6. Zod schema + TS type

packages/shared/src/schemas/product.ts
export const ProductSchema = z.object({
name: z.string().min(1),
price: z.string().regex(/^\d+\.\d{2}$/),
stockQuantity: z.number().int().min(0).default(0),
})
export type Product = z.infer<typeof ProductSchema> & { id: string }

7. The React Query hook

apps/web/hooks/use-products.ts
export function useProducts() {
return useQuery({ queryKey: ['products'], queryFn: api.products.list })
}
export function useCreateProduct() {
const qc = useQueryClient()
return useMutation({
mutationFn: api.products.create,
onSuccess: () => qc.invalidateQueries({ queryKey: ['products'] }),
})
}

8. The admin resource page

apps/admin/app/resources/products/page.tsx
export default function ProductsPage() {
return defineResource({
model: "products",
columns: [
{ key: "name", label: "Name" },
{ key: "price", label: "Price", format: "money" },
{ key: "stockQuantity", label: "Stock", format: "number" },
],
form: [
{ name: "name", type: "text", required: true },
{ name: "price", type: "money", required: true },
{ name: "stockQuantity", type: "number", default: 0 },
],
})
}

That's the Filament-style declaration. No HTML, no form wiring. DataTable + FormBuilder do the rest.

The generator is a starting point, not a final word. Once the files exist, edit them. Add custom service methods, extend the admin columns, change the form. The generator runs once; the code is yours from there.

Quick check

You want the admin Products page to show a 'Total inventory value' summary at the top. Where do you add it?

Try it

Open three of the eight generated files (your pick) and answer in notes.md:

  • Which file?
  • What does the first 10 lines do?
  • If you were to extend it (e.g., add "is_active" to the model), what would you change?

What's next

One more command for the toolkit: grit sync. The Go side keeps changing; the TypeScript types need to keep up. That's the next (and last) lesson of chapter 4.

Spot a typo? Have an idea?

Help us improve this lesson. One click opens a GitHub issue with the lesson URL pre-filled — suggest clearer wording, report a bug, or request more depth. The course keeps improving thanks to learners like you.

Suggest an improvement on GitHub