NotNet

August 29, 2026 ยท View on GitHub

A lightweight HTTP router written in Go.

Release Go Version GoDoc Tests Go Report Card codecov License: MIT

NotNet is a lightweight, high-performance, and ergonomic routing framework for Go. It allows you to quickly structure RESTful HTTP services with an expressive API, built-in middleware, and custom group routing.

ChatGPT Image Apr 23, 2026, 08_48_29 PM

Features

  • Expressive Routing: Simple .GET(), .POST(), .PUT(), etc., for robust route handling.
  • Route Groups: Group endpoints with shared prefixes and nested middleware stacks.
  • Path Parameters: Extract dynamic URL parameters like /users/:id out of the box.
  • Pre-packaged Middleware:
    • Logger: Detailed request logging.
    • Recovery: Auto-recovery from application panics.
    • CORS: Simplifies Cross-Origin Resource Sharing.
    • RateLimit: Prevents abuse through IP-based request limits.
    • AuthRequired: Simple stubbed bearer token checking.
    • RequestID: Track requests universally with trace IDs.
  • Extensible Request/Response Engine: Bind JSON payloads quickly, return robust JSON, HTML or strings using dedicated Response/Request wrappers.
  • Server-Sent Events (SSE): Built-in support for streaming real-time updates to clients with res.SSE() and res.SendEvent().

Installation

Run this in your Go module project to get notnet:

go get github.com/nottechdm/notnet

Quick Start

Creating an API in NotNet takes only a few lines:

package main

import (
	"log"
	"github.com/nottechdm/notnet/pkg/notnet"
)

func main() {
	app := notnet.New(nil)

	// Attach Universal Middleware
	app.Use(notnet.Logger())
	app.Use(notnet.Recovery())

	// Simple Route
	app.GET("/ping", func(req *notnet.Request, res *notnet.Response) error {
		return res.String(200, "pong")
	})

	// Dynamic Path Parameters
	app.GET("/users/:id", func(req *notnet.Request, res *notnet.Response) error {
		id := req.Param("id")
		return res.JSON(200, map[string]string{"user_id": id})
	})

	// Read JSON Payload
	app.POST("/api/data", func(req *notnet.Request, res *notnet.Response) error {
		var payload map[string]interface{}
		if err := req.BindJSON(&payload); err != nil {
			return res.JSON(400, map[string]string{"error": "invalid json"})
		}
		
		payload["received"] = true
		return res.JSON(201, payload)
	})

	log.Println("Server running on :8080")
	log.Fatal(app.Listen(":8080"))
}

Route Groups & Custom Middleware

You can isolate your authentication handlers from public endpoints easily by using groups:

// Public
app.GET("/status", func(req *notnet.Request, res *notnet.Response) error {
	return res.JSON(200, map[string]string{"status": "ok"})
})

// Protected API V1 Group 
api := app.Group("/api/v1", notnet.AuthRequired())

api.GET("/dashboard", func(req *notnet.Request, res *notnet.Response) error {
	return res.JSON(200, map[string]string{"msg": "You have access!"})
})

Custom Middleware

You can create your own middleware by returning a notnet.HandlerFunc. The function receives the request and response, can run logic before or after calling req.Next(), and can stop the chain by returning an error or writing a response directly.

func CustomLogger() notnet.HandlerFunc {
    return func(req *notnet.Request, res *notnet.Response) error {
        start := time.Now()
        err := req.Next()

        log.Printf("[%s] %s %s - %s",
            req.Method(),
            req.Path(),
            req.RemoteAddr(),
            time.Since(start),
        )

        return err
    }
}

app.Use(CustomLogger())

A common pattern is to validate headers, attach request metadata, or log timing for each request before continuing to the next handler:

func RequireAPIKey() notnet.HandlerFunc {
    return func(req *notnet.Request, res *notnet.Response) error {
        if req.Header("X-API-Key") == "" {
            return res.JSON(401, map[string]string{"error": "missing api key"})
        }

        return req.Next()
    }
}

app.Use(RequireAPIKey())

Custom Handlers

NotNet comes with default 404, error, and panic handling, but you can override them with your unique application responses at any time:

app.SetNotFoundHandler(func(req *notnet.Request, res *notnet.Response) {
    res.JSON(404, map[string]string{
        "error": "This page does not exist",
        "path":  req.Path(),
    })
})

Server-Sent Events (SSE)

NotNet provides native support for Server-Sent Events, allowing you to stream real-time updates to your clients effortlessly:

app.GET("/events", func(req *notnet.Request, res *notnet.Response) error {
    // Set up the SSE connection (sets headers and flushes)
    res.SSE()

    ticker := time.NewTicker(1 * time.Second)
    defer ticker.Stop()

    for i := 0; i < 5; i++ {
        <-ticker.C
        // Send a named event with data (can be string, []byte, or any JSON-marshalable object)
        res.SendEvent("message", map[string]interface{}{
            "id":   i,
            "text": "Hello from NotNet!",
        })
    }

    return nil
})

License

This project is licensed under the MIT License.