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.
The Philosophy
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.
What It Looks Like
Here's a complete API in Marten:
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")
}
Clean. Familiar. No magic.
The Core Features
Fast Routing with Radix Trees
Marten uses a radix tree router for efficient path matching. It handles path parameters (:id), wildcards (*filepath), and route groups:
api := app.Group("/api/v1")
api.GET("/users", listUsers)
api.GET("/users/:id", getUser)
api.POST("/users", createUser)
Middleware That Makes Sense
Middleware in Marten is just a function that wraps a handler:
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
}
}
The framework includes 14 built-in middleware: Logger, Recover, CORS, RateLimit, BasicAuth, Timeout, Secure, BodyLimit, Compress, ETag, RequestID, Static, and NoCache.
Context Pooling
Every request gets a Ctx object from a sync.Pool. This reduces allocations and keeps memory usage low, even under heavy load:
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)
}
Static File Serving
The latest release (v0.1.3) adds static file serving with all the features you'd expect:
app.Use(middleware.StaticWithConfig(middleware.StaticConfig{
Root: "./public",
Prefix: "/static",
MaxAge: 3600,
Browse: false,
}))
Content-type detection, HTTP caching (If-Modified-Since), directory browsing, and security against directory traversal attacks—all built-in.
The Performance Story
How does a zero-dependency framework perform? Surprisingly well.
Benchmarks against Gin, Echo, and Chi show Marten holding its own:
| Benchmark | Marten | Gin | Echo | Chi |
|---|---|---|---|---|
| Static Route | 1464 ns/op | 1336 ns/op | 1436 ns/op | 2202 ns/op |
| Param Route | 1564 ns/op | 1418 ns/op | 1472 ns/op | 2559 ns/op |
| JSON Response | 1755 ns/op | 2050 ns/op | 1835 ns/op | 1868 ns/op |
Not the fastest, but competitive. And with zero dependencies.
Real-World Use Cases
Microservices
When you're building dozens of microservices, dependency bloat adds up. Marten keeps your Docker images small and your build times fast.
REST APIs
Build production-ready APIs with built-in middleware for logging, rate limiting, CORS, and authentication:
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)
Single Page Applications
Serve your SPA with automatic fallback to index.html for client-side routing:
// 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)
})
Learning and Teaching
Want to understand how web frameworks work? Read Marten's source. It's ~2,000 lines of readable Go code. No abstractions hiding abstractions.
The Testing Story
Marten v0.1.3 ships with 325 tests covering:
- Unit tests for every component
- Integration tests for real-world workflows
- Stress tests with 1,000+ concurrent requests
- Edge cases and error conditions
All tests pass with Go's race detector. No known memory leaks. Production-ready.
What's Missing (And Why)
Marten doesn't have:
-
ORM integration: Use
database/sqldirectly -
Template engine: Use
html/templatefrom stdlib - Validation library: Write your own or use a third-party package
- WebSocket support: Coming in a future release
The philosophy is simple: if the standard library can do it, use the standard library. If you need more, add it yourself.
The Roadmap
Future releases will add:
- WebSocket middleware
- Template rendering helpers
- Session management middleware
- Enhanced static file serving options
But always with the same constraint: zero dependencies.
Try It Yourself
go get github.com/gomarten/marten@v0.1.3
Check out the examples for CRUD APIs, JWT auth, file servers, and more.
The Takeaway
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.
Links:
- GitHub: https://github.com/gomarten/marten
- Documentation: https://gomarten.github.io/docs
- Discussions: https://github.com/gomarten/marten/discussions
Top comments (0)