DEV Community

Jack Prescott
Jack Prescott

Posted on

Building a minimal Go framework in public (v0.1.3)

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")
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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,
}))
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
})
Enter fullscreen mode Exit fullscreen mode

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/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

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
Enter fullscreen mode Exit fullscreen mode

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:

Top comments (0)