Courses/Grit Web/Code Generator Mastery
Course 2 of 8~30 min15 challenges

Code Generator Mastery

In this course, you will master the grit generate resource command — the most powerful tool in the Grit CLI. You will learn how to generate full-stack CRUD features from a single command, understand every field type, use modifiers and relationships, and keep your Go and TypeScript types perfectly in sync.


What is Code Generation?

Every feature in a web app needs the same set of files: a database model to store data, a service for business logic, a handler for HTTP endpoints, validation schemas, TypeScript types, and React pages for the admin panel.

Instead of manually creating all of these files for every feature — which is tedious and error-prone — the Grit code generator creates all of them from one command. You describe your data (a "Book" with a title, author, and page count), and Grit writes every file, wires up every route, and registers everything in the admin panel automatically.

CRUD: Create, Read, Update, Delete — the four basic operations for any data in your app. A blog has CRUD for posts (create a post, read/list posts, update a post, delete a post). An e-commerce site has CRUD for products. Almost every feature is built on CRUD.
Resource: In Grit, a resource is a data entity with its full stack of files. When you generate a "Product" resource, you get everything needed to create, list, view, edit, and delete products — from the database table all the way to the admin UI.
Code generation is not just about saving time. It also ensures consistency — every resource follows the same patterns, naming conventions, and folder structure. This makes your codebase easier to navigate and maintain as it grows.

Your First Resource

Let's generate a Book resource. Open your terminal in the project root and run:

Terminal
grit generate resource Book --fields "title:string,author:string,pages:int,published:bool"

Let's break down each part of this command:

  • grit generate resource — the command that triggers the code generator
  • Book — the resource name, always in PascalCase and singular (Book, not Books)
  • --fields — inline field definitions, a comma-separated list
  • title:string — a field definition: field name : field type

Generated Files

That single command creates all of these files:

Generated File Tree
apps/api/internal/models/book.go          # Go model (database table)
apps/api/internal/services/book_service.go # Business logic (CRUD operations)
apps/api/internal/handlers/book_handler.go # HTTP endpoints (REST API)
packages/shared/schemas/book.ts            # Zod validation schema
packages/shared/types/book.ts              # TypeScript type definitions
apps/admin/app/(dashboard)/books/page.tsx  # Admin page with DataTable

It also injects code into existing files:

  • routes.go — registers all Book API routes (/api/books, /api/books/:id)
  • database.go — adds Book to the auto-migration list
  • main.go — initializes the Book service and handler
1

Challenge: Generate Your First Resource

Run the command above to generate a Book resource. After it completes, open your project in your editor and verify that each of the 6 files listed above was created. Check routes.go — can you find the new Book routes?

Understanding the Generated Go Model

Open apps/api/internal/models/book.go. You will see something like this:

apps/api/internal/models/book.go
package models

import "gorm.io/gorm"

type Book struct {
    gorm.Model
    Title     string `gorm:"not null" json:"title"`
    Author    string `gorm:"not null" json:"author"`
    Pages     int    `gorm:"not null" json:"pages"`
    Published bool   `gorm:"default:false" json:"published"`
}

Explained

  • gorm.Model gives you four fields for free: ID (primary key), CreatedAt, UpdatedAt, and DeletedAt (for soft deletes)
  • gorm:"not null" means the database requires this field — it cannot be empty
  • json:"title" controls how the field appears in API responses (lowercase, matching JavaScript conventions)
  • The struct name Book becomes the database table books (GORM automatically pluralizes and lowercases it)
  • gorm:"default:false" on the Published field means new books are unpublished by default
Struct: Go's way of defining a data structure. Like a class in other languages, but without methods attached directly. A struct groups related fields together — Book groups Title, Author, Pages, and Published into one type.
Tags: The backtick strings after each field (like gorm:"not null" json:"title"). They are metadata that tells libraries how to handle the field. The gorm tag tells GORM how to create the database column. The json tag tells the JSON encoder what name to use in API responses.
2

Challenge: Explore gorm.Model

Open the generated book.go model. Find the gorm.Model line. What 4 fields does it give you for free? (Hint: check the GORM documentation.)

