Core Concepts
Registration

Registration

Registration is the foundation of lit's performance. By registering models at startup, all reflection and query generation happens once, not on every database operation.

Why Registration Exists

Traditional ORMs use reflection on every query to map struct fields to database columns. This adds overhead to every operation. lit takes a different approach:

  1. Register once at application startup
  2. Use cached data for all subsequent operations
  3. Minimal reflection overhead during queries

Basic Registration

import "github.com/tracewayapp/lit/v2"
 
type User struct {
    Id        int
    FirstName string
    LastName  string
    Email     string
}
 
func init() {
    lit.RegisterModel[User](lit.PostgreSQL)
}

Driver Registration

You can register default driver globally. This driver will be used for all models registered without specifying a driver.

lit.RegisterDriver(lit.PostgreSQL) // or lit.MySQL, lit.SQLite, lit.DuckDB
lit.RegisterModel[User]() // uses PostgreSQL driver

If for any reason you need to register a model with a different driver than the default, you can still specify it explicitly:

lit.RegisterDriver(lit.PostgreSQL)
lit.RegisterModel[User](lit.MySQL) // uses MySQL driver

You can also pass a custom driver to support databases beyond the built-in four.

What Gets Cached

When you call RegisterModel, lit creates a FieldMap containing:

FieldDescription
ColumnsMapMaps column names to field positions
ColumnKeysOrdered list of column names
HasIntIdWhether id field is an integer (for auto-increment)
InsertQueryPre-built INSERT statement
UpdateQueryPre-built UPDATE statement (without WHERE clause)
InsertColumnsColumns used in INSERT (excludes auto-increment id)
DriverDatabase driver (PostgreSQL, MySQL, SQLite, DuckDB, or custom)

Default Naming Convention

lit converts Go's CamelCase to SQL's snake_case:

Go FieldSQL Column
Idid
FirstNamefirst_name
LastNamelast_name
CreatedAtcreated_at
UserIDuser_i_d

Table names are derived from struct names with an s suffix:

  • User → users
  • Product → products

ID Detection

lit automatically detects if your id field is an integer:

// Integer ID - uses DEFAULT in INSERT, RETURNING id (PostgreSQL) or LastInsertId (MySQL)
type User struct {
    Id   int    // HasIntId = true
    Name string
}
 
// String/UUID ID - includes id in INSERT values
type Session struct {
    Id   string  // HasIntId = false
    Data string
}

Custom Naming Strategy

For different naming conventions, use RegisterModelWithNaming:

type MyNamingStrategy struct{}
 
func (MyNamingStrategy) GetTableNameFromStructName(name string) string {
    return "tbl_" + strings.ToLower(name)
}
 
func (MyNamingStrategy) GetColumnNameFromStructName(name string) string {
    return strings.ToLower(name)
}
 
func init() {
    lit.RegisterModelWithNaming[User](lit.PostgreSQL, MyNamingStrategy{})
}

See Naming Strategies for more details.

Generated Queries Example

For a registered User struct with PostgreSQL:

INSERT Query:

INSERT INTO users (id,first_name,last_name,email) VALUES (DEFAULT,$1,$2,$3) RETURNING id

UPDATE Query (base):

UPDATE users SET id = $1,first_name = $2,last_name = $3,email = $4 WHERE

The WHERE clause is appended at runtime when you call Update().