
Building a GORM CRUD Module with Codex: A Golang Engineer’s Workflow
As a Golang backend engineer, I spend a fair amount of my time writing repetitive boilerplate: struct definitions, GORM model tags, CRUD handlers, request validation, and the wiring that connects them all to an HTTP router. It’s the kind of work that isn’t hard, but it’s tedious enough to be a good candidate for AI-assisted development. Recently I used Codex to scaffold and refine a full CRUD management module for a single database table, and the experience taught me a few things about how to work effectively with an AI coding agent on a real Go project. This post walks through that workflow.
Why CRUD Is a Good Testbed for AI-Assisted Coding
A single-table CRUD module is deceptively simple on the surface — create, read, update, delete — but it touches almost every layer of a typical Go service: the GORM model, the repository or DAO layer, the service layer with business rules, the HTTP handlers, request/response DTOs, validation, error handling, and often a bit of pagination and filtering logic. That makes it a great sandbox for evaluating how well an AI coding tool understands Go idioms, GORM conventions, and the shape of a maintainable service architecture, without the complexity of multi-table joins or transactions muddying the results.
Setting Up the Task
I started by giving Codex a clear, structured prompt rather than a vague one. Instead of “build me a CRUD API,” I specified:
- The exact table schema (a
productstable with fields likeid,name,sku,price,stock,status,created_at,updated_at, and a soft-delete column) - The project layout I wanted to follow (
model/,repository/,service/,handler/,router/) - The GORM version and any project conventions, such as using
gorm.Modelversus a custom base struct - The HTTP framework in use (I was using Gin, but this generalizes to Echo or Fiber)
- Naming conventions already established in the codebase
Giving Codex this context up front mattered enormously. Coding agents produce noticeably better results when they aren’t guessing at architectural decisions, and a few extra sentences of context saved several rounds of back-and-forth.
What Codex Generated
Within a few iterations, Codex produced a reasonably complete module:
The model layer defined a Product struct with correct GORM tags — gorm:"column:name;size:100;not null" and similar — along with a TableName() method to pin the table name explicitly rather than relying on GORM’s pluralization inference, which is a habit worth encouraging in any generated code.
The repository layer wrapped *gorm.DB with methods like Create, FindByID, List, Update, and Delete. One detail Codex got right without being asked was using .Where("deleted_at IS NULL") semantics implicitly through GORM’s soft-delete support, and returning wrapped errors using fmt.Errorf("%w", err) so callers could still use errors.Is against gorm.ErrRecordNotFound.
The service layer added a thin layer of business validation — checking that stock isn’t negative, that sku is unique before insert, and that updates use partial-field semantics (Updates with a map rather than Save, to avoid accidentally zeroing out fields not present in the request).
The handler layer used Gin’s binding tags for request validation (binding:"required,min=1") and returned consistent JSON error responses.
Pagination was implemented as ?page=1&page_size=20 query parameters, translated into GORM’s Offset() and Limit(), with a separate Count() query for total records — a pattern that’s easy to get subtly wrong (e.g., forgetting to reset limit/offset before the count query) and one area where I had to correct the first draft.
Where Human Review Was Essential
Codex’s first pass was functional but not production-ready, and a few issues are worth calling out because they’re the kind of mistakes that are easy to miss in a quick review:
N+1 query risk in list endpoints. The initial version didn’t preload any associations, which was fine for a single-table case but is a trap worth flagging if the schema grows a foreign key later. I added an explicit comment noting where
Preload()would need to go.Update semantics. The first draft used
db.Save(&product), which overwrites every column, including zero-valued fields not present in the incoming request. I asked Codex to switch todb.Model(&product).Updates(map[string]interface{}{...})built from only the non-nil fields in the request DTO, which is the safer pattern for partial updates.Missing transaction boundaries. Even for single-table CRUD, an update that also needs to touch a cache or emit an event should be wrapped correctly. Codex didn’t add this by default since I hadn’t asked for it initially, which is a useful reminder that AI-generated code follows the spec you give it, not the spec you meant.
Error message consistency. The generated error responses mixed styles between handlers until I explicitly asked for a shared error-response helper. This is a common pattern with AI-assisted generation: consistency across files tends to degrade unless you review holistically rather than file-by-file.
Test coverage. Codex wrote a reasonable set of table-driven tests using
sqlmockfor the repository layer, but I had to prompt for edge cases like duplicate SKU conflicts and updating a soft-deleted record.
Iterating Efficiently
The workflow that worked best was iterative and specific: generate a layer, review it, point out the exact line or pattern that needed to change, and ask for a targeted fix rather than a full regeneration. Regenerating entire files tends to introduce new inconsistencies elsewhere; targeted edits keep the rest of the code stable. I also found it useful to ask Codex to explain its reasoning for GORM-specific choices (like hook usage in BeforeCreate), since that surfaced a couple of assumptions I wouldn’t have caught from the diff alone.
Takeaways
Using Codex for a GORM CRUD module didn’t eliminate the need for engineering judgment — it shifted where that judgment gets applied. Instead of typing out struct tags and repository boilerplate, I spent more time reviewing GORM semantics, catching subtle update-vs-save bugs, and enforcing architectural consistency across layers. For a well-understood pattern like single-table CRUD, that trade is a clear win: the scaffolding time dropped from roughly half a day to under an hour, while the review time stayed proportional to what I’d spend reviewing a junior engineer’s pull request. The lesson that generalizes beyond this one task is straightforward: give the agent precise schema and architectural context up front, review generated code the way you’d review a colleague’s PR rather than trusting it outright, and iterate with targeted fixes instead of wholesale regenerations.




