Tired of wrestling with HTML, CSS, and JavaScript just to create a simple user interface? Meet github.com/ebuckley/bff , the Go developer's new best friend for backend flows.
BFF is a powerful library that lets you build functional UIs and forms without writing a single line of frontend code. It's designed for Gophers who need quick, efficient internal tools without the hassle of learning new frameworks.
Key features:
Let's dive into how BFF can streamline your development process and save you valuable time.
Our example will be a process that updates this Google Sheet. You can try out this exact code which I have integrated into the site here.
func(ctx context.Context, io *bff.Io) error {
io.Display.Markdown(`
# Update a Google Sheet
This little action exists to show you how you can update a Google Sheet from a BFF action.`)
Here, we set up our handler which simply takes an bff.Io
. The Io struct is our portal into the UI, it creates components on the screen and lets us capture input from the person on the website. The interface for BFF is inspired by the http.Handler
we know and love, but better because you can just return an error if you want to end execution.
row, err := io.Input.Number("What Row would you like to update?")
if err != nil {
return err
}
col, err := io.Input.Text("What Column would you like to update?")
if err != nil {
return err
}
val, err := io.Input.Text("What value would you like to set?")
if err != nil {
return err
}
io.Display.Markdown(fmt.Sprintf("Updating row %d, column %s with value %s", row, col, val))
The Input
struct encapsulates a bunch of handy form components. Right now you can choose from text
,textarea
,number
,email
,datetime
and slider
variants.
keyPath := os.Getenv("GOOGLE_API_KEY_PATH")
if len(keyPath) == 0 {
return fmt.Errorf("GOOGLE_API_KEY_PATH is not set")
}
keyJSON, err := os.ReadFile(keyPath)
if err != nil {
return err
}
srv, err := sheets.NewService(ctx, option.WithCredentialsJSON(keyJSON))
if err != nil {
return err
}
spreadsheetID := "1Uol7hBRk65_FVs5Z13WeZ2rJ2yv5z-OHKhi33fo2yIw"
writeRange := fmt.Sprintf("Sheet1!%s%d", col, row)
vr := &sheets.ValueRange{
Values: [][]interface{}{{val}},
}
// Perform the update
_, err = srv.Spreadsheets.Values.Update(spreadsheetID, writeRange, vr).
ValueInputOption("RAW").Do()
if err != nil {
return err
}
io.Display.Markdown("# 🍬 Updated the Google Sheet 🍬")
io.Display.Link("Do another update?", "/backend/a/update-a-google-sheet")
return nil
}
All the rest of this is about setting up and making the API call for updating the google sheet. This is a good thing. We want our handlers to not be about UI framework but about doing the work that’s actually delivering value. Finally we let the user know that the process is complete and invite them to do it again by showing a link.
The run of this entire handler function is within a single websocket connection, as soon as you call a function on the IO
it will update the user interface. If you need to run a slow query – that’s totally fine the display function will be waiting to show your content as soon as it is ready for the user.
Do use this for quickly standing up UI for your internal team to run processes and automations. Don’t run it for the public like I’m doing in this example. This thing is designed to be the retool alternative that doesn’t require any learning and clicking in a new UI.
Here’s a quick list of ideas you could use bff for today.
It is early days for this little library inspired by interval. I’m really interested in helping people like you get started with it.