Tools
Tools: Building a minimal Go framework in public (v0.1.3)
2026-01-20
0 views
admin
The Philosophy ## What It Looks Like ## The Core Features ## Fast Routing with Radix Trees ## Middleware That Makes Sense ## Context Pooling ## Static File Serving ## The Performance Story ## Real-World Use Cases ## Microservices ## REST APIs ## Single Page Applications ## Learning and Teaching ## The Testing Story ## What's Missing (And Why) ## The Roadmap ## Try It Yourself ## The Takeaway What happens when you build a web framework with one simple rule: zero dependencies? That's the question behind Marten, a minimal HTTP framework built entirely on Go's standard library. Most Go web frameworks pull in dozens of dependencies. Gin has 9 direct dependencies. Echo has 11. Fiber has 15. Each dependency brings its own dependencies, and suddenly your go.mod looks like a phone book. Marten takes a different approach: use only what Go gives you. No external packages. No vendor lock-in. Just net/http, encoding/json, and the rest of the standard library. Here's a complete API in Marten: Clean. Familiar. No magic. Marten uses a radix tree router for efficient path matching. It handles path parameters (:id), wildcards (*filepath), and route groups: Middleware in Marten is just a function that wraps a handler: The framework includes 14 built-in middleware: Logger, Recover, CORS, RateLimit, BasicAuth, Timeout, Secure, BodyLimit, Compress, ETag, RequestID, Static, and NoCache. Every request gets a Ctx object from a sync.Pool. This reduces allocations and keeps memory usage low, even under heavy load: The latest release (v0.1.3) adds static file serving with all the features you'd expect: Content-type detection, HTTP caching (If-Modified-Since), directory browsing, and security against directory traversal attacks—all built-in. How does a zero-dependency framework perform? Surprisingly well. Benchmarks against Gin, Echo, and Chi show Marten holding its own: Not the fastest, but competitive. And with zero dependencies. When you're building dozens of microservices, dependency bloat adds up. Marten keeps your Docker images small and your build times fast. Build production-ready APIs with built-in middleware for logging, rate limiting, CORS, and authentication: Serve your SPA with automatic fallback to index.html for client-side routing: Want to understand how web frameworks work? Read Marten's source. It's ~2,000 lines of readable Go code. No abstractions hiding abstractions. Marten v0.1.3 ships with 325 tests covering: All tests pass with Go's race detector. No known memory leaks. Production-ready. The philosophy is simple: if the standard library can do it, use the standard library. If you need more, add it yourself. Future releases will add: But always with the same constraint: zero dependencies. Check out the examples for CRUD APIs, JWT auth, file servers, and more. Marten isn't trying to replace Gin or Echo. It's an experiment in minimalism. A proof that you can build a capable web framework without pulling in the world. Sometimes, less is more. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK:
package main import ( "github.com/gomarten/marten" "github.com/gomarten/marten/middleware"
) func main() { app := marten.New() app.Use(middleware.Logger) app.Use(middleware.Recover) app.GET("/", func(c *marten.Ctx) error { return c.OK(marten.M{"message": "Hello, World!"}) }) app.GET("/users/:id", func(c *marten.Ctx) error { id := c.ParamInt("id") return c.OK(marten.M{"id": id}) }) app.Run(":8080")
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
package main import ( "github.com/gomarten/marten" "github.com/gomarten/marten/middleware"
) func main() { app := marten.New() app.Use(middleware.Logger) app.Use(middleware.Recover) app.GET("/", func(c *marten.Ctx) error { return c.OK(marten.M{"message": "Hello, World!"}) }) app.GET("/users/:id", func(c *marten.Ctx) error { id := c.ParamInt("id") return c.OK(marten.M{"id": id}) }) app.Run(":8080")
} CODE_BLOCK:
package main import ( "github.com/gomarten/marten" "github.com/gomarten/marten/middleware"
) func main() { app := marten.New() app.Use(middleware.Logger) app.Use(middleware.Recover) app.GET("/", func(c *marten.Ctx) error { return c.OK(marten.M{"message": "Hello, World!"}) }) app.GET("/users/:id", func(c *marten.Ctx) error { id := c.ParamInt("id") return c.OK(marten.M{"id": id}) }) app.Run(":8080")
} CODE_BLOCK:
api := app.Group("/api/v1")
api.GET("/users", listUsers)
api.GET("/users/:id", getUser)
api.POST("/users", createUser) Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
api := app.Group("/api/v1")
api.GET("/users", listUsers)
api.GET("/users/:id", getUser)
api.POST("/users", createUser) CODE_BLOCK:
api := app.Group("/api/v1")
api.GET("/users", listUsers)
api.GET("/users/:id", getUser)
api.POST("/users", createUser) CODE_BLOCK:
func Timer(next marten.Handler) marten.Handler { return func(c *marten.Ctx) error { start := time.Now() err := next(c) log.Printf("took %v", time.Since(start)) return err }
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
func Timer(next marten.Handler) marten.Handler { return func(c *marten.Ctx) error { start := time.Now() err := next(c) log.Printf("took %v", time.Since(start)) return err }
} CODE_BLOCK:
func Timer(next marten.Handler) marten.Handler { return func(c *marten.Ctx) error { start := time.Now() err := next(c) log.Printf("took %v", time.Since(start)) return err }
} CODE_BLOCK:
func handler(c *marten.Ctx) error { // Path parameters id := c.Param("id") // Query parameters page := c.QueryInt("page") // JSON binding var user User c.Bind(&user) // Response helpers return c.OK(user)
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
func handler(c *marten.Ctx) error { // Path parameters id := c.Param("id") // Query parameters page := c.QueryInt("page") // JSON binding var user User c.Bind(&user) // Response helpers return c.OK(user)
} CODE_BLOCK:
func handler(c *marten.Ctx) error { // Path parameters id := c.Param("id") // Query parameters page := c.QueryInt("page") // JSON binding var user User c.Bind(&user) // Response helpers return c.OK(user)
} CODE_BLOCK:
app.Use(middleware.StaticWithConfig(middleware.StaticConfig{ Root: "./public", Prefix: "/static", MaxAge: 3600, Browse: false,
})) Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
app.Use(middleware.StaticWithConfig(middleware.StaticConfig{ Root: "./public", Prefix: "/static", MaxAge: 3600, Browse: false,
})) CODE_BLOCK:
app.Use(middleware.StaticWithConfig(middleware.StaticConfig{ Root: "./public", Prefix: "/static", MaxAge: 3600, Browse: false,
})) CODE_BLOCK:
app := marten.New() app.Use( middleware.RequestID, middleware.Logger, middleware.Recover, middleware.CORS(middleware.DefaultCORSConfig()), middleware.RateLimit(middleware.RateLimitConfig{ Max: 100, Window: time.Minute, }),
) api := app.Group("/api/v1")
api.GET("/users", listUsers)
api.POST("/users", createUser, authMiddleware) Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
app := marten.New() app.Use( middleware.RequestID, middleware.Logger, middleware.Recover, middleware.CORS(middleware.DefaultCORSConfig()), middleware.RateLimit(middleware.RateLimitConfig{ Max: 100, Window: time.Minute, }),
) api := app.Group("/api/v1")
api.GET("/users", listUsers)
api.POST("/users", createUser, authMiddleware) CODE_BLOCK:
app := marten.New() app.Use( middleware.RequestID, middleware.Logger, middleware.Recover, middleware.CORS(middleware.DefaultCORSConfig()), middleware.RateLimit(middleware.RateLimitConfig{ Max: 100, Window: time.Minute, }),
) api := app.Group("/api/v1")
api.GET("/users", listUsers)
api.POST("/users", createUser, authMiddleware) CODE_BLOCK:
// API routes
app.GET("/api/users", listUsers) // Serve static files
app.Use(middleware.Static("./dist")) // SPA fallback
app.NotFound(func(c *marten.Ctx) error { if strings.HasPrefix(c.Path(), "/api/") { return c.NotFound("endpoint not found") } // Serve index.html for client-side routing return c.HTML(200, indexHTML)
}) Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
// API routes
app.GET("/api/users", listUsers) // Serve static files
app.Use(middleware.Static("./dist")) // SPA fallback
app.NotFound(func(c *marten.Ctx) error { if strings.HasPrefix(c.Path(), "/api/") { return c.NotFound("endpoint not found") } // Serve index.html for client-side routing return c.HTML(200, indexHTML)
}) CODE_BLOCK:
// API routes
app.GET("/api/users", listUsers) // Serve static files
app.Use(middleware.Static("./dist")) // SPA fallback
app.NotFound(func(c *marten.Ctx) error { if strings.HasPrefix(c.Path(), "/api/") { return c.NotFound("endpoint not found") } // Serve index.html for client-side routing return c.HTML(200, indexHTML)
}) CODE_BLOCK:
go get github.com/gomarten/[email protected] Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
go get github.com/gomarten/[email protected] CODE_BLOCK:
go get github.com/gomarten/[email protected] - Unit tests for every component
- Integration tests for real-world workflows
- Stress tests with 1,000+ concurrent requests
- Edge cases and error conditions - ORM integration: Use database/sql directly
- Template engine: Use html/template from stdlib
- Validation library: Write your own or use a third-party package
- WebSocket support: Coming in a future release - WebSocket middleware
- Template rendering helpers
- Session management middleware
- Enhanced static file serving options - GitHub: https://github.com/gomarten/marten
- Documentation: https://gomarten.github.io/docs
- Discussions: https://github.com/gomarten/marten/discussions
how-totutorialguidedev.toaimlserverroutingrouterdockerdatabasegitgithub