Your First Generated Application

By the end of this guide you will have used Gen-X to generate SeedVault — a seed inventory application with two related models, an enum field, and full CRUD views. The guide takes around 20 minutes.

What We’re Building

SeedVault tracks seed packets organised by plant family. A PlantFamily groups related seeds together — Solanaceae for tomatoes and peppers, Cucurbitaceae for squash and cucumbers. Each Seed record belongs to one plant family and carries a germination status that moves through a fixed lifecycle: stored, soaking, germinating, and planted. The application gives you list views, create and edit forms, and detail pages for both models out of the box.

Before You Begin

You need a working Gen-X environment. If you haven’t set it up yet, start with Installation.

Step 1 — Register SeedVault in ATC

Open the App Tower Control at http://localhost:4555 and create a new application entry.

  1. Click New App in the top navigation.
  2. Fill in the registration form:
    • Name: seed-vault
    • URL: seed-vault
    • Port: 8086
  3. Click Submit.
  4. On the app detail page that loads, note the integer App ID shown next to the application name. You will need this value in the depiction script.

📝 Note: The projectId argument in the depiction script must be the integer ID assigned by ATC during registration, not the app name. Find it in the app detail view after clicking Submit.

Step 2 — Write the Depiction Script

Create a new file for the depiction script. By convention, depiction scripts live in src/srv/depicters/ in the gen-x repository.

touch ~/go/src/gen-x/src/srv/depicters/seed_vault.go

The sections below build the script incrementally. The complete, copy-paste-ready version is at the end of this step.

2a — Initialize the Project

The CreateProject call registers the application with the generation pipeline and returns a ProjectConfig that every subsequent call depends on. Replace 42 with the App ID you noted in Step 1.

config := d.CreateProject(
    ctx,
    42,                                       // projectId from ATC
    "seed-vault",                             // app name
    "seed-vault",                             // folder name
    "github.com/your-org/seed-vault",         // Go module path
    nil,
)

2b — Add the PlantFamily Model

AddModel with crud: true generates the full actor set — list, create, edit, detail, and delete — for the model automatically.

plantFamilyModel := d.AddModel(ctx, config, "PlantFamily", true)
d.AddModelField(ctx, config, &mdl.ModelField{Name: "name", Kind: "string"}, plantFamilyModel)
d.AddModelField(ctx, config, &mdl.ModelField{Name: "description", Kind: "string"}, plantFamilyModel)

2c — Add the Seed Model

The germination_status field uses StringDataEnum to restrict its value to a fixed set of lifecycle states. The generated form will render a select input automatically.

seedModel := d.AddModel(ctx, config, "Seed", true)
d.AddModelField(ctx, config, &mdl.ModelField{Name: "name", Kind: "string"}, seedModel)
d.AddModelField(ctx, config, &mdl.ModelField{Name: "quantity", Kind: "int"}, seedModel)
d.AddModelField(ctx, config, &mdl.ModelField{Name: "germination_status", Kind: "string", Data: d.StringDataEnum([]string{"stored", "soaking", "germinating", "planted"})}, seedModel)

2d — Add a Relation

AddManyChildToOneParentRelation links Seed (the child) to PlantFamily (the parent). One plant family can have many seeds.

d.AddManyChildToOneParentRelation(ctx, config, seedModel, "PlantFamily")

📝 Note: AddManyChildToOneParentRelation automatically adds a plant_family_id foreign-key column to Seed. You do not need to declare it as a separate ModelField.

2e — Wire the Router and Navigation

AddPublicRouter registers all generated routes with the Gin router. The two AddIntoTopMenu calls add navigation links to the application’s top bar. CopiesOverwrittenFiles applies any handcrafted // |@@| W overrides at the end of the run.

d.AddPublicRouter(ctx)
d.AddIntoTopMenu(ctx, "Plant Families", "/plant-families")
d.AddIntoTopMenu(ctx, "Seeds", "/seeds")
d.CopiesOverwrittenFiles(ctx, config)

Complete Script

Here is the full seed_vault.go depiction file ready to copy into src/srv/depicters/.

package depicters

// |@@| F

import (
	"context"
	"gen-x/src/mdl"
)

type SeedVaultDepicter struct {
	Depicter *Depicter
}

func (this *SeedVaultDepicter) Depict(ctx context.Context) {
	config := this.Depicter.CreateProject(
		ctx,
		42,                                       // projectId from ATC
		"seed-vault",                             // app name
		"seed-vault",                             // folder name
		"github.com/your-org/seed-vault",         // Go module path
		nil,
	)

	this.PlantFamilyModel(ctx, config)
	this.SeedModel(ctx, config)
	this.SeedRelations(ctx, config)

	this.Depicter.AddPublicRouter(ctx)
	this.Depicter.AddIntoTopMenu(ctx, "Plant Families", "/plant-families")
	this.Depicter.AddIntoTopMenu(ctx, "Seeds", "/seeds")
	this.Depicter.CopiesOverwrittenFiles(ctx, config)
}

