-
Notifications
You must be signed in to change notification settings - Fork 0
Getting Started
This guide takes you from an empty directory to a running JSON API — with migrations, models, factories, and a Laravel-style HTTP server — in roughly ten minutes. By the end you'll have run every command you'd use on a real project.
If you only want to skim the API surface, the Web-Framework and ORM pages are the dense references. This page is the walkthrough.
You need Go 1.25 or newer (the module is on the Go 1.25 toolchain).
mkdir myapp && cd myapp
go mod init github.com/you/myapp
# Library
go get github.com/devituz/lagodev@latest
# CLI — produces a `lago` binary in $(go env GOPATH)/bin
go install github.com/devituz/lagodev/cmd/lago@latestlago and artisan are interchangeable aliases of the same CLI. Pick
either; the rest of this guide uses lago.
Tip: add
$(go env GOPATH)/binto yourPATHif it isn't already (export PATH="$(go env GOPATH)/bin:$PATH").
lago init # writes lago.json, config/, routes/
lago env:init # writes .env with documented defaultsYou now have:
myapp/
├── .env ← DB_CONNECTION, APP_*, etc.
├── lago.json ← directory layout for generators
├── config/
│ ├── app.go ← AppConfig from env
│ └── database.go ← database.Config from env
└── routes/
└── api.go ← routes.Register(app)
The default .env points at SQLite:
DB_CONNECTION=sqlite
DB_DATABASE=app.db
Switch to Postgres or MySQL by editing those lines — see Configuration for the full list of variables.
lago make:model Post -mfsc \
--fields="title:string,body:text,published:bool:default(false)"The -mfsc flags compose: migration, factory, seeder, service +
controller. All six artifacts share the same field spec, so the model
struct, the migration columns, and the factory's faker defaults stay
in lockstep.
After the command:
models/post.go ← type Post struct { orm.Model; … }
migrations/2026_..._create_posts_table.go ← schema.Create("posts", …)
factories/post_factory.go ← factory.New[Post]
seeders/post_seeder.go ← registers itself in init()
services/post_service.go ← List/Get/Create/Update/Delete
controllers/post_controller.go ← *web.Context handlers
Inspect controllers/post_controller.go — every method is one or two
lines because the framework auto-converts the (any, error) return
into the right HTTP response (see Web-Framework for the full
contract).
Edit routes/api.go:
package routes
import (
"github.com/devituz/lagodev/web"
"github.com/you/myapp/controllers"
)
func Register(app *web.App) {
app.Get("/health", func(c *web.Context) (any, error) {
return map[string]string{"status": "ok"}, nil
})
app.Group("/api/v1", func(g *web.Router) {
g.Resource("posts", controllers.NewPostController(app.DB()))
})
}Resource() registers six routes in one call:
GET /api/v1/posts → Index
GET /api/v1/posts/{id} → Show
POST /api/v1/posts → Store
PUT /api/v1/posts/{id} → Update
PATCH /api/v1/posts/{id} → Update
DELETE /api/v1/posts/{id} → Destroy
Group is recursive: nest groups for /api/v1/admin/... and pass
g.Use(adminOnly) to apply middleware to just that branch.
package main
import (
"log"
"github.com/devituz/lagodev/config"
"github.com/devituz/lagodev/database"
_ "github.com/devituz/lagodev/drivers/sqlite" // blank import = driver registered
"github.com/devituz/lagodev/web"
_ "github.com/you/myapp/migrations" // registers migrations via init()
_ "github.com/you/myapp/seeders" // (optional) seeders too
appcfg "github.com/you/myapp/config"
"github.com/you/myapp/routes"
)
func main() {
_ = config.LoadEnv()
cfg := appcfg.App()
dbCfg := appcfg.Database()
mgr := database.NewManager()
conn, err := mgr.Open("default", dbCfg)
if err != nil { log.Fatal(err) }
app := web.New(
web.WithDatabase(conn),
web.WithManager(mgr),
web.WithMigrations(nil), // applies registered migrations at startup
web.WithAddr(cfg.Addr),
)
routes.Register(app)
app.MustRun()
}The blank import (_ "myapp/migrations") is what makes init() run —
that's where each migration registers itself with the global registry.
Forget the underscore and the migrator runs against an empty list. The
same pattern applies to seeders.
go run .The server prints every registered route and starts listening on
:8080 (or whatever APP_ADDR you set):
ro'yxatdan o'tgan marshrutlar:
GET /health
GET /api/v1/posts
GET /api/v1/posts/{id}
POST /api/v1/posts
PUT /api/v1/posts/{id}
PATCH /api/v1/posts/{id}
DELETE /api/v1/posts/{id}
server tinglamoqda: http://localhost:8080
Try it:
# create
curl -X POST http://localhost:8080/api/v1/posts \
-H "Content-Type: application/json" \
-d '{"Title":"Hello","Body":"World"}'
# → 201 Created with the inserted row
# list
curl http://localhost:8080/api/v1/posts
# → 200 OK with the array
# fetch by id
curl http://localhost:8080/api/v1/posts/1
# → 200 OK or 404 if missing (auto-mapped from orm.ErrNotFound)
# delete
curl -X DELETE http://localhost:8080/api/v1/posts/1
# → 204 No ContentCtrl+C triggers a graceful shutdown — in-flight requests finish
before the listener closes (see Web-Framework for the shutdown
contract).
The repo ships an .air.toml
config. Install
air once:
go install github.com/air-verse/air@latestThen run air from your project root and every save rebuilds and
restarts the server.
// services/post_service_test.go
package services_test
import (
"context"
"testing"
lagotest "github.com/devituz/lagodev/testing"
"github.com/you/myapp/services"
)
func TestList(t *testing.T) {
conn, cleanup := lagotest.SQLite(t)
defer cleanup()
svc := services.NewPostService(conn)
out, err := svc.List(context.Background())
if err != nil { t.Fatal(err) }
if len(out) != 0 { t.Fatalf("expected empty, got %d", len(out)) }
}lagotest.SQLite(t) spins up an in-memory database and applies every
migration registered via init(). Tests run in parallel because each
gets its own database — no shared state.
- Web-Framework — the full Context API, middleware, the secure- by-default stack (security headers, CSRF, rate limit, body limit), validation, cookies.
-
CLI-Reference — every
lagocommand with flag-level detail. - ORM — query-builder cookbook, hooks, casts, transactions.
- Migrations-and-Schema — the full Blueprint DSL, programmatic use, safety guarantees.
- Factories-and-Seeders — generic factories with states, topological seeders.
- examples/blog — a more realistic project with three models, foreign keys, and seeders, all wired together.
lagodev on GitHub · MIT-licensed — see LICENSE.