A database model code generator for Go. Generate ORM models from database schema using bun framework.
- Supports MySQL and PostgreSQL databases
- Automatic Go type mapping for database columns
- Automatic foreign key detection and relation generation (belongs-to, has-many)
- Composite unique index detection (
unique,unique:index_nametags) - Customizable templates
- Custom struct generation — define non-DB structs for JSON columns, value objects, configuration objects
- Custom field names, types, comments, tags, and multi-line comments
- Naming conventions (snake_case, camelCase, consecutive acronyms)
- Timezone support for created_at, updated_at, deleted_at hooks
- Environment-based configuration (e.g., bake.gen.dev.yaml for env=dev)
- Concurrent table processing
- Auto-detect database driver from DSN scheme
For each table, bake generates two files:
<table>.gen.go— base constants, struct, time hooks<table>.alias.gen.go— alias-prefixed constants for joined queries
| Category | Expressions | Description |
|---|---|---|
| Column | Eq, Neq | Equality comparisons |
| Ordered | Gt, Gte, Lt, Lte | Ordering comparisons |
| String | Like, LikePrefix, LikeSuffix, LikeContain, NotLike, ConcatExpr | Pattern matching |
| String | LengthExpr, LowerExpr, UpperExpr | String functions |
| Numeric | In, NotIn, Between, NotBetween | Range operations |
| Aggregate | SUMExpr, AVGExpr, MINExpr, MAXExpr | Aggregate functions |
| Arithmetic | AddExpr, SubExpr, MulExpr, DivExpr | Arithmetic assignment |
| Arithmetic | AddLeastExpr, SubGreatestExpr, ClampExpr | Bounded arithmetic |
| Distinct | DistinctExpr, CountDistinctExpr | Deduplication |
| Nullable | IsNull, IsNotNull, CoalesceExpr | NULL handling |
| Time | DateExpr, YearExpr, MonthExpr, DayExpr, HourExpr, MinuteExpr, SecondExpr | Time extraction |
| Ordering | Asc, Desc | Sort order |
| Join | InnerJoin, LeftJoin, RightJoin, FullJoin (alias file, FullJoin skipped on MySQL) | JOIN helpers |
go install github.com/sishui/bake/cmd/bake@latest- Initialize a configuration file:
bake initEdit
bake.gen.yamlwith your database connection detailsGenerate models:
bake| Command | Description |
|---|---|
bake init | Initialize a configuration file in current directory |
bake version | Show current version |
bake | Generate models based on configuration |
log:
file: ""# log file path (optional; level defaults to "info")uncountables: ["sms", "mms", "rls"] # words that should not be pluralizedinitialisms: ["ID", "URL", "URI", "UUID", "IP"] # naming conventionstimezone: "Asia/Shanghai"# timezone for time hookstemplate:
dir: ""# custom template directoryoutput:
dir: "model"# output directorypackage: "model"# package namemodule: "github.com/username/project"# module path for importscustom: [] # custom struct definitions (not tied to db tables)db:
- driver: "postgres"dsn: "postgres://user:pass@localhost:5432/db?sslmode=disable"schema: "public"# required for postgres, omitted for mysqlinclude: [] # tables to include (default: all)exclude: [] # tables to exclude (default: none)custom: {} # per-table custom field/tag overridesWhen .env contains env=dev, bake looks for bake.gen.dev.yaml first, then falls back to bake.gen.yaml.
db:
- driver: "postgres"# ...custom:
users:
comment: "User table"# custom table commenttags:
- key: "form"name: "$SnakeCase"# convert to snake_case
- key: "xml"name: "$CamelCase"# convert to camelCasefields:
created_at: # database column namename: "CreatedAt"# custom field nametags:
- key: "json"name: "created_at"# custom json tag nameposts:
fields:
author_id:
name: "Author"# custom field nametype: "*User"# custom field typerelation: true # is a relationtags:
- key: "bun"options: ["rel:belongs", "join:author_id=id"]| Value | Description |
|---|---|
$SnakeCase | Convert to snake_case |
$CamelCase | Convert to camelCase |
#field_name | Use literal value |
You can use custom templates. The following data is passed to templates:
typeModelstruct {
Versionstring// bake versionModulestring// module pathPackagestring// package nameImports [][]string// importsBunModelstring// bun.BaseModelDriverstring// database driver: mysql, postgresTablestring// table nameModelstring// model nameAliasstring// model aliasComments []string// model commentsFields []*Field// fieldsTimezonestring// timezoneCreatedAtTypestring// created_at typeUpdatedAtTypestring// updated_at typeDeletedAtTypestring// deleted_at typeMaxFieldLengthint// max field lengthMaxNullableLengthint// max nullable lengthMaxStringLengthint// max string lengthMaxNumericLengthint// max numeric lengthMaxOrderedLengthint// max ordered lengthMaxOrderedNonStringLengthint// max ordered non-string length (numeric + time)MaxEquatableLengthint// max equatable lengthMaxRelationLengthint// max relation lengthMaxArithmeticLengthint// max arithmetic length (non-pk numeric)MaxTimeLengthint// max time length
}
typeFieldstruct {
Imports []string// field importsNamestring// field nameAlignedNamestring// aligned field nameTypestring// Go typeAlignedTypestring// aligned typeTagstring// field tagsAlignedTagstring// aligned tagComments []string// field commentsColumnNamestring// database column nameKindstring// field kind: NUMERIC, STRING, TIME, etc.IsPrimarybool// is primary keyIsNullablebool// is nullableIsCustombool// is custom fieldIsRelationbool// is relation field
}When using the custom template (default: custom.tmpl), the following data is passed:
typeCustomStructstruct {
Versionstring// bake versionPackagestring// package nameModulestring// module pathImports [][]string// grouped importsNamestring// struct name (PascalCase)Comment []string// struct-level comment linesFields []*StructField// fields in this struct
}
typeStructFieldstruct {
Namestring// Go field name (PascalCase)AlignedNamestring// Name padded to max widthGoTypestring// Go typeAlignedTypestring// GoType padded to max widthTagstring// Struct tag (including backticks)AlignedTagstring// Tag padded to max widthComment []string// Multi-line comment
}
Generated features:
-**Fieldalignment** — Names, types, andtagsarealignedwithpadding-**Multi-linecomments** — Renderedbeforethefield, tagalignmentisskippedforfieldswithmulti-linecomments-**Commentgroups** — Multi-linecommentfieldsseparatestructfieldsintoalignmentgroups-**Scan/Value** — Eachstructgets`Scan(src any)`and`Value() (driver.Value, error)`methodsfor`database/sql`compatibility
## TypeMappings
### MySQL|MySQLType|GoType||-------------------|---------------||tinyint(1) |bool||tinyint|int8/uint8||smallint|int16/uint16||int|int32/uint32||bigint|int64/uint64||float|float32||double|float64||decimal|decimal.Decimal||varchar, text|string||blob| []byte||datetime, timestamp|time.Time||json|json.RawMessage||enum, set|string||geometry, point, linestring, polygon, etc. | []byte (WKB) |
### PostgreSQL|PostgreSQLType|GoType||---------------------|---------------------------------------||int2|int16||int4|int32||int8|int64||float4|float32||float8|float64||numeric, decimal|decimal.Decimal||bool|bool||text, varchar|string||bytea| []byte||timestamp, date, time|time.Time||json, jsonb|json.RawMessage||uuid|uuid.UUID||inet, cidr|net.IP||interval|time.Duration||ARRAY| []string, []int32, []int64, []uuid.UUID||geometry, geography, point, etc. (PostGIS) | []byte (WKB) |
## GeneratedBunTagsbakeautomaticallygeneratesbunstructtagsforeachcolumnbasedonthedatabase schema.
### IndexTags|Scenario|GeneratedTag|Description||----------|--------------|-------------||Primarykey|`pk,autoincrement`|Primarykeycolumn||Single-columnuniqueindex|`unique`|Columnhasauniqueconstraint||Compositeuniqueindex|`unique:index_name`|Columnispartofamulti-columnuniqueindex|Exampleforacompositeuniqueindexon`(start_at, end_at)`:
```goStartAt time.Time `bun:"start_at,unique:idx_unique_range,notnull"`EndAt time.Time `bun:"end_at,unique:idx_unique_range,notnull"`| Property | Tag | Condition |
|---|---|---|
notnull | Non-nullable column | IS_NOT_NULL or NOT NULL |
nullzero | Nullable column | IS_NULL or nullable |
default:value | Has default value | Column has a default |
soft_delete | Soft delete column | Column name is deleted_at |
type:decimal(M,N) | Decimal type | MySQL/PostgreSQL decimal columns |
bake automatically detects foreign key relationships from your database schema and generates the appropriate bun relation tags.
When a column has a foreign key constraint:
-- posts.user_id references users.idALTERTABLE posts ADD CONSTRAINT fk_posts_user_id
FOREIGN KEY (user_id) REFERENCES users(id);bake will automatically:
- On
poststable: GenerateUser *Userfield withrel:belongs-to - On
userstable: GeneratePosts []*Postfield withrel:has-many
For the posts table with user_id foreign key:
// Post structtypePoststruct {
bun.BaseModel`bun:"table:posts"`IDint64`bun:"id,pk,autoincrement"`UserIDint64`bun:"user_id,notnull"`Titlestring`bun:"title,notnull"`User*User`bun:"user,rel:belongs-to,join:user_id=id"`
}
// User struct (auto-generated reverse relation)typeUserstruct {
bun.BaseModel`bun:"table:users"`IDint64`bun:"id,pk,autoincrement"`Namestring`bun:"name,notnull"`Posts []*Post`bun:"posts,rel:has-many,join:id=user_id"`
}You can still manually configure relations using the custom configuration. Manual configuration takes precedence over automatic detection.
Custom structs allow you to define Go structs that are not tied to database tables. They are generated with Scan and Value methods for database/sql compatibility, making them ideal for JSON/JSONB column types.
Define custom structs in your bake.gen.yaml:
output:
dir: "model"package: "model"module: "github.com/user/project"custom:
- name: "Config"comment: "Application configuration stored as JSONB"fields:
- name: "Theme"type: "string"comment: "UI theme (light/dark)"
- name: "Notifications"type: "bool"comment: "Enable push notifications"
- name: "Description"type: "string"comment: "A detailed description\nwith multiple lines"Running bake generates one file per custom struct (e.g., model/config.gen.go):
// Code generated by bake. DO NOT EDIT.// version: v0.4.0package model
import (
"database/sql/driver""encoding/json""fmt"
)
// Application configuration stored as JSONBtypeConfigstruct {
// A detailed description// with multiple linesDescriptionstring`json:"description,omitempty"`Notificationsbool`json:"notifications,omitempty"`// Enable push notificationsThemestring`json:"theme,omitempty"`// UI theme (light/dark)
}
func (o*Config) Scan(srcany) error {
ifsrc==nil {
returnnil
}
switchv:=src.(type) {
case []byte:
returnjson.Unmarshal(v, o)
casestring:
returnjson.Unmarshal([]byte(v), o)
default:
returnfmt.Errorf("unsupported scan type %T for Config", src)
}
}
func (oConfig) Value() (driver.Value, error) {
returnjson.Marshal(o)
}Fields within the same comment group are aligned for readability:
| Group condition | Alignment behavior |
|---|---|
| Consecutive single-line comments | Names/types/tags are aligned to the widest value in the group |
| Multi-line comment field | The field's tag is not padded; creates a new alignment group |
| No comment | Rendered with // suffix and aligned normally |
See examples/mysql/ or examples/postgres/ for complete working examples with custom structs integrated alongside database models.
go test ./...go build ./cmd/bakeApache License 2.0 - see LICENSE file