func (this *SeedVaultDepicter) PlantFamilyModel(ctx context.Context, config *mdl.ProjectConfig) {
	model := this.Depicter.AddModel(ctx, config, "PlantFamily", true)
	this.Depicter.AddModelField(ctx, config, &mdl.ModelField{Name: "name", Kind: "string"}, model)
	this.Depicter.AddModelField(ctx, config, &mdl.ModelField{Name: "description", Kind: "string"}, model)
}

func (this *SeedVaultDepicter) SeedModel(ctx context.Context, config *mdl.ProjectConfig) {
	model := this.Depicter.AddModel(ctx, config, "Seed", true)
	this.Depicter.AddModelField(ctx, config, &mdl.ModelField{Name: "name", Kind: "string"}, model)
	this.Depicter.AddModelField(ctx, config, &mdl.ModelField{Name: "quantity", Kind: "int"}, model)
	this.Depicter.AddModelField(ctx, config, &mdl.ModelField{Name: "germination_status", Kind: "string", Data: this.Depicter.StringDataEnum([]string{"stored", "soaking", "germinating", "planted"})}, model)
}

func (this *SeedVaultDepicter) SeedRelations(ctx context.Context, config *mdl.ProjectConfig) {
	model := this.Depicter.GetModel(ctx, "Seed")
	this.Depicter.AddManyChildToOneParentRelation(ctx, config, model, "PlantFamily")
}

Step 3 — Run the Generator

⚠️ Warning: Running the Depicter overwrites all // |@@| C files in the output directory. If ~/go/src/seed-vault already exists with custom changes not protected by // |@@| W, those changes will be lost.

From the gen-x repository root, run:

ENV=dev go run main.go depict seed-vault

The terminal prints a series of progress dots as files are written, and displays the output path at the end of a clean run — typically /tmp/generated_project_seed-vault, before the project is copied to ~/go/src/seed-vault.

Step 4 — Start SeedVault

cd ~/go/src/seed-vault && bird

Open http://localhost:8086/plant-families in your browser. You will see the plant family list page with a working create form.

Step 5 — Explore the Generated Output

The generator has produced a complete application. Here is what was created for the Seed model:

src/
├── mdl/
│   └── seed.go                          # Seed entity struct
├── srv/
│   ├── handlers/
│   │   └── seed_handler.go              # Create, Update, Delete for Seed
│   ├── fetchers/
│   │   └── seed_fetcher.go              # Read queries for Seed
│   ├── hydrators/
│   │   └── seed_hydrator.go             # Entity hydration from DB rows
│   ├── molders/
│   │   └── create_seed_form_molder.go   # Form → entity for create
│   │   └── update_seed_form_molder.go   # Form → entity for update
│   ├── form_validators/
│   │   └── create_seed_form_validator.go
│   │   └── update_seed_form_validator.go
│   ├── presenters/
│   │   └── seed_presenter.go
│   └── serializers/
│       └── seed_serializer.go
├── mae/
│   ├── new_seed_mae.go                  # Renders the create form
│   ├── create_seed_mae.go               # Processes the create submission
│   ├── edit_seed_mae.go                 # Renders the edit form
│   ├── update_seed_mae.go               # Processes the edit submission
│   ├── show_seed_mae.go                 # Detail view for a single Seed
│   ├── list_seeds_mae.go                # List view with pagination
│   └── delete_seed_mae.go              # Delete action

Each file carries a // |@@| C annotation at the top, meaning it will be regenerated on the next Depicter run.

What Happens When You Re-Run

One of Gen-X’s defining features is safe, repeatable regeneration. After you run the Depicter a second time:

  • Files annotated // |@@| C are overwritten with freshly generated code.
  • Files annotated // |@@| W are left untouched.
  • The depiction script itself carries // |@@| W by convention, so it is never clobbered by the generator.

This means you can extend the depiction script at any time — add a new field, a new model, or a new relation — and re-run to regenerate the entire application. Your custom business logic in // |@@| W files survives every regeneration cycle intact.

For a full explanation of how the annotation system works, see Modifying an App and File Header Reference.

Beyond This Walkthrough

Two natural directions from here:

  1. Extend SeedVault — Add more fields, validations, and relations to the depiction script and re-run. See the Depicter API Reference for all available methods.
  2. Understand the pipelineArchitecture Overview explains how the Depicter, Appliers, Handlers, and Configurators work together to produce the output.

Next Steps

  • Configuration — Configure Gen-X and understand the two configuration layers
  • Modifying an App — How to safely add custom code to a generated app