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

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • MyrinNew
    Senior Member
    • Feb 2024
    • 5168

    #1

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







    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.Sta ticConfig{
    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:


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







    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:



    More...
Working...