Skip to main content

Generate GORM Models (Go Code) from a Database Schema

Although Atlas supports reading GORM schemas (gorm.Model) as the desired state and planning migrations based on it, it can sometimes be useful to go the other way - generating GORM models from an existing schema, such as a live database, HCL or SQL schema files, or even another ORM.

Overview

One way to generate custom code from an Atlas schema is to get a JSON representation of it using the atlas schema inspect command with the --format '{{ json . }}' flag, and then use a custom script to generate the desired code.

However, in this guide, we'll show how you can use Atlas templates to generate custom code from an Atlas schema. Atlas, like many other CLI tools such as kubectl and docker, supports templating the output of commands using Go templates. This lets you write "custom code" that is evaluated at runtime and generates the desired output.

If you're not familiar with the Go templates language, you can read more about it in the Go documentation.

The template execution context is the result of the schema inspection, which is an object with two fields: URL and Realm. The URL field holds the inspected URL, and the Realm field contains the schema information, such as schemas, tables, columns, and more. You can see this object here, and the definition of the Realm object at this link.

Defining a Template

Let's define a simple template that generates GORM model structs from the schema.Realm object. For this example, we assume the database contains a single schema, and all models will be generated into a single file named models.go.

Note that besides the main template, this example defines several small helper templates that act like functions ("title", "singular", etc.). These can be reused across the template using exec and include functions.

models.tmpl
{{- /* A helper function-like template for generating a title-case from a database-object name. */}}
{{- define "title" }}
{{ $v := "" }}
{{- range $w := splitBy $ "_" }}
{{- if le (len $w) 1 }}
{{ $v = print $v (upper $w) }}
{{- else }}
{{ $v = print $v (upper (slice $w 0 1)) (lower (slice $w 1)) }}
{{- end }}
{{- end }}
{{ print $v }}
{{- end }}

{{- /* A helper function-like template for converting a plural noun to its singular form using basic English rules. */}}
{{- define "singular" -}}
{{- $s := . }}
{{- if and (hasSuffix $s "ies") (gt (len $s) 3) -}}
{{- printf "%sy" (slice $s 0 (sub (len $s) 3)) }}
{{- else if hasSuffix $s "ses" -}}
{{- trimSuffix $s "es" }}
{{- else if hasSuffix $s "xes" -}}
{{- trimSuffix $s "es" }}
{{- else if and (hasSuffix $s "s") (gt (len $s) 1) -}}
{{- trimSuffix $s "s" }}
{{- else -}}
{{- $s }}
{{- end }}
{{- end }}

{{- /* A helper function-like template for determining the Go type of a column based on its type. */}}
{{- define "gotype" }}
{{- $m := dict
"character" "string"
"character varying" "string"
"text" "string"
"boolean" "bool"
"smallint" "int"
"integer" "int"
"bigint" "int64"
}}
{{- with $t := columnType . }}
{{- if hasKey $m $t }}{{ get $m $t }}{{ else }}any{{ end }}
{{- end }}
{{- end }}

{{- /* A helper function-like template for generating a struct tag for a column. */}}
{{- define "struct-tag" }}
{{- $tags := list (printf "column:%s" $.Name) }}
{{- if $.Type.Null }}
{{ $tags = append $tags "not null" }}
{{- end }}
{{- printf "`gorm:\"%s\"`" (join $tags ";") }}
{{- end }}

{{- /* The template for generating a Go struct for the given table (defined in $) */}}
{{- define "model" }}
{{- $name := exec "title" $.Name | exec "singular" }}
// The {{ $name }} type holds the definition of the {{ $.Name }} table.
type {{ $name }} struct {
{{- println "\n\tgorm.Model" }}
{{- range $c := $.Columns }}
{{- /* Ignore fields defined in gorm.Model */}}
{{- if not (eq $c.Name "id" "created_at" "updated_at" "deleted_at") }}
{{- printf "\t%s %s %s\n" (exec "title" $c.Name) (exec "gotype" $c) (exec "struct-tag" $c) }}
{{- end }}
{{- end -}}
}
{{- end }}
{{- /* Main template for generating all models in the schema */}}
{{- assert (eq (len .Realm.Schemas) 1) "only one schema is supported in this example" -}}

package models

import "gorm.io/gorm"

{{ range $t := (index .Realm.Schemas 0).Tables }}
{{- /* Include the result of the "model" template for each table */}}
{{- include "model" $t }}
{{- println }}
{{- end }}

After defining the template, we can configure the atlas.hcl file to run the template whenever we execute schema inspection with the generate environment.

atlas.hcl
env "generate" {
src = "file://schema.sql"
dev = "docker://postgres/16/dev?search_path=public"
format {
schema {
inspect = file("models.tmpl")
}
}
}

Executing the Template

After we defined the template and configured it in our atlas.hcl, we can execute the template using the atlas schema inspect --env generate --url env://src command. The --env flag selects the environment, and the --url flag specifies the schema source. In this case, env://src references the src attribute defined in the selected environment.

atlas schema inspect \
--env generate \
--url env://src

The output of this command will look like something like this:

package models

import "gorm.io/gorm"


// The User type holds the definition of the users table.
type User struct {
gorm.Model
Name string `gorm:"column:name;not null"`
// ...
}

To write the output to a file, redirect stdout to models.go and run goimports to format it:

atlas schema inspect \
--env generate \
--url env://src > models.go && goimports -w models.go

write and txtar Functions

The write function lets us write files directly from within the template. This is useful when we want to generate multiple files (e.g., one per model) and then run goimports on the entire directory.

To do this, we'll adapt the template to produce txtar output and use the write function to write the generated files to disk. The updated template will look like this:

models.tmpl
{{- /* The template for generating a Go struct for the given table (defined in $) */}}
{{- define "model" }}
{{- $name := exec "title" $.Name | exec "singular" }}
-- {{ lower $name }}.go --
package models

import "gorm.io/gorm"

// The {{ $name }} type holds the definition of the {{ $.Name }} table.
type {{ $name }} struct {
{{- println "\n\tgorm.Model" }}
{{- range $c := $.Columns }}
{{- /* Ignore fields defined in gorm.Model */}}
{{- if not (eq $c.Name "id" "created_at" "updated_at" "deleted_at") }}
{{- printf "\t%s %s %s\n" (exec "title" $c.Name) (exec "gotype" $c) (exec "struct-tag" $c) }}
{{- end }}
{{- end -}}
}
{{- end }}
{{- /* Main template for generating all models in the schema */}}
{{- assert (eq (len .Realm.Schemas) 1) "only one schema is supported in this example" -}}

{{- $txtar := list }}
{{- range $t := (index .Realm.Schemas 0).Tables }}
{{- /* Append the result of the "model" template for each table */}}
{{- $txtar = append $txtar (include "model" $t) }}
{{- end }}
{{- /* Join the result of all table templates, convert to txtar, and then write all at once to the file-system. */}}
{{- join $txtar "\n\n" | txtar | write "models" }}

Then, run the same command as before to execute the template:

atlas schema inspect \
--env generate \
--url env://src && goimports -w models

This will generate a models directory containing one file per model, all formatted and ready to use.

models
├── friendship.go
├── message.go
├── post.go
└── user.go

Conclusion

This guide showed how to generate GORM model structs from a database schema using the Atlas --format flag and Go's text/template engine. For more, see the Go templates documentation and the Atlas template functions reference.