DEV Community

A0mineTV
A0mineTV

Posted on

Learning Buffalo by Building a Small Helpdesk App in Go

When people talk about Go web development, the conversation often jumps straight to minimal stacks: net/http, a router, a query builder, a templating choice, and then a lot of glue code.

That approach is great when you want total control.

But sometimes, you want a more complete web framework experience — something that gives you a structured project layout, routing, templates, models, migrations, forms, and a development workflow that feels closer to Laravel or Rails.

That is exactly why I wanted to explore Buffalo.

To make it concrete, I built a small example project called HelpDesk Lite, a simple support app with users, tickets, and comments:

GitHub repo: https://github.com/VincentCapek/helpdesk_lite

In this article, I want to show how Buffalo works in practice through this project, what the developer experience feels like, and what I learned along the way.


Why Buffalo ?

Buffalo is interesting because it is not just "a router plus a few helpers".

It gives you a full web application structure for Go:

  • actions for request handling
  • models for database entities
  • Pop for ORM-style database work and migrations
  • Plush for server-side templates
  • a CLI that helps you run, build, and manage the app

If you are coming from frameworks like Laravel, Rails, or even Symfony, the mental model feels familiar — but you still write Go.

That mix is what makes Buffalo appealing.


The Example: HelpDesk Lite

To learn Buffalo properly, I did not want a “hello world” project. I wanted a small app with enough moving parts to exercise the framework.

So I used this structure:

  • users
  • tickets
  • comments

The idea is simple:

  • a user can create a support ticket
  • tickets have a title, description, status, priority, and category
  • users can add comments to a ticket
  • there is also an admin entry point in the app

This kind of project is perfect for learning because it touches most of the things you expect in a real web app:
routing, CRUD, validation, relational data, templates, and tests.


What a Buffalo Project Feels Like

One of the first things I liked about Buffalo is that the project layout pushes you toward a clean separation of concerns.

In this app, the important folders are easy to understand:

  • actions/ → request handlers
  • models/ → structs and validation
  • migrations/ → schema changes
  • templates/ → Plush views
  • config/ → application setup
  • cmd/app/ → application entry point

That is a big part of the Buffalo experience:
instead of deciding everything from scratch, you start with conventions.

For small and medium-sized applications, that is a real productivity boost.


Routing: Very Easy to Read

In Buffalo, routes are straightforward.

For a helpdesk app, the route tree is very readable:

app.GET("/", HomeHandler)

app.GET("/tickets", TicketsIndex)
app.GET("/tickets/new", TicketsNew)
app.POST("/tickets", TicketsCreate)

app.GET("/tickets/{ticket_id}", TicketsShow)
app.GET("/tickets/{ticket_id}/edit", TicketsEdit)
app.PUT("/tickets/{ticket_id}", TicketsUpdate)
app.DELETE("/tickets/{ticket_id}", TicketsDestroy)

app.POST("/tickets/{ticket_id}/comments", CommentsCreate)

app.GET("/admin", AdminDashboard)
Enter fullscreen mode Exit fullscreen mode

If you are used to MVC frameworks, this feels natural immediately.

It also makes the request flow easy to follow:
URL → action → model/database → template.


Actions: The Core of the Request Flow

The actions/ folder is where the app logic starts to feel real.

A very typical Buffalo action looks like this:

  1. get the database transaction from the context
  2. bind request data into a struct
  3. validate the data
  4. save it with Pop
  5. either render a template again on failure or redirect on success

That pattern appears again and again in a Buffalo app.

For example, creating a ticket or comment follows the same idea:

  • c.Bind(...) to map form data into a struct
  • Validate(...) or ValidateAndCreate(...)
  • tx.Create(...) to persist
  • c.Render(...) for validation errors
  • c.Redirect(...) after success

I like this because it keeps actions explicit.
Nothing feels too magical.
You can read the code top to bottom and understand the full lifecycle of the request.


Pop: Database Work Without Too Much Boilerplate

Buffalo uses Pop for database access and migrations.

For a project like HelpDesk Lite, that feels like a nice middle ground:

  • simpler than hand-writing everything with raw SQL
  • less heavy than some ORMs that hide too much
  • integrated well into the Buffalo workflow

In practice, you work with your models as Go structs, and Pop helps you:

  • query data
  • create/update/delete rows
  • validate models
  • manage migrations

That was especially useful for ticket and comment creation because the code stays readable.

A request can bind data into a struct, validate it, save it, then redirect — all in a very compact way.


Migrations: A Good Workflow Once You Understand the Rules

Buffalo migrations are powered by Pop, and you can write them with Fizz or raw SQL.

For this project, the migrations created tables like:

  • users
  • tickets
  • comments

One thing that is important to understand early:
only proper migration files are executed by Pop.

So files like these are valid:

  • 20260318102228_create_tickets.up.fizz
  • 20260318102228_create_tickets.down.fizz

But a standalone file like schema.sql is not treated as a migration by soda migrate.

That was a useful lesson.

Another practical point:
once a migration is marked as applied, changing its content later does not magically re-run it.
If you want to evolve the schema, create a new migration.

That is standard migration discipline, but Buffalo/Pop makes you feel it very quickly.


Plush Templates: Familiar, but You Need to Respect the Context

Buffalo uses Plush for templates.

If you already know server-side template engines, Plush is easy to pick up:
you print values, loop through collections, and render HTML with embedded logic.

What I liked:

  • templates stay readable
  • forms are easy to build
  • rendering is tightly integrated with actions

What I learned the hard way:
your template only sees what you explicitly put into the context.

If a template uses ticket.Title, the action must provide ticket with c.Set("ticket", ticket) before rendering.

That sounds obvious, but it is one of those details that teaches you how Buffalo thinks:
templates are not magically connected to your action state.
You pass the data they need, explicitly.

That is actually a good thing.
It keeps the rendering layer predictable.


Validation and Error Rendering

One part I found especially educational was validation handling.

The pattern is simple:

  • validate the model
  • if there are errors, put them into the context
  • re-render the form with a 422
  • if validation passes, save and redirect

This makes form handling very clear.

It also reinforces a classic web application pattern:
render on failure, redirect on success.

That kind of flow is where Buffalo feels very comfortable.


Running the App

The basic local workflow is straightforward.

Run migrations:

soda migrate -e development -d
Enter fullscreen mode Exit fullscreen mode

Then start the app:

buffalo dev
Enter fullscreen mode Exit fullscreen mode

And Buffalo handles the development loop.

One useful detail:
if you use SQLite, make sure your Buffalo binary actually includes SQLite support.
I ran into that while testing the project.
buffalo dev and soda are not always built the same way, so environment setup matters.

That was a good reminder that frameworks improve workflow, but understanding the tooling still matters.


What I Like About Buffalo

After building this helpdesk example, here is what stood out to me.

1. It gives Go a more “full-stack framework” feel

A lot of Go web stacks are intentionally minimal.
Buffalo is more opinionated, and that is exactly its value.

You get a real application structure instead of assembling everything manually.

2. The request flow is easy to understand

Routes, actions, models, templates, redirects:
everything follows a clear pattern.

3. Pop and migrations are practical

For CRUD-style applications, Pop feels productive without becoming too abstract.

4. The framework teaches discipline

Buffalo makes some mistakes very visible:

  • missing context variables in templates
  • incorrect migration assumptions
  • validation/rendering mistakes
  • SQLite setup issues in the CLI tooling

That might sound annoying at first, but it helps you understand the framework faster.

Top comments (0)