early · v0.66.0

Full-stack Go that doesn't get in the way of you or your agents.

GoFastr is a full-stack Go framework. Declare your domain in Go and get server-rendered screens, REST endpoints, MCP tools, migrations, and typed queries. It stays plain Go on disk that you own.

$ go install github.com/DonaldMurillo/gofastr/cmd/gofastr@v0.66.0

During development, gofastr dev gives Claude Code or Codex the app's routes, config, and logs over MCP. In production, user agents call the same data under the same permissions.

core only
framework
full-stack app
main.go
30 lines
package mainimport (	"context"	"net/http"	"github.com/DonaldMurillo/gofastr/core/handler"	"github.com/DonaldMurillo/gofastr/core/render"	"github.com/DonaldMurillo/gofastr/core/router")type Pong struct {	Status string `json:"status"`}func main() {	r := router.New()	// A server-rendered page.	r.Get("/", render.HTMLHandler(func(req *http.Request) render.HTML {		return render.Tag("h1", nil, render.Text("Hello from core."))	}))	// A typed JSON route — the adapter binds input and serializes output.	r.Get("/api/ping", handler.HandlerAdapter(func(ctx context.Context, _ struct{}) (Pong, error) {		return Pong{Status: "ok"}, nil	}))	http.ListenAndServe(":8080", r)}
main.go
26 lines
package mainimport (	"database/sql"	"log"	"github.com/DonaldMurillo/gofastr/core/schema"	"github.com/DonaldMurillo/gofastr/framework"	_ "github.com/DonaldMurillo/gofastr/sqlite/stdlib")func main() {	db, _ := sql.Open("sqlite3", "app.db")	app := framework.NewApp(framework.WithDB(db), framework.WithMCP()) // WithMCP serves the tools at /mcp	// CRUD is auto-on when a DB is set (CRUD *bool: nil = auto).	app.Entity("posts", framework.EntityConfig{		Exposure: &framework.ExposureConfig{			Public: true, // anonymous read AND write; omit it and CRUD requires a session (secure by default)			MCP:    true, // emit posts_list/get/create/update/delete MCP tools		},		Fields: []schema.Field{{Name: "title", Type: schema.String, Required: true}},	})	log.Fatal(app.Start(":8080")) // GET/POST /posts, /openapi.json, MCP — all live}
main.go
66 lines
package mainimport (	"database/sql"	"log"	"github.com/DonaldMurillo/gofastr/battery/auth"	"github.com/DonaldMurillo/gofastr/core-ui/app"	"github.com/DonaldMurillo/gofastr/core-ui/html"	"github.com/DonaldMurillo/gofastr/core/render"	"github.com/DonaldMurillo/gofastr/core/schema"	"github.com/DonaldMurillo/gofastr/framework"	"github.com/DonaldMurillo/gofastr/framework/uihost"	_ "github.com/DonaldMurillo/gofastr/sqlite/stdlib")// A screen is plain Go: Render returns server-rendered HTML.type HomeScreen struct{}func (s *HomeScreen) ScreenTitle() string { return "Notes" }func (s *HomeScreen) Render() render.HTML {	return html.Heading(html.HeadingConfig{Level: 1}, render.Text("My notes"))}func main() {	db, _ := sql.Open("sqlite3", "notes.db")	// Server-rendered screens. Each also serves an auto llm.md.	ui := app.NewApp("Notes")	ui.Register("/", &HomeScreen{}, nil)	// SEO for those pages.	host := uihost.New(ui,		uihost.WithDescription("A tiny notes app."),		uihost.WithOpenGraph(uihost.OG{Title: "Notes", Type: "website"}),		uihost.WithSitemap(uihost.SitemapConfig{BaseURL: "https://notes.example"}),	)	// MCP for agents.	fwApp := framework.NewUIHostApp(host,		framework.WithDB(db),		framework.WithAPIPrefix("/api"),		framework.WithMCP(),	)	// Scope.OwnerField scopes rows per user: anonymous → 401, cross-user → 404.	fwApp.Entity("notes", framework.EntityConfig{		Scope:    &framework.ScopeConfig{OwnerField: "user_id"},		Exposure: &framework.ExposureConfig{MCP: true},		Fields:   []schema.Field{{Name: "title", Type: schema.String, Required: true}},	})	// Login + sessions.	authMgr := auth.New(auth.AuthConfig{		DevMode:      true, // dev only: mints a per-process JWT secret; set JWTSecret in prod		UserStore:    auth.NewEntityUserStore(db, "auth_users"),		SessionStore: auth.NewEntitySessionStore(db, "auth_sessions"),	})	authMgr.Use(auth.NewCorePlugin())	if err := authMgr.Init(fwApp); err != nil {		log.Fatal(err)	}	fwApp.Use(auth.SessionMiddleware(authMgr))	log.Fatal(fwApp.Start(":8080"))}

Numbers you can check.

Each value is measured by this running binary or enforced by a test in the repo. Nothing here is an adjective.

12.1 KBof client JavaScript, gzipped

The core runtime, measured from this running binary. Feature modules load on demand; a size-budget test fails the build if any of them grows.

95docs embedded in every binary

gofastr docs reads them offline. This site serves the same files under /docs and /llms.txt; agents query them over MCP.

5MCP tools per entity

list, get, create, update, delete. Each dispatches through the app router, so the caller's login and permissions apply.

2databases

SQLite and Postgres. No MySQL, no Mongo.

1binary to deploy

go build emits it. No Node, no platform, no telemetry.

0npm packages

There is no package.json in the repo. The client runtime is checked-in JS the binary serves.

Server-rendered screens, not just an API.

Below is the shape of a server-rendered screen: a data table with status badges and a create button, built from framework/ui components and served as plain HTML.

Most Go frameworks stop at the API. GoFastr renders the pages too: on the server, in Go, with no React or Vue on the client.

  • Every page is full HTML on first load: fast and readable by crawlers and agents.
  • A small JS runtime hydrates that HTML in place with no re-render. Cross-page nav swaps content client-side with a route cache; you never write the router.
  • In-page changes such as sort, paginate, or add a row are island calls. The server returns new HTML and the runtime swaps one part.
  • You write screens in Go, composed from framework/ui components.
meridian.local/customers

Customers

+ New customer
NamePlanMRRStatus
Acme Corppro$1,240active
Globexenterprise$8,900active
Initechfree$0churned
Umbrellapro$2,150active

/customers: a server-rendered screen from framework/ui, in plain Go you own.