A powerful, lightweight code generator that creates type-safe Data Access Objects (DAOs) for Go applications with support for PostgreSQL, MySQL, SQL Server, Oracle, and SQLite.
- Lightning Fast: Generates comprehensive DAOs in seconds
- Type-Safe: Leverages Go's type system for compile-time safety
- Multi-Database: Supports PostgreSQL, MySQL, SQL Server, Oracle, and SQLite with native query optimization
- Zero Dependencies: Generated code uses only standard library packages
- Smart Tagging: Automatic field mapping using struct tags
- Transaction Support: Built-in transaction management
- Rich Operations: CRUD, bulk operations, pagination, counting, and sorting
- Interface Generation: Generate DAO interfaces for better abstraction and testing
- Clean Code: Generates readable, maintainable Go code
- CLI Ready: Simple command-line interface
Install via go install:
go install github.com/Jibaru/gormless@latest# Generate PostgreSQL DAOs
gormless --input ./models --output ./dao --driver postgres
# Generate MySQL DAOs
gormless --input ./models/user.go --output ./dao --driver mysql
# Generate SQL Server DAOs
gormless --input ./models --output ./dao --driver sqlserver
# Generate Oracle DAOs
gormless --input ./models --output ./dao --driver oracle
# Generate SQLite DAOs
gormless --input ./models --output ./dao --driver sqlite
# Generate DAO interfaces (database-agnostic)
gormless --input ./models --output ./dao --interfaceDefine your models with struct tags to specify database mapping:
package models
import "time"
type User struct {
ID string `sql:"id,primary"`
Name string `sql:"name"`
Email string `sql:"email"`
Age *int `sql:"age"`
CreatedAt time.Time `sql:"created_at"`
UpdatedAt time.Time `sql:"updated_at"`
}
// Optional: Custom table name
func (u *User) TableName() string {
return "users"
}Gormless generates a comprehensive DAO with the following methods:
type UserDAO struct {
db *sql.DB
}
// CRUD Operations
func (dao *UserDAO) Create(ctx context.Context, user *User) error
func (dao *UserDAO) Update(ctx context.Context, user *User) error
func (dao *UserDAO) FindByPk(ctx context.Context, pk string) (*User, error)
func (dao *UserDAO) DeleteByPk(ctx context.Context, pk string) error
// Bulk Operations
func (dao *UserDAO) CreateMany(ctx context.Context, users []*User) error
func (dao *UserDAO) UpdateMany(ctx context.Context, users []*User) error
func (dao *UserDAO) DeleteManyByPks(ctx context.Context, pks []string) error
// Query Operations
func (dao *UserDAO) FindOne(ctx context.Context, where string, sort string, args ...interface{}) (*User, error)
func (dao *UserDAO) FindAll(ctx context.Context, where string, sort string, args ...interface{}) ([]*User, error)
func (dao *UserDAO) FindPaginated(ctx context.Context, limit, offset int, where string, sort string, args ...interface{}) ([]*User, error)
func (dao *UserDAO) Count(ctx context.Context, where string, args ...interface{}) (int64, error)
// Advanced Operations
func (dao *UserDAO) PartialUpdate(ctx context.Context, pk string, fields map[string]interface{}) error
func (dao *UserDAO) WithTransaction(ctx context.Context, fn func(ctx context.Context) error) errorGenerate database-agnostic DAO interfaces for better abstraction, dependency injection, and testing:
# Generate interfaces for all models in a directory
gormless --input ./models --output ./dao --interface
# Generate interface for a specific model
gormless --input ./models/user.go --output ./dao --interfaceThis generates clean interfaces like:
package dao
import (
"context"
"your-project/models"
)
type User = models.User
type UserDAO interface {
// CRUD Operations
Create(ctx context.Context, m *User) error
Update(ctx context.Context, m *User) error
FindByPk(ctx context.Context, pk string) (*User, error)
DeleteByPk(ctx context.Context, pk string) error
// Bulk Operations
CreateMany(ctx context.Context, models []*User) error
UpdateMany(ctx context.Context, models []*User) error
DeleteManyByPks(ctx context.Context, pks []string) error
// Query Operations
FindOne(ctx context.Context, where string, sort string, args ...interface{}) (*User, error)
FindAll(ctx context.Context, where string, sort string, args ...interface{}) ([]*User, error)
FindPaginated(ctx context.Context, limit, offset int, where string, sort string, args ...interface{}) ([]*User, error)
Count(ctx context.Context, where string, args ...interface{}) (int64, error)
// Advanced Operations
PartialUpdate(ctx context.Context, pk string, fields map[string]interface{}) error
WithTransaction(ctx context.Context, fn func(ctx context.Context) error) error
}Benefits of Interface Generation:
- Database Agnostic: Switch between different database implementations seamlessly
- Better Testing: Easily mock DAO interfaces for unit testing
- Dependency Injection: Use interfaces for cleaner architecture
- Team Development: Define contracts before implementation
package main
import (
"context"
"database/sql"
"log"
"your-project/dao/postgres"
"your-project/models"
_ "github.com/lib/pq"
)
func main() {
db, err := sql.Open("postgres", "postgresql://user:password@localhost/dbname")
if err != nil {
log.Fatal(err)
}
defer db.Close()
userDAO := postgres.NewUserDAO(db)
// Create a user
user := &models.User{
ID: "user-123",
Name: "John Doe",
Email: "john@example.com",
}
err = userDAO.Create(context.Background(), user)
if err != nil {
log.Fatal(err)
}
// Find user by primary key
foundUser, err := userDAO.FindByPk(context.Background(), "user-123")
if err != nil {
log.Fatal(err)
}
// Use transactions
err = userDAO.WithTransaction(context.Background(), func(ctx context.Context) error {
return userDAO.Update(ctx, foundUser)
})
}package main
import (
"context"
"database/sql"
"log"
"your-project/dao/sqlserver"
"your-project/models"
_ "github.com/denisenkom/go-mssqldb"
)
func main() {
db, err := sql.Open("sqlserver", "sqlserver://user:password@localhost:1433?database=dbname")
if err != nil {
log.Fatal(err)
}
defer db.Close()
userDAO := sqlserver.NewUserDAO(db)
// Same API as other drivers
user := &models.User{
ID: "user-123",
Name: "John Doe",
Email: "john@example.com",
}
err = userDAO.Create(context.Background(), user)
if err != nil {
log.Fatal(err)
}
}package main
import (
"context"
"database/sql"
"log"
"your-project/dao/oracle"
"your-project/models"
_ "github.com/godror/godror"
)
func main() {
db, err := sql.Open("godror", "user/password@localhost:1521/XE")
if err != nil {
log.Fatal(err)
}
defer db.Close()
userDAO := oracle.NewUserDAO(db)
// Same API as other drivers
user := &models.User{
ID: "user-123",
Name: "John Doe",
Email: "john@example.com",
}
err = userDAO.Create(context.Background(), user)
if err != nil {
log.Fatal(err)
}
}package main
import (
"context"
"database/sql"
"log"
"your-project/dao/sqlite"
"your-project/models"
_ "github.com/mattn/go-sqlite3"
)
func main() {
db, err := sql.Open("sqlite3", "./database.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
userDAO := sqlite.NewUserDAO(db)
// Same API as other drivers
user := &models.User{
ID: "user-123",
Name: "John Doe",
Email: "john@example.com",
}
err = userDAO.Create(context.Background(), user)
if err != nil {
log.Fatal(err)
}
}Generate and use DAO interfaces for better architecture:
package main
import (
"context"
"database/sql"
"log"
"your-project/dao" // Generated interfaces
"your-project/dao/postgres" // Concrete implementation
"your-project/models"
_ "github.com/lib/pq"
)
// Service uses the interface for database independence
type UserService struct {
userDAO dao.UserDAO
}
func NewUserService(userDAO dao.UserDAO) *UserService {
return &UserService{userDAO: userDAO}
}
func (s *UserService) CreateUser(ctx context.Context, name, email string) (*models.User, error) {
user := &models.User{
ID: generateID(),
Name: name,
Email: email,
}
if err := s.userDAO.Create(ctx, user); err != nil {
return nil, err
}
return user, nil
}
func main() {
// Setup database connection
db, err := sql.Open("postgres", "postgresql://user:password@localhost/dbname")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Use concrete implementation
userDAO := postgres.NewUserDAO(db)
// Pass to service via interface
userService := NewUserService(userDAO)
user, err := userService.CreateUser(context.Background(), "John Doe", "john@example.com")
if err != nil {
log.Fatal(err)
}
log.Printf("Created user: %+v", user)
}
func generateID() string {
// Your ID generation logic
return "user-123"
}The FindOne, FindAll, and FindPaginated methods support optional sort expressions to control the order of returned results.
Pass a sort expression as a string using standard SQL ORDER BY syntax:
// Basic sorting
users, err := userDAO.FindAll(ctx, "", "name ASC")
users, err := userDAO.FindAll(ctx, "", "created_at DESC")
// Multiple columns
users, err := userDAO.FindAll(ctx, "", "name ASC, created_at DESC")
// With WHERE conditions
users, err := userDAO.FindAll(ctx, "age > $1", "name ASC", 21)
// Complex sorting
users, err := userDAO.FindPaginated(ctx, 10, 0, "email LIKE $1", "created_at DESC, name ASC", "%@example.com")PostgreSQL:
// Find users ordered by name ascending
users, err := userDAO.FindAll(ctx, "", "name ASC")
// Find users ordered by creation date (newest first)
users, err := userDAO.FindAll(ctx, "", "created_at DESC")
// Find users with conditions and sorting
users, err := userDAO.FindAll(ctx, "age >= $1", "age ASC, name DESC", 18)
// Paginated results with sorting
users, err := userDAO.FindPaginated(ctx, 5, 10, "email LIKE $1", "name ASC", "%@gmail.com")MySQL:
// Find users ordered by name ascending
users, err := userDAO.FindAll(ctx, "", "name ASC")
// Find users with conditions and sorting
users, err := userDAO.FindAll(ctx, "age >= ?", "age ASC, name DESC", 18)
// Paginated results with sorting
users, err := userDAO.FindPaginated(ctx, 5, 10, "email LIKE ?", "name ASC", "%@gmail.com")SQL Server:
// Find users ordered by name ascending
users, err := userDAO.FindAll(ctx, "", "name ASC")
// Find users with conditions and sorting
users, err := userDAO.FindAll(ctx, "age >= @p1", "age ASC, name DESC", 18)
// Paginated results with sorting
users, err := userDAO.FindPaginated(ctx, 5, 10, "email LIKE @p1", "name ASC", "%@gmail.com")Oracle:
// Find users ordered by name ascending
users, err := userDAO.FindAll(ctx, "", "name ASC")
// Find users with conditions and sorting
users, err := userDAO.FindAll(ctx, "age >= :1", "age ASC, name DESC", 18)
// Paginated results with sorting
users, err := userDAO.FindPaginated(ctx, 5, 10, "email LIKE :1", "name ASC", "%@gmail.com")SQLite:
// Find users ordered by name ascending
users, err := userDAO.FindAll(ctx, "", "name ASC")
// Find users with conditions and sorting
users, err := userDAO.FindAll(ctx, "age >= ?", "age ASC, name DESC", 18)
// Paginated results with sorting
users, err := userDAO.FindPaginated(ctx, 5, 10, "email LIKE ?", "name ASC", "%@gmail.com")- Empty Sort: Pass an empty string
""for the sort parameter to use default ordering (or no explicit ordering) - SQL Injection: Sort expressions are concatenated directly into queries. Only use trusted input or validate sort expressions carefully
- Column Names: Use the actual database column names in sort expressions, not Go field names
- Database-Specific: Sort expressions use standard SQL syntax but may have database-specific features available
| Option | Short | Description | Required |
|---|---|---|---|
--input |
-i |
Path to model file or directory | ✅ |
--output |
-o |
Output directory for generated DAOs | ✅ |
--driver |
-d |
Database driver (postgres, mysql, sqlserver, oracle, sqlite) |
✅* |
--interface |
Generate DAO interfaces instead of concrete implementations | ❌ |
* Required only when not using --interface
| Tag | Description | Example |
|---|---|---|
sql:"column_name" |
Map field to database column | sql:"user_name" |
sql:"column_name,primary" |
Mark field as primary key | sql:"id,primary" |
| Database | Driver | Placeholder Style |
|---|---|---|
| PostgreSQL | postgres |
$1, $2, $3 |
| MySQL | mysql |
?, ?, ? |
| SQL Server | sqlserver |
@p1, @p2, @p3 |
| Oracle | oracle |
:1, :2, :3 |
| SQLite | sqlite |
?, ?, ? |
This project is licensed under the MIT License - see the LICENSE file for details.
- Inspired by the need for simple, efficient database access patterns in Go
- Built with Cobra CLI for excellent command-line experience