This document provides a comprehensive guide for migrating Gogs from the Macaron web framework to Flamego. Flamego is the official successor to Macaron, created by the same author, offering improved performance, better routing capabilities, and modern Go practices.
| Feature | Macaron | Flamego | Notes |
|---|---|---|---|
| Initialization | macaron.New() |
flamego.New() |
Similar API |
| Classic Setup | macaron.Classic() |
flamego.Classic() |
Both include logger, recovery, static |
| Handler Signature | func(*macaron.Context) |
func(flamego.Context) |
Flamego uses interface |
| Dependency Injection | Function parameters | Function parameters | Same pattern |
| Routing | Basic | Advanced (regex, optional segments) | Flamego more powerful |
| Route Groups | m.Group() |
f.Group() |
Same concept, similar API |
| Middleware | m.Use() |
f.Use() |
Same pattern |
| Operation | Macaron | Flamego |
|---|---|---|
| Get Request | c.Req.Request |
c.Request().Request |
| Get Response | c.Resp |
c.ResponseWriter() |
| URL Params | c.Params(":name") |
c.Param("name") (no colon) |
| Query Params | c.Query("key") |
c.Query("key") |
| Redirect | c.Redirect(url) |
c.Redirect(url) |
| Set Header | c.Resp.Header().Set() |
c.ResponseWriter().Header().Set() |
| JSON Response | c.JSON(200, data) |
Use render middleware |
| HTML Response | c.HTML(200, tpl) |
Use template middleware |
Macaron:
m := macaron.New()
m.Use(macaron.Logger())
m.Use(macaron.Recovery())
m.Get("/:username/:repo", func(c *macaron.Context) {
username := c.Params(":username")
repo := c.Params(":repo")
c.JSON(200, map[string]string{
"username": username,
"repo": repo,
})
})
Flamego:
f := flamego.New()
f.Use(flamego.Logger())
f.Use(flamego.Recovery())
f.Get("/<username>/<repo>", func(c flamego.Context) {
username := c.Param("username")
repo := c.Param("repo")
// Use render middleware for JSON
c.ResponseWriter().Header().Set("Content-Type", "application/json")
json.NewEncoder(c.ResponseWriter()).Encode(map[string]string{
"username": username,
"repo": repo,
})
})
| Function | Macaron Package | Flamego Package | Status |
|---|---|---|---|
| Core | gopkg.in/macaron.v1 |
github.com/flamego/flamego |
✅ Available |
| Binding | github.com/go-macaron/binding |
github.com/flamego/binding |
✅ Available |
| Cache | github.com/go-macaron/cache |
github.com/flamego/cache |
✅ Available |
| Captcha | github.com/go-macaron/captcha |
github.com/flamego/captcha |
✅ Available |
| CSRF | github.com/go-macaron/csrf |
github.com/flamego/csrf |
✅ Available |
| Gzip | github.com/go-macaron/gzip |
github.com/flamego/gzip |
✅ Available |
| i18n | github.com/go-macaron/i18n |
github.com/flamego/i18n |
✅ Available |
| Session | github.com/go-macaron/session |
github.com/flamego/session |
✅ Available |
| Template | Built-in macaron.Renderer() |
github.com/flamego/template |
✅ Available |
| Toolbox | github.com/go-macaron/toolbox |
N/A | ⚠️ Need custom implementation |
Macaron:
m.Use(session.Sessioner(session.Options{
Provider: "memory",
ProviderConfig: "",
}))
// In handler
func(sess session.Store) {
sess.Set("key", "value")
value := sess.Get("key")
}
Flamego:
f.Use(session.Sessioner(session.Options{
Config: session.MemoryConfig{},
}))
// In handler
func(s session.Session) {
s.Set("key", "value")
value := s.Get("key")
}
Macaron:
m.Use(csrf.Csrfer(csrf.Options{
Secret: "secret-key",
}))
// In handler
func(x csrf.CSRF) {
token := x.GetToken()
}
Flamego:
f.Use(csrf.Csrfer(csrf.Options{
Secret: "secret-key",
}))
// In handler - similar API
func(x csrf.CSRF) {
token := x.Token()
}
Macaron:
type Form struct {
Username string `form:"username" binding:"Required"`
}
m.Post("/signup", binding.Bind(Form{}), func(form Form) {
// Use form
})
Flamego:
type Form struct {
Username string `form:"username" validate:"required"`
}
f.Post("/signup", binding.Form(Form{}), func(form Form) {
// Use form
})
Macaron:
m.Use(macaron.Renderer(macaron.RenderOptions{
Directory: "templates",
}))
func(c *macaron.Context) {
c.HTML(200, "index")
}
Flamego:
import "github.com/flamego/template"
f.Use(template.Templater(template.Options{
Directory: "templates",
}))
func(t template.Template, data template.Data) {
data["Title"] = "Home"
t.HTML(200, "index")
}
Macaron:
m.Use(cache.Cacher(cache.Options{
Adapter: "memory",
}))
func(cache cache.Cache) {
cache.Put("key", "value", 60)
value := cache.Get("key")
}
Flamego:
import "github.com/flamego/cache"
f.Use(cache.Cacher(cache.Options{
Config: cache.MemoryConfig{},
}))
func(c cache.Cache) {
c.Set("key", "value", 60)
value := c.Get("key")
}
feature/flamego-migrationUpdate main application setup (internal/cmd/web.go)
macaron.New() with flamego.New()Update Context wrapper (internal/context/context.go)
*macaron.Context to flamego.ContextMigrate middleware configuration
Update route definitions
:param → <param>)Update handler functions (organized by module)
internal/route/user/*.go)internal/route/repo/*.go)internal/route/admin/*.go)internal/route/org/*.go)internal/route/api/v1/*.go)internal/route/lfs/*.go)Update context usage in handlers
c.Params(":name") with c.Param("name")Update form structs (internal/form/*.go)
Update custom validators
Unit tests
Integration tests
Manual testing
Performance testing
Code cleanup
Documentation updates
Core Web Setup (2 files)
internal/cmd/web.go - Main application setupinternal/app/api.go - API setup
Context System (10 files in internal/context/)
context.go - Main context wrapperauth.go - Authentication contextapi.go - API contextuser.go - User contextrepo.go - Repository contextorg.go - Organization contextForm Definitions (6 files in internal/form/)
Route Handlers (100+ files)
internal/route/ and subdirectoriesTests (50+ files)
/internal/cmd/web.go # Main app setup - HIGH PRIORITY
/internal/context/context.go # Context wrapper - HIGH PRIORITY
/internal/form/form.go # Form binding - HIGH PRIORITY
/internal/route/install.go # Install flow - CRITICAL
/internal/route/home.go # Home page - CRITICAL
/internal/route/user/*.go # User management
/internal/route/repo/*.go # Repository operations
/internal/route/admin/*.go # Admin panel
/internal/route/api/v1/*.go # API endpoints
/internal/route/lfs/*.go # LFS operations
/templates/embed.go # Template system
/go.mod # Dependencies
Issue: Macaron's toolbox middleware (health checks, profiling) has no direct Flamego equivalent.
Solution: Implement custom health check endpoint:
f.Get("/-/health", func(c flamego.Context) {
if err := database.Ping(); err != nil {
c.ResponseWriter().WriteHeader(500)
return
}
c.ResponseWriter().WriteHeader(200)
c.ResponseWriter().Write([]byte("OK"))
})
Issue: Current Context embeds *macaron.Context, which is tightly coupled.
Solution: Refactor to use composition instead:
type Context struct {
ctx flamego.Context
// Other fields...
}
func (c *Context) Context() flamego.Context {
return c.ctx
}
Issue: Gogs has many custom response methods on Context (HTML, JSON, etc.).
Solution: Update methods to use Flamego's middleware:
// Before (Macaron)
func (c *Context) JSON(status int, data any) {
c.Context.JSON(status, data)
}
// After (Flamego) - inject template.Template
func (c *Context) JSON(status int, data any) {
c.ResponseWriter().Header().Set("Content-Type", "application/json")
c.ResponseWriter().WriteHeader(status)
json.NewEncoder(c.ResponseWriter()).Encode(data)
}
Issue: Macaron uses :param, Flamego uses <param>.
Solution: Find and replace all route definitions:
# Find all route definitions
grep -r 'm\.Get\|m\.Post\|m\.Put\|m\.Delete\|m\.Patch' internal/cmd/web.go
# Update syntax
:param → <param>
Issue: Macaron uses ^pattern$ for regex, Flamego has different syntax.
Solution: Update regex patterns to Flamego format:
// Macaron
m.Get("/^:type(issues|pulls)$", handler)
// Flamego
f.Get("/<type:issues|pulls>", handler)
Issue: Handler function parameter order matters in both frameworks.
Solution: Ensure correct parameter order in handlers:
// Flamego injects in order: Context, custom services, form bindings
func handler(
c flamego.Context,
sess session.Session,
form UserForm,
) { }
Issue: Flash messages API may differ.
Solution: Test and update flash message handling:
// Verify API compatibility
sess.SetFlash("message")
flash := sess.GetFlash()
Issue: Any custom Macaron middleware needs porting.
Solution: Audit and rewrite custom middleware:
// Macaron middleware
func MyMiddleware() macaron.Handler {
return func(c *macaron.Context) { }
}
// Flamego middleware
func MyMiddleware() flamego.Handler {
return func(c flamego.Context) { }
}
# Keep both versions temporarily
go mod tidy
# Run tests with Flamego
go test ./...
# Compare behavior
Create integration test suite that covers:
# Benchmark before migration
ab -n 1000 -c 10 http://localhost:3000/
# Benchmark after migration
ab -n 1000 -c 10 http://localhost:3000/
# Compare results
If critical issues are discovered:
Git Revert
git revert <commit-range>
git push
go.mod Rollback
git checkout main -- go.mod go.sum
go mod tidy
Deploy Previous Version
If full migration is too risky:
Migrating from Macaron to Flamego is a significant but manageable undertaking. Flamego provides excellent feature parity with Macaron, including all the middleware that Gogs currently uses (except toolbox, which is easy to replace).
✅ Complete Feature Parity: All required middleware is available
✅ Same Philosophy: Dependency injection pattern maintained
✅ Better Performance: Improved routing engine
✅ Active Development: Regular updates and improvements
✅ Official Successor: Created by Macaron's author
✅ Better Routing: More powerful routing capabilities
⚠️ Large Scope: ~150+ files need modification
⚠️ Testing Burden: Comprehensive testing required
⚠️ Learning Curve: Team needs to learn new APIs
⚠️ Toolbox Replacement: Need custom health check implementation
Proceed with migration using the phased approach outlined above. The migration is worthwhile because:
// Old imports
import (
"gopkg.in/macaron.v1"
"github.com/go-macaron/binding"
"github.com/go-macaron/cache"
"github.com/go-macaron/captcha"
"github.com/go-macaron/csrf"
"github.com/go-macaron/gzip"
"github.com/go-macaron/i18n"
"github.com/go-macaron/session"
)
// New imports
import (
"github.com/flamego/flamego"
"github.com/flamego/binding"
"github.com/flamego/cache"
"github.com/flamego/captcha"
"github.com/flamego/csrf"
"github.com/flamego/gzip"
"github.com/flamego/i18n"
"github.com/flamego/session"
"github.com/flamego/template"
)
// Route parameters
m.Get("/:username/:repo") → f.Get("/<username>/<repo>")
// Handler signature
func(c *macaron.Context) → func(c flamego.Context)
// Get parameter
c.Params(":username") → c.Param("username")
// Response writer
c.Resp → c.ResponseWriter()
// Request
c.Req.Request → c.Request().Request
// Session interface
func(sess session.Store) → func(sess session.Session)
// Template rendering
c.HTML(200, "tpl") → t.HTML(200, "tpl")