Folio is a tool I built to save time on the boring stuff. After years of building internal tools and getting stuck on UI work, I created Folio to automatically generate UIs from Go structs, letting me skip the front-end hassle and focus on the fun parts of development.
In this example, we define a Person
struct with various fields and tags for validation and rendering. Folio automatically generates a user interface for the Person
model, allowing users to create, read, update, and delete records.
type Person struct {
folio.Meta `kind:"person" json:",inline"`
Name string `json:"name" form:"rw" is:"required"`
Age int `json:"age" form:"rw" is:"range(0|130)"`
Gender string `json:"gender" form:"rw" is:"required,in(male|female|prefer_not_to)"`
Country string `json:"country" form:"rw"`
Address string `json:"address" form:"rw"`
Phone string `json:"phone" form:"rw"`
Boss folio.URN `json:"boss" form:"rw" kind:"person"`
IsEmployed bool `json:"isEmployed" form:"rw" desc:"Is the person employed?"`
JobTitle string `json:"jobTitle" form:"rw"`
Workplace folio.URN `json:"workplace" form:"rw" kind:"company" query:"namespace=*;match=Inc"`
}
The generated UI includes form fields for each struct field, as well as buttons for creating, updating, and deleting records. The UI also supports pagination, sorting, and filtering.
I’ve built a lot of internal tools over the years — everything from experimentation platforms to machine learning management tools. And while those tools were powerful, the process often felt like a never-ending cycle of reinventing the wheel, except this wheel was for a car that I didn’t really want to drive.
The problem? The minor stuff always took way more time and energy than it should. Need a UI for CRUD operations? That’ll be hours of React, CSS, and front-end misery. I just wanted to get things done, not spend my weekends pretending to enjoy writing JavaScript.
That’s where this project comes in. I built this for my personal projects where I have no team, no budget, and let’s be honest — no patience for building full-blown React apps. Folio generates the UI for me straight from my Go structs (view models), so I can focus on the fun parts (or at least the parts that don’t make me want to quit tech and become a beekeeper).
In short: Folio takes care of the boring stuff, so you can keep your focus on the good stuff—like actually building cool things instead of wrangling with endless form fields and dropdowns.
Keep in mind that this project is still in its early stages, so there’s a lot of room for improvement. I'm also not going to pretend that this is the best solution for every project, and there's still a ton of features that I want to add, so use it at your own risk.
- Auto-Generated UI: Automatically generates user interfaces from view models, eliminating the need for manual UI creation.
- CRUD Operations: Simplifies Create, Read, Update, and Delete operations for objects.
- Error Handling & Validation: Provides utilities for error handling and validation.
- Templating: Uses the
templ
package to define and render HTML templates. - Abstracted Storage: Supports SQLite database operations, but can be extended to other storage solutions.
- Pagination, Search and Filtering: Built-in support for paginated lists, search, and namespace (i.e project) filtering.
-
Navigate to the company example directory:
cd examples/company && go run .
-
Open your browser and navigate to
http://localhost:7000
.
Define your models by embedding folio.Meta
and specifying field tags for validation and form rendering.
type Person struct {
folio.Meta `kind:"person" json:",inline"`
Name string `json:"name" form:"rw" is:"required"`
Age int `json:"age" form:"rw" is:"range(0|130)"`
Gender string `json:"gender" form:"rw" is:"required,in(male|female|prefer_not_to)"`
Country string `json:"country" form:"rw"`
Address string `json:"address" form:"rw"`
Phone string `json:"phone" form:"rw"`
Boss folio.URN `json:"boss" form:"rw" kind:"person"`
IsEmployed bool `json:"isEmployed" form:"rw" desc:"Is the person employed?"`
JobTitle string `json:"jobTitle" form:"rw"`
Workplace folio.URN `json:"workplace" form:"rw" kind:"company" query:"namespace=*;match=Inc"`
}
Register your models with the registry and provide options like icons, titles, and sorting.
reg := folio.NewRegistry()
folio.Register[*Person](reg, folio.Options{
Icon: "user-round",
Title: "Person",
Plural: "People",
Sort: "1",
})
Use the render.ListenAndServe function to start the server.
db, err := sqlite.Open("file:data.db?_journal_mode=WAL", reg)
if err != nil {
panic(err)
}
if err := render.ListenAndServe(7000, reg, db); err != nil {
slog.Error("Failed to start server!", "details", err.Error())
os.Exit(1)
}
Contributions are welcome! Please open an issue or submit a pull request on GitHub.
This project is licensed under the MIT License. See the LICENSE file for details.
This project leverages several open-source libraries and tools. We would like to acknowledge and thank the following projects:
- templ: A Go package for defining and rendering HTML templates.
- htmx-go: A Go package for integrating htmx with Go web applications.
- SQLite: A C library that provides a lightweight, disk-based database.
- Franken-UI: A collection of UI components for building modern web applications.
- Tailwind CSS: A utility-first CSS framework for rapidly building custom user interfaces.