Lagodev: A Laravel-style Go framework that ships with an ORM and CLI
Lagodev is a full-stack Go backend framework that borrows heavily from Laravel’s developer experience. It gives you an Eloquent-style ORM, schema migrations, seeders, factories, and a web layer that can plug into Gin, Fiber, or Echo. If you’ve used Laravel in PHP and switched to Go, the structure will feel familiar. But the Laravel comparison isn’t what caught my attention — it’s the Go patterns the framework uses to make it work.
The CLI: an Artisan for Go
Laravel developers know php artisan. Lagodev has its own version, split across two entry points: cmd/lago/main.go for project scaffolding, and cmd/artisan/main.go for in-project commands like generating models, controllers, and running migrations.
The commands live in cli/cmd/. Each file maps to a specific generator or action — make_model.go generates a model struct, make_migration.go generates a migration file, make_controller.go a controller, make_seeder.go a database seeder, make_factory.go a factory for test data, make_service.go a service layer file, and make_crud.go scaffolds an entire CRUD set in one shot.
cli/cmd/stubs.go holds Go template strings — blueprints that get rendered into .go files when you run a command. This is a well-worn pattern in Go CLI tools: embed template text as string constants, then use text/template to fill in names, types, and fields. Nothing exotic, but it works.
Field parsing lives in cli/cmd/fields.go. When you specify model fields via the CLI (something like name:string age:int), this file parses those definitions and maps them to Go types, then feeds the results into the templates from stubs.go.
Database layer: connection management and transactions
The database package (database/) handles connections, configuration, and transactions. It supports MySQL, PostgreSQL, and SQLite.
database/config.go defines the configuration structs. database/connection.go manages connections, wrapping database/sql and providing a consistent interface regardless of driver. database/manager.go is the coordinator: it picks the right driver based on config and exposes the connection pool.
database/transaction.go handles transaction management. If you’ve dealt with transactions in Go through database/sql, you know how easy it is to botch the error handling. The standard pattern is to defer a rollback and only commit on success:
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
// ... do work ...
return tx.Commit()
This works because Rollback() on an already-committed transaction is a no-op. Lagodev wraps the pattern in database/connection.go with Transaction and TransactionWith, committing on success and rolling back on errors or panics. The database/transaction.go file adds savepoint helpers on top. If you want to go deeper on how context.Context flows through database calls and controls cancellation, I covered that in the post on context in Go.
The adapter pattern: supporting multiple routers
This is the design decision I find most interesting. The adapters/ directory contains framework-specific adapter packages. For Gin, there’s:
adapters/gin/context.go— wraps Gin’s*gin.Contextinto Lagodev’s own context interfaceadapters/gin/handler.go— converts Lagodev handler functions into Gin-compatible handlersadapters/gin/doc.go— package documentation
The core router at router/router.go defines a framework-agnostic routing interface. Each adapter bridges that interface to a specific framework.
This is Go interfaces done right. You define a small interface in the core package, then implement it per framework. Same pattern you see in logging libraries like slog, where the Handler interface can be backed by different outputs. The principle worth internalizing: keep interfaces small, define them where they’re consumed (not where they’re implemented), and let each adapter satisfy them independently.
If you’re new to this style of interface design, the post on functional options covers a related pattern for flexible configuration.
Reflection and performance caching
An ORM needs to map Go structs to database rows. That means reflection. Lagodev puts its reflection utilities in internal/reflectutil/cache.go. The filename tells you everything: it caches reflection results.
Reflection in Go is expensive. Every call to reflect.TypeOf() or every struct tag inspection costs you. ORMs that naively reflect on every query pay that cost over and over. The fix is to cache struct metadata — field names, types, column tags — on first access and reuse it on subsequent calls. This is what sqlx does internally, and it’s the right call here.
internal/inflect/inflect.go handles string inflection — pluralizing model names to derive table names, converting CamelCase to snake_case, that sort of thing. Laravel does this automatically (model Post maps to table posts), and Lagodev brings the same convention to Go.
The migration system
Migrations live in cli/cmd/migrate.go and cli/cmd/make_migration.go. The make_migration command generates a new migration file from a template. The migrate command runs pending migrations against the database.
Go doesn’t have a built-in migration system. Most projects reach for goose or golang-migrate. Lagodev rolls its own to stay consistent with the artisan-style workflow. The generated migration files contain Up and Down functions — same convention every migration tool uses, because it’s a good convention.
Service layer and CRUD generation
The make_service command generates a service file — a layer between your controller and your ORM models. The example at examples/blog/services/post_service.go shows what this looks like in practice for a blog post service.
make_crud ties the whole thing together. One command generates a model, migration, controller, service, and factory. This is where the framework earns its “full-stack” label. Each generated file follows a convention, and the templates in stubs.go keep everything consistent.
When would you use this?
Lagodev targets Go developers who want Rails/Laravel-style productivity. If you prefer explicit, minimal Go code, this may feel heavier than raw database/sql plus a small router. If you’re coming from Laravel and want familiar patterns in Go, it could save you real setup time.
It supports MySQL, PostgreSQL, and SQLite. It works as a standalone web framework or as an ORM-only library alongside Gin, Fiber, or Echo — that flexibility comes from the adapter pattern I described above.
Some things worth keeping in mind: the project is young. The reflection-based ORM adds overhead compared to raw SQL or code generation tools like sqlc. And artisan-style code generation means you’ll have a pile of generated files to understand and maintain. For a comparison of how Go developers typically handle database access, the post on Go project structure patterns discusses service layers and database organization.
Patterns to study, even if you skip the framework
Whether or not you adopt Lagodev, the patterns it uses are worth studying on their own. Interface-based adapters for swapping web frameworks. Reflection caching to avoid paying the same cost twice. Template-driven code generation that keeps a CLI consistent. Clean separation between CLI tooling and runtime code. These aren’t Lagodev inventions — they’re solid Go idioms assembled into one place. The source code is a good read if you’re curious about how Go frameworks get built from scratch.