3

Challenge: Understand GORM Tags

Look at the GORM tags on the model fields. What would happen if you removed not null from the Title field? Try it — restart the API and create a book without a title. Does the database accept it?

Field Types

Grit supports 13 field types. Each type maps to a Go type, a TypeScript type, and a database column type. Here is the complete reference:

TypeGoTypeScriptDatabaseUse for
stringstringstringvarcharShort text (names, titles)
textstringstringtextLong text (descriptions, bios)
richtextstringstringtextHTML content (blog posts) — Tiptap editor in admin
intintnumberintegerWhole numbers (age, count)
uintuintnumberunsigned integerPositive-only numbers (IDs)
floatfloat64numberdouble precisionDecimal numbers (price, rating)
boolboolbooleanbooleanTrue/false (published, active)
datetimetime.Timestring (ISO 8601)timestampDate and time (event_date)
datetime.Timestring (ISO 8601)dateDate only (birthday, due_date)
slugstringstringvarchar (unique index)URL-friendly string (auto-generated)
belongs_touintnumberinteger (FK)Foreign key relationship
many_to_many[]ModelModel[]junction tableMany-to-many relationship
string_arraypq.StringArraystring[]jsonbArray of strings (tags, categories)
The most commonly used types are string, text, int, float, and bool. You will use belongs_to and many_to_many once you start building relationships between resources.
4

Challenge: Pick the Right Type

Which field type would you use for each of these?

  • 1. A product price (e.g., $29.99)
  • 2. A blog post body with formatting
  • 3. An article's publish date
  • 4. A user's age

(Answers: float, richtext, date or datetime, int)

Generate with Different Fields

Let's see progressively more complex examples to build your intuition.

Example 1: Simple Blog Post

Terminal
grit generate resource Post --fields "title:string,content:richtext,published:bool"

This generates a Post with a short title, a rich text editor for the content (with bold, italic, headings, links, etc.), and a boolean toggle for published/draft status.

Example 2: Product with Price

Terminal
grit generate resource Product --fields "name:string,description:text,price:float,stock:int,active:bool"

Notice how description uses text (plain long text) instead of richtext — product descriptions usually don't need a rich text editor. The price uses float for decimal values, and stock uses int for whole numbers.

Example 3: Event with Dates

Terminal
grit generate resource Event --fields "name:string,description:text,start_date:datetime,end_date:datetime,capacity:int"

Events use datetime for start and end times. The admin form will show a date-time picker for these fields. The capacity field uses int for the maximum number of attendees.

5

Challenge: Generate a Post Resource

Generate the Post resource from Example 1. Open the admin panel at http://localhost:3001 and create a blog post. Does the richtext field show a rich text editor with formatting options?

6

Challenge: Generate a Product Resource

Generate the Product resource from Example 2. Create 3 products in the admin panel with different prices (e.g., $9.99, $24.50, $149.00). Does the DataTable show all three products with their prices?

Field Modifiers

Modifiers let you customize how a field behaves. You add them after the field type, separated by a colon.

:unique — No Duplicates

Adding :unique creates a unique index on the database column, so no two records can have the same value for that field.

Terminal
grit generate resource Category --fields "name:string:unique,description:text:optional"

With name:string:unique, if you try to create two categories both named "Technology", the database will reject the second one with a unique constraint error.

:optional — Nullable Fields

By default, all fields are required (NOT NULL in the database). Adding :optional allows the field to be empty (NULL).

In the example above, description:text:optional means a category can be created without a description. The name field is still required — every category must have a name.

You can combine modifiers: email:string:unique:optional means the field is nullable, but if a value IS provided, it must be unique across all records.
7

Challenge: Test the Unique Modifier

Generate the Category resource with name:string:unique. Create a category named "Technology". Now try to create another category with the exact same name. What error message do you get?

8

Challenge: Test the Optional Modifier

Using the same Category resource (which has description:text:optional), create a category WITHOUT filling in the description field. Does it save successfully? Now create one WITH a description. Both should work.

Slug Fields

A slug is a URL-friendly version of a string. The title "My First Blog Post" becomes the slug my-first-blog-post. Slugs are used in URLs so visitors see /articles/my-first-blog-post instead of /articles/42.

Slug: A URL-friendly string derived from another field. Slugs are lowercase, use hyphens instead of spaces, and strip special characters. They make URLs readable and SEO-friendly.

The format is slug:slug:source_field — the third part tells Grit which field to generate the slug from:

Terminal
grit generate resource Article --fields "title:string,slug:slug:title,content:richtext"

Here, slug:slug:title means: create a field called "slug", of type "slug", derived from the "title" field. When you create an Article with the title "Hello World", the slug is automatically set to hello-world.

If two articles have the same title, Grit appends a random suffix to keep the slug unique (e.g., hello-world-a3f2). Slugs always have a unique index in the database.

9

Challenge: Test Slug Generation

Generate the Article resource with a slug field. Create an article with the title "Hello World". What slug was auto-generated? Now create another article with the exact same title — is the slug different? Check both slugs in the admin panel.

Relationships: belongs_to

Real apps have related data. A blog post belongs to a category. An order belongs to a customer. The belongs_to type creates a foreign key relationship between two resources.

Foreign Key: A column that references the primary key (ID) of another table. For example, category_id on the posts table points to id on the categories table. This is how databases link related records together.

The format is field_name:belongs_to:ParentModel:

Terminal
# First, generate the parent resource
grit generate resource Category --fields "name:string:unique"

# Then generate the child with a belongs_to relationship
grit generate resource Post --fields "title:string,content:richtext,category_id:belongs_to:Category"

This creates:

  • A CategoryID uint field on the Post model
  • A foreign key index in the database
  • A dropdown select in the admin form to pick a category
  • The API automatically returns the related category data when fetching posts
Always generate the parent resource first (Category), then the child (Post). The parent must exist before you can reference it with belongs_to.
10

Challenge: Build a belongs_to Relationship

Generate Category (with name:string), then Post with category_id:belongs_to:Category. In the admin panel, create 2 categories ("Tech" and "Design"), then create a post and select a category from the dropdown. Open GORM Studio to verify the category_id column has the correct value.

Relationships: many_to_many

Sometimes, a single belongs_to is not enough. A blog post can have many tags, and a tag can belong to many posts. This is a many-to-many relationship.

Junction Table: A table that connects two other tables in a many-to-many relationship. For Posts and Tags, GORM creates a post_tags table with post_id and tag_id columns. Each row represents one connection between a post and a tag.

The format is field_name:many_to_many:OtherModel:

Terminal
# Generate the related resource first
grit generate resource Tag --fields "name:string:unique"

# Then generate with many_to_many
grit generate resource Post --fields "title:string,content:richtext,tag_ids:many_to_many:Tag"

In the admin form, the tag_ids field renders as a multi-select — you can pick multiple tags for a single post. Behind the scenes, GORM manages the junction table automatically.

You can combine belongs_to and many_to_many on the same resource. For example, a Post can belong to one Category AND have many Tags: category_id:belongs_to:Category,tag_ids:many_to_many:Tag
11

Challenge: Build a many_to_many Relationship

Generate Tag (with name:string:unique), then Post with tag_ids:many_to_many:Tag. Create 3 tags ("Go", "React", "Grit"). Create a post and select multiple tags. Open GORM Studio and look for the post_tags junction table — do you see the connections?

Interactive Mode

Typing all fields on one long command line can be tedious, especially for resources with many fields. Grit offers two alternatives.

Interactive Prompts (-i)

Use the -i flag for a step-by-step interactive experience:

Terminal
grit generate resource Product -i

Grit will prompt you to add fields one at a time, asking for the field name, type, and any modifiers. Type "done" when you have added all your fields.

YAML Definition (--from)

For complex resources, you can define fields in a YAML file:

product.yaml
name: Product
fields:
  - name: string
  - description: text:optional
  - price: float
  - active: bool
Terminal
grit generate resource Product --from product.yaml

YAML files are great for resources you plan to regenerate or share with your team.

12

Challenge: Try Interactive Mode

Generate a Contact resource using interactive mode (-i). Add these fields one by one: name (string), email (string:unique), phone (string:optional), message (text). Verify all files are created correctly.

Removing a Resource

Made a mistake? Want to start over? The grit remove resource command cleanly removes everything:

Terminal
grit remove resource Book

This deletes all generated files (model, service, handler, schemas, types, admin page) AND reverses all code injections (imports, route registrations, database migrations, admin entries).

Your project returns to exactly how it was before you ran grit generate resource Book.

The remove command only removes generated code. If you manually edited the generated files (added custom logic to the service, for example), those changes will be lost. Always put custom business logic in separate files.
13

Challenge: Remove and Verify

Run grit remove resource Book. Verify the model file apps/api/internal/models/book.go is gone. Check routes.go — are the Book routes removed? Check database.go — is Book removed from the migration list?

Type Sync

Sometimes you need to manually edit a Go model — add a field, change a type, tweak a tag. When you do, the TypeScript types and Zod schemas become out of date. That's where grit sync comes in.

Terminal
grit sync

This command reads all your Go models, parses their struct fields and tags, and regenerates the matching TypeScript interfaces and Zod validation schemas. Your frontend types stay perfectly in sync with your backend models.

Type Sync: The process of keeping Go types and TypeScript types in sync. Grit parses your Go struct tags and generates matching TypeScript interfaces (for type checking) and Zod validation schemas (for runtime validation). This prevents type mismatches between backend and frontend.
Run grit sync anytime you manually edit a Go model. It's fast and safe — it only updates the generated type files, never your custom code.
14

Challenge: Test Type Sync

Open a generated Go model and manually add a new field (e.g., Rating float64 `gorm:"default:0" json:"rating"`). Run grit sync. Check the corresponding TypeScript type file — did a rating: number field appear? Check the Zod schema — was a rating validator added?

Code Markers

You might wonder: how does Grit know where to inject code into existing files? The answer is code markers — special comments placed in your scaffolded files.

apps/api/internal/routes/routes.go
// ... existing routes ...

bookGroup := api.Group("/books")
bookGroup.GET("", handler.BookHandler.GetAll)
bookGroup.POST("", handler.BookHandler.Create)
// grit:routes

The markers look like // grit:routes, // grit:models, and // grit:imports. When you generate a resource, Grit finds these markers and inserts code right before them. When you remove a resource, Grit deletes those injected lines.

The markers are invisible to your app (they're just comments), but they are essential for the code generator to work correctly.

Never delete or move these marker comments. If you remove a // grit:routes marker, Grit won't know where to add routes for new resources. If you move them, code will be injected in the wrong place.

Summary

You now have a complete understanding of Grit's code generator. Here's what you learned:

  • Code generation basics — one command creates model, service, handler, schemas, types, and admin page
  • Go model structure — gorm.Model, struct tags, field types, and how they map to database columns
  • All 13 field types — string, text, richtext, int, uint, float, bool, datetime, date, slug, belongs_to, many_to_many, string_array
  • Field modifiers — :unique for no duplicates, :optional for nullable fields
  • Slug fields — auto-generated URL-friendly strings from a source field
  • belongs_to relationships — foreign keys with dropdown selects in the admin
  • many_to_many relationships — junction tables with multi-select in the admin
  • Interactive mode and YAML — alternative ways to define fields
  • Removing resources — clean removal of all generated files and injections
  • Type sync — keeping Go and TypeScript types in sync with grit sync
  • Code markers — how Grit knows where to inject and remove code
15

Challenge: Final Challenge: Build a Bookstore

Put everything together. Build a complete bookstore by generating these resources in order:

  1. Authorname:string,bio:text:optional
  2. Genrename:string:unique
  3. Booktitle:string,slug:slug:title,isbn:string:unique,price:float,author_id:belongs_to:Author,genre_ids:many_to_many:Genre,synopsis:text:optional,published:bool

Then:

  • • Create 3 authors, 5 genres, and 10 books in the admin panel
  • • Assign each book to an author and multiple genres
  • • Run grit routes to see all generated endpoints
  • • Open the API docs and test the GET /api/books endpoint