A Go code generator that automatically generates CRUD operations for databases and HTTP client code based on predefined schemas.
defc originates from the tedium of repetitively writing code for "create, read, update, delete" (CRUD) operations
and "network interface integration" in daily development work. By defining schemas, defc automatically generates
boilerplate code for database operations and HTTP requests, including parameter construction, error handling, result
mapping, and logging logic.
The name "defc" is a combination of "def" (define) and "c" (CLI - command line interface), reflecting its nature as a command-line tool for defining and generating code schemas. Currently, defc provides three main code generation scenarios:
- Database CRUD: Code generation based on an enhanced fork of sqlx for database operations
- HTTP Client: Request code generation based on Go's standard
net/httppackage - RPC: Client and server wrappers generation based on Go's standard
net/rpcpackage
- 🚀 Automatic Code Generation: Generate database CRUD, HTTP client, and net/rpc wrappers from interface definitions
- 📊 SQL Query Support: Full support for complex SQL queries with template functionality
- 🌐 HTTP Client Generation: Generate HTTP client code with request/response handling
- 🔧 Template Engine: Built-in template support for dynamic SQL and HTTP requests
- 🎯 Transaction Support: Automatic transaction handling for database operations
- 📝 Logging Integration: Built-in logging support for queries and requests
- 🔄 Pagination Support: Automatic pagination handling for HTTP APIs
- 🎨 Flexible Configuration: Multiple configuration options and feature flags
- 📁 File Inclusion: Support for including external SQL files and scripts
- 🔍 Type Safety: Generate type-safe code with proper error handling
✨ Enhanced Features: Starting from v1.37.0,
sqlx/futureandapi/futurefeatures are enabled by default, providing enhanced capabilities and improved interfaces. Use thelegacybuild tag to disable these features if needed.
go install github.com/x5iu/defc@latestOr use with go run:
go run -mod=mod github.com/x5iu/defc@latest --help- Define your schema interface:
//go:generate go run -mod=mod "github.com/x5iu/defc" --mode=sqlx --output=query.go
type Query interface {
// CreateUser EXEC
// INSERT INTO `user` (`name`, `age`) VALUES (?, ?);
CreateUser(ctx context.Context, user *User) error
// GetUser QUERY
// SELECT * FROM `user` WHERE `id` = ?;
GetUser(ctx context.Context, id int64) (User, error)
// GetUsers QUERY
// SELECT * FROM `user` WHERE `id` IN ({{ bindvars $.ids }});
GetUsers(ctx context.Context, ids []int64) ([]*User, error)
}- Run code generation:
go generate- Use the generated code:
// Option 1: Create with driver name and DSN
query := NewQuery("mysql", "connection_string")
user, err := query.GetUser(context.Background(), 1)
// Option 2: Create from existing *sqlx.DB
db, _ := sqlx.Open("mysql", "connection_string")
query := NewQueryFromCore(db)
user, err := query.GetUser(context.Background(), 1)- Define your API schema:
//go:generate go run -mod=mod "github.com/x5iu/defc" --mode=api --output=service.go
type Service interface {
Options() *Config
ResponseHandler() *Response
// CreateUser POST {{ $.Service.Host }}/user
// Content-Type: application/json
//
// {
// "name": {{ $.name }},
// "age": {{ $.age }}
// }
CreateUser(ctx context.Context, name string, age int) (*User, error)
// GetUsers GET {{ $.Service.Host }}/users?page={{ page }}
GetUsers(ctx context.Context) ([]*User, error)
}- Run code generation:
go generate- Use the generated code:
config := &Config{
Host: "https://api.example.com",
APIKey: "your-api-key",
}
service := NewService(config)
user, err := service.CreateUser(context.Background(), "John", 25)- Define your RPC schema:
//go:generate go run -mod=mod "github.com/x5iu/defc" --mode=rpc --output=arith.gen.go
// Methods must have exactly 1 input and 2 outputs (second is error)
type Arith interface {
Multiply(args chan int) (int, error)
}- Run code generation:
go generate- Use the generated code:
// Client side
cli := rpc.NewClient(conn)
arith := NewArith(cli)
res, err := arith.Multiply(make(chan int))
// Server side (wrap implementation)
srv := rpc.NewServer()
srv.RegisterName("Arith", NewArithServer(&arithImpl{}))# Simplest usage - auto-detect everything
defc generate schema.go
# With custom output file
defc generate --output=query.go schema.go
# With specific features
defc generate --features=sqlx/log,sqlx/rebind schema.goSmart Defaults: The defc generate command provides intelligent defaults:
- Auto-detect mode by analyzing your interface methods
- Auto-generate output filename as
<source-file>.gen.gowhen--outputis not specified - sqlx mode is detected when methods contain
EXEC/QUERYoperations, or whenWithTxmethod is present - api mode is detected when methods contain HTTP method names (GET, POST, etc.), or when
Options()/ResponseHandler()methods are present - rpc mode is detected when interface methods each have exactly 1 input parameter and 2 outputs, with the second being
error
sqlx: Generate database CRUD operations using sqlxapi: Generate HTTP client coderpc: Generate net/rpc client and server wrappers
| Flag | Short | Type | Description |
|---|---|---|---|
--mode |
-m |
string | Generation mode: sqlx, api, or rpc (auto-detected in generate command) |
--output |
-o |
string | Output file name (auto-generated as <source>.gen.go in generate command) |
--features |
-f |
[]string | Enable specific features (see Features section above) |
--import |
[]string | Additional import packages | |
--func / --function |
[]string | Additional template functions (format: name=function) |
|
--disable-auto-import |
bool | Disable automatic import detection | |
--help |
-h |
Show help information | |
--version |
-v |
Show version information |
| Flag | Short | Type | Description |
|---|---|---|---|
--type |
-T |
string | Specify the target interface type when multiple candidates exist |
--template |
-t |
string | Additional template content (sqlx mode only, experimental) |
# Basic usage with explicit mode
defc --mode=sqlx --output=query.go
# Using generate command (auto-detect mode and output)
defc generate schema.go
# With features and custom imports
defc generate --features=sqlx/log,sqlx/rebind --import="fmt" schema.go
# Specify target type when multiple interfaces exist
defc generate --type=UserQuery user_schema.go
# Custom template (experimental, sqlx only)
defc generate --template="SELECT * FROM {{ .table }}" --type=MyQuery schema.goNote: defc uses an enhanced fork of sqlx that provides additional interfaces and optimizations. Since v1.37.0,
sqlx/future is enabled by default. To use legacy behavior, build with the legacy tag: go build -tags=legacy.
sqlx/log: Enable query loggingsqlx/rebind: Automatic parameter placeholder rebinding for different databasessqlx/in: Enhanced IN query supportsqlx/future: Use enhanced sqlx fork (github.com/x5iu/defc/sqlx) with additional interfaces and improvements ( enabled by default since v1.37.0)sqlx/callback: Support for callback methods automatically executed after query completionsqlx/any-callback: Support for callback methods with flexible executor interfacesqlx/nort: Generate code without runtime dependencies
Note: Since v1.37.0, api/future is enabled by default, providing enhanced response handling capabilities. To use
legacy behavior, build with the legacy tag: go build -tags=legacy.
api/log: Enable request loggingapi/logx: Enhanced logging with request/response detailsapi/client: Custom HTTP client supportapi/cache: Response caching functionalityapi/page: Automatic pagination supportapi/error: Enhanced error handling with HTTP status codesapi/future: Use enhanced response handling withFromResponse()method (enabled by default since v1.37.0)api/get-body: Enable access to request body copy viahttp.Request.GetBody()for debugging and loggingapi/nort: Generate code without runtime dependencies
rpc/nort: Generate code without runtime dependency on defc runtime helpers (uses reflection-based zero value helpers in generated code)- Generated client constructor:
New{Interface}(client *rpc.Client) {Interface} - Generated server wrapper:
New{Interface}Server(impl {Interface}) *{Interface}Server - Method signature rules: exactly 1 input parameter and 2 outputs, with the second being
error
type Query interface {
// MethodName OPERATION [ARGUMENTS]
// SQL_STATEMENT
MethodName(ctx context.Context, params...) (result, error)
}Operations:
EXEC: For INSERT, UPDATE, DELETE operationsQUERY: For SELECT operations
Arguments:
NAMED: Use named parameters (:param)MANY: Usesqlx.Selectfor multiple resultsONE: Usesqlx.Getfor single resultCONST: Disable template processing for better performanceCONSTBIND: Use${expr}syntax for compile-time parameter binding without template renderingBIND: Use binding mode for parametersSCAN(expr): Custom scan targetWRAP=func: Wrap the query with a custom functionISOLATION=level: Set transaction isolation levelARGUMENTS=var: Use custom arguments variable
type Service interface {
Options() *Config
ResponseHandler() *Response
// MethodName HTTP_METHOD [ARGUMENTS] URL
// HEADERS
//
// BODY
MethodName(ctx context.Context, params...) (result, error)
}The ResponseHandler() method must return a type that implements the Response interface with the following methods:
Err() error: Checks if the response contains an error (e.g., non-200 status codes)ScanValues(...any) error: Scans response data into target objects (similar tosql.Rows.Scan)FromBytes(method string, data []byte) error: Processes response from raw bytes (traditional approach)FromResponse(method string, resp *http.Response) error: Processes response from HTTP response object (enabled by default since v1.37.0)Break() bool: Controls pagination flow - returntrueto stop pagination,falseto continue
HTTP Methods: GET, POST, PUT, DELETE, PATCH, etc.
Arguments:
MANY: For paginated results (returns slice)ONE: For single result (returns single value)Scan(expr): Custom scan parameters for response processingOptions(expr): Custom request optionsRetry=N: Set maximum retry attempts (default: 2)
The sqlx mode supports including external SQL files and script output using special directives:
Include external SQL files or use glob patterns:
//go:generate go run -mod=mod "github.com/x5iu/defc" --mode=sqlx --output=user_query.go
type UserQuery interface {
// GetUser QUERY ONE
// #INCLUDE "queries/get_user.sql"
GetUser(ctx context.Context, id int64) (*User, error)
// GetActiveUsers QUERY MANY
// #INCLUDE "queries/*.sql" // Include all SQL files
GetActiveUsers(ctx context.Context) ([]*User, error)
}Execute shell commands and include their output:
type UserQuery interface {
// ListUsers QUERY MANY
// #SCRIPT cat "queries/list_users.sql"
ListUsers(ctx context.Context) ([]*User, error)
// GetUserCount QUERY ONE
// #SCRIPT echo "SELECT COUNT(*) as count FROM users"
GetUserCount(ctx context.Context) (int64, error)
}defc automatically validates template syntax and provides detailed error messages:
// Invalid template will show clear error
type UserQuery interface {
// GetUser QUERY ONE
// SELECT * FROM users WHERE id = {{ .invalid_function }};
GetUser(ctx context.Context, id int64) (*User, error)
}Error output:
Error: template: defc(sqlx):1:45: executing "GetUser" at <.invalid_function>:
can't evaluate field invalid_function in type map[string]interface {}
type UserQuery interface {
// GetUsers QUERY MANY
// SELECT * FROM users
// WHERE 1=1
// {{if $.name}} AND name = {{ bind $.name }}{{end}}
// {{if $.age}} AND age >= {{ bind $.age }}{{end}}
GetUsers(ctx context.Context, name string, age int) ([]*User, error)
}type UserQuery interface {
// GetUsersByIDs QUERY MANY
// SELECT * FROM users WHERE id IN ({{ bindvars $.ids }})
GetUsersByIDs(ctx context.Context, ids []int64) ([]*User, error)
// BulkInsertUsers EXEC
// INSERT INTO users (name, email) VALUES
// {{range $i, $user := $.users}}
// {{if $i}},{{end}}({{ bind $user.Name }}, {{ bind $user.Email }})
// {{end}}
BulkInsertUsers(ctx context.Context, users []*User) error
}The --template parameter (sqlx mode only, experimental) allows you to define additional template content that can be
shared across all methods in your interface.
Format 1: Template File Path
defc generate --template path/to/template.tmpl schema.goFormat 2: Direct Template Expression (with : prefix)
# Using string literal (requires quotes)
defc generate --template ':"{{ define \"common\" }}SELECT * FROM {{ .table }}{{ end }}"' schema.go
# Using variable reference (no quotes needed)
defc generate --template ':templateVar' schema.goCreate a template file templates/common.tmpl:
{{ define "audit_header" }}
/* Generated by defc - Method: {{ .method }} */
{{ end }}
{{ define "pagination" }}
LIMIT {{ .limit }} OFFSET {{ .offset }}
{{ end }}
{{ define "where_active" }}
WHERE active = 1 AND deleted_at IS NULL
{{ end }}Use in your schema:
//go:generate defc generate --template templates/common.tmpl --features sqlx/log -o user.gen.go
type UserQuery interface {
// GetUsers QUERY MANY
// {{ template "audit_header" . }}
// SELECT * FROM users
// {{ template "where_active" }}
// ORDER BY created_at DESC
// {{ template "pagination" . }}
GetUsers(ctx context.Context, limit, offset int) ([]*User, error)
// GetActiveUserCount QUERY ONE
// {{ template "audit_header" . }}
// SELECT COUNT(*) FROM users {{ template "where_active" }}
GetActiveUserCount(ctx context.Context) (int64, error)
}//go:generate defc generate --template ':"{{ define \"timestamp\" }}/* Generated at {{ .now }} */{{ end }}"' --function now=time.Now -o user.gen.go
type UserQuery interface {
// GetUser QUERY ONE
// {{ template "timestamp" . }}
// SELECT * FROM users WHERE id = ?;
GetUser(ctx context.Context, id int64) (*User, error)
}When using bind function in templates, the system automatically enables BIND mode for all methods:
//go:generate defc generate --template ':"{{ define \"bulk_insert\" }}INSERT INTO {{ .table }} ({{ .fields }}) VALUES {{ range $i, $item := .items }}{{ if $i }},{{ end }}({{ range $j, $field := .fields }}{{ if $j }},{{ end }}{{ bind (index $item $field) }}{{ end }}){{ end }}{{ end }}"' -o bulk.gen.go
type BulkQuery interface {
// BulkInsertUsers EXEC
// {{ template "bulk_insert" . }}
BulkInsertUsers(ctx context.Context, users []*User) error
}defc generate --template ':"{{ define \"custom_log\" }}/* {{ logLevel . }} */{{ end }}"' --function logLevel=getLogLevel schema.go- sqlx mode only: The
--templateparameter only works in sqlx mode, not in api mode - Experimental feature: This is an experimental parameter that may change in future versions
- Shared scope: Templates are shared across all methods in the interface
- Auto-bind detection: If templates use the
bindfunction, BIND mode is automatically enabled for all methods - Performance consideration: Using
bindin templates adds runtime overhead as templates are parsed on each call
//go:generate go run -mod=mod "github.com/x5iu/defc" --mode=sqlx --output=user_query.go
type UserQuery interface {
// CreateUser EXEC
// INSERT INTO users (name, email, age) VALUES (?, ?, ?);
CreateUser(ctx context.Context, name, email string, age int) error
// GetUserByID QUERY ONE
// SELECT * FROM users WHERE id = ?;
GetUserByID(ctx context.Context, id int64) (*User, error)
// GetUsersByAge QUERY MANY
// SELECT * FROM users WHERE age >= ? ORDER BY name;
GetUsersByAge(ctx context.Context, minAge int) ([]*User, error)
// UpdateUser EXEC
// UPDATE users SET name = ?, email = ? WHERE id = ?;
UpdateUser(ctx context.Context, name, email string, id int64) error
// DeleteUser EXEC
// DELETE FROM users WHERE id = ?;
DeleteUser(ctx context.Context, id int64) error
}//go:generate go run -mod=mod "github.com/x5iu/defc" --mode=sqlx --output=user_query.go
type UserQuery interface {
// FindUsers QUERY NAMED
// SELECT * FROM users WHERE name = :name AND age >= :min_age;
FindUsers(ctx context.Context, name string, minAge int) ([]*User, error)
}The CONSTBIND option provides a simple way to bind Go expressions directly in SQL without template rendering overhead.
Use ${expr} syntax to reference variables or expressions:
//go:generate go run -mod=mod "github.com/x5iu/defc" --mode=sqlx --output=user_query.go
type UserQuery interface {
// GetUserByName QUERY CONSTBIND
// SELECT * FROM users WHERE name = ${name} AND age > ${minAge};
GetUserByName(ctx context.Context, name string, minAge int) (*User, error)
// UpdateUser EXEC CONSTBIND
// UPDATE users SET name = ${user.Name}, age = ${user.Age} WHERE id = ${user.ID};
UpdateUser(ctx context.Context, user *User) error
// GetUsersByStatus QUERY CONSTBIND
// SELECT * FROM users WHERE status = ${status} AND created_at > ${time.Now().Add(-24 * time.Hour)};
GetUsersByStatus(ctx context.Context, status string) ([]*User, error)
}Key differences from other modes:
CONST: SQL is used as-is, parameters must use?placeholders manuallyCONSTBIND: SQL uses${expr}syntax, automatically converted to?placeholders with expressions as argumentsBIND: Uses Go template with{{ bind $.var }}syntax, rendered at runtime
//go:generate go run -mod=mod "github.com/x5iu/defc" --mode=sqlx --output=user_query.go
type UserQuery interface {
WithTx(ctx context.Context, fn func (UserQuery) error) error
// CreateUser EXEC
// INSERT INTO users (name, email) VALUES (?, ?);
CreateUser(ctx context.Context, name, email string) error
// CreateProfile EXEC
// INSERT INTO profiles (user_id, bio) VALUES (?, ?);
CreateProfile(ctx context.Context, userID int64, bio string) error
}
// Usage
err := query.WithTx(ctx, func(q UserQuery) error {
if err := q.CreateUser(ctx, "John", "john@example.com"); err != nil {
return err
}
return q.CreateProfile(ctx, userID, "Software Developer")
})The sqlx/callback and sqlx/any-callback features allow structs to implement callback methods that are automatically
executed after query completion:
//go:generate go run -mod=mod "github.com/x5iu/defc" --mode=sqlx --features=sqlx/callback --output=user_query.go
type UserQuery interface {
// GetUser QUERY
// SELECT id, name FROM users WHERE id = ?;
GetUser(ctx context.Context, id int64) (*User, error)
// GetUsers QUERY
// SELECT id, name FROM users WHERE active = 1;
GetUsers(ctx context.Context) ([]*User, error)
}
type User struct {
ID int64 `db:"id"`
Name string `db:"name"`
Projects []*Project // Will be populated by callback
}
// Callback method for sqlx/callback feature
func (u *User) Callback(ctx context.Context, query UserQuery) error {
// Automatically called after User is populated from query
projects, err := query.GetProjectsByUserID(ctx, u.ID)
if err != nil {
return err
}
u.Projects = projects
return nil
}
// For sqlx/any-callback feature (more flexible)
func (u *User) Callback(ctx context.Context, executor any) error {
// executor can be any type, providing more flexibility
if q, ok := executor.(UserQuery); ok {
return u.loadProjects(ctx, q)
}
return nil
}Callback Features:
sqlx/callback: ExpectsCallback(context.Context, SpecificInterface) errorsqlx/any-callback: ExpectsCallback(context.Context, any) errorfor more flexibility
The sqlx/log feature allows you to log SQL queries and their execution details:
//go:generate go run -mod=mod "github.com/x5iu/defc" --mode=sqlx --features=sqlx/log --output=user_query.go
type UserQuery interface {
// GetUser QUERY
// SELECT id, name FROM users WHERE id = ?;
GetUser(ctx context.Context, id int64) (*User, error)
}
// Your database connection must implement the Log interface
type LoggingDB struct {
*sqlx.DB
}
func (db *LoggingDB) Log(
ctx context.Context,
caller string, // Method name (e.g., "GetUser")
query string, // SQL query
args any, // Query arguments
elapse time.Duration, // Execution time
) {
argsJSON, _ := json.Marshal(args)
log.Printf("=== %s\nquery: %s\nargs: %s\nelapse: %s\n",
caller, query, string(argsJSON), elapse)
}
// Usage
db := &LoggingDB{DB: sqlx.MustOpen("postgres", dsn)}
query := NewUserQueryFromCore(db)The WithTx method supports setting transaction isolation levels using the ISOLATION argument:
//go:generate go run -mod=mod "github.com/x5iu/defc" --mode=sqlx --output=user_query.go
type UserQuery interface {
// WithTx ISOLATION=sql.LevelSerializable
WithTx(ctx context.Context, fn func (UserQuery) error) error
// CreateUser EXEC
// INSERT INTO users (name, email) VALUES (?, ?);
CreateUser(ctx context.Context, name, email string) error
}
// Usage with serializable isolation level
err := query.WithTx(ctx, func(q UserQuery) error {
return q.CreateUser(ctx, "John", "john@example.com")
})type Config struct {
Host string
APIKey string
}
type Response struct {
Data any `json:"data"`
Error string `json:"error"`
Status int `json:"status"`
}
func (r *Response) Err() error {
// Check for API-level errors
if r.Error != "" {
return fmt.Errorf("API error: %s", r.Error)
}
// Check for HTTP status errors
if r.Status >= 400 {
return fmt.Errorf("HTTP error: status %d", r.Status)
}
return nil
}
func (r *Response) ScanValues(dest ...any) error {
// Scan response data into provided destinations
for _, d := range dest {
if err := json.Unmarshal([]byte(r.Data.(string)), d); err != nil {
return fmt.Errorf("failed to scan response data: %w", err)
}
}
return nil
}
func (r *Response) FromBytes(method string, data []byte) error {
// Process response from raw bytes
return json.Unmarshal(data, r)
}
func (r *Response) FromResponse(method string, resp *http.Response) error {
// Process response from HTTP response object (default since v1.37.0)
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response body: %w", err)
}
return json.Unmarshal(data, r)
}
func (r *Response) Break() bool {
// Control pagination: return true to stop, false to continue
// Example: stop if no more data or reached limit
return r.Data == nil || len(r.Data.([]any)) == 0
}
//go:generate go run -mod=mod "github.com/x5iu/defc" --mode=api --output=user_service.go
type UserService interface {
Options() *Config
ResponseHandler() *Response
// CreateUser POST {{ $.Service.Host }}/api/users
// Content-Type: application/json
// Authorization: Bearer {{ $.Service.APIKey }}
//
// {
// "name": {{ $.name }},
// "email": {{ $.email }}
// }
CreateUser(ctx context.Context, name, email string) (*User, error)
// GetUser GET {{ $.Service.Host }}/api/users/{{ $.id }}
// Authorization: Bearer {{ $.Service.APIKey }}
GetUser(ctx context.Context, id int64) (*User, error)
// ListUsers GET MANY {{ $.Service.Host }}/api/users?page={{ page }}
// Authorization: Bearer {{ $.Service.APIKey }}
ListUsers(ctx context.Context) ([]*User, error)
}//go:generate go run -mod=mod "github.com/x5iu/defc" --mode=api --features=api/client --output=service.go
type Service interface {
Options() *Config
ResponseHandler() *Response
// GetData GET {{ $.Service.Host }}/data
GetData(ctx context.Context) (*Data, error)
}
type Config struct {
Host string
client *http.Client
}
func (c *Config) Client() *http.Client {
return c.client
}
// Usage
config := &Config{
Host: "https://api.example.com",
client: &http.Client{Timeout: 30 * time.Second},
}
service := NewService(config)The api/log and api/logx features provide HTTP request logging capabilities:
//go:generate go run -mod=mod "github.com/x5iu/defc" --mode=api --features=api/log --output=user_service.go
type UserService interface {
Options() *Config
ResponseHandler() *Response
// GetUser GET {{ $.Service.Host }}/users/{{ $.id }}
GetUser(ctx context.Context, id int64) (*User, error)
}
// Basic logging with api/log
type Config struct {
Host string
}
func (c *Config) Log(
ctx context.Context,
caller string, // Method name (e.g., "GetUser")
method string, // HTTP method (e.g., "GET")
url string, // Request URL
elapse time.Duration, // Request duration
) {
log.Printf("=== %s %s %s\nelapse: %s\n", caller, method, url, elapse)
}
// Enhanced logging with api/logx
func (c *Config) Log(
ctx context.Context,
caller string, // Method name
request *http.Request, // Full HTTP request
response *http.Response, // Full HTTP response
elapse time.Duration, // Request duration
) {
log.Printf("=== %s %s %s\nStatus: %d\nElapse: %s\n",
caller, request.Method, request.URL.String(),
response.StatusCode, elapse)
}
// Usage
config := &Config{Host: "https://api.example.com"}
service := NewUserService(config)This project is licensed under the MIT License - see the LICENSE file for details.