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

Published: (January 20, 2026 at 12:58 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

Introduction

Most Go web frameworks pull in dozens of dependencies—Gin has 9 direct dependencies, Echo 11, Fiber 15—so your go.mod quickly looks like a phone book. Marten takes a different approach: it uses only what Go gives you (e.g., net/http, encoding/json). No external packages, no vendor lock‑in.

Complete API Example

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")
}

Router

Marten uses a radix‑tree router for efficient path matching. It supports:

  • Path parameters (:id)
  • Wildcards (*filepath)
  • Route groups
api := app.Group("/api/v1")
api.GET("/users", listUsers)
api.GET("/users/:id", getUser)
api.POST("/users", createUser)

Middleware

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

Built‑in Middleware

Marten ships with 14 built‑in middleware components:

  • Logger
  • Recover
  • CORS
  • RateLimit
  • BasicAuth
  • Timeout
  • Secure
  • BodyLimit
  • Compress
  • ETag
  • RequestID
  • Static
  • NoCache

Context (Ctx) Usage

Every request gets a Ctx object from a sync.Pool, reducing allocations and keeping memory usage low:

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 (v0.1.3)

The latest release adds static file serving with a full feature set:

app.Use(middleware.StaticWithConfig(middleware.StaticConfig{
    Root:   "./public",
    Prefix: "/static",
    MaxAge: 3600,
    Browse: false,
}))

Features include content‑type detection, HTTP caching (If-Modified-Since), directory browsing, and protection against directory‑traversal attacks.

Performance Benchmarks

Benchmarks against Gin, Echo, and Chi show Marten holding its own:

BenchmarkMartenGinEchoChi
Static Route1464 ns/op1336 ns/op1436 ns/op2202 ns/op
Param Route1564 ns/op1418 ns/op1472 ns/op2559 ns/op
JSON Response1755 ns/op2050 ns/op1835 ns/op1868 ns/op

Not the fastest, but competitive—and with zero dependencies.

Building Production‑Ready APIs

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)

SPA Fallback

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

Tests

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.

What Marten Doesn’t Provide

  • ORM integration – use database/sql directly
  • Template engine – use html/template from the stdlib
  • Validation library – write your own or import a third‑party package
  • WebSocket support – planned for a future release

Philosophy

If the standard library can do it, use the standard library. If you need more, add it yourself—still keeping the zero‑dependency constraint.

Future Releases

  • WebSocket middleware
  • Template rendering helpers
  • Session management middleware
  • Enhanced static file serving options

All with the same zero‑dependency philosophy.

Installation

go get github.com/gomarten/marten@v0.1.3

Check out the examples for CRUD APIs, JWT auth, file servers, and more.

Conclusion

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.

  • GitHub:
  • Documentation:
  • Discussions:
Back to Blog

Related posts

Read more »