navaros

package module
v1.17.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 26, 2026 License: MIT Imports: 20 Imported by: 5

README

Navaros

A lightweight, flexible HTTP router for Go. Build fast web applications with powerful route matching, middleware support, and composable handlers.

Go Reference Go Report Card CI Coverage GitHub release License Sponsor

Table of Contents

Features

  • 🚀 High Performance - Efficient route matching and context pooling for minimal overhead
  • 🔌 Middleware Support - Composable middleware chain for request/response processing
  • 🎯 Powerful Patterns - Flexible routing with parameters, wildcards, regex constraints, and modifiers
  • 📦 Body Handling - Streaming request/response bodies, with buffering and marshaling via middleware
  • 🗂️ Multiple Formats - Built-in middleware for JSON, MessagePack, and Protocol Buffers
  • 🛡️ Panic Recovery - Built-in handler panic recovery prevents crashes
  • 📋 Unified Context - Single context object for request, response, params, and cancellation
  • 🔄 Context Cancellation - Implements Go's context interface for cancellable operations
  • 📁 Nestable Routers - Modular route organization with sub-routers
  • Minimal Dependencies - Core router uses only Go standard library
  • 🧩 Extensible - Simple interfaces for custom middleware and handlers

Installation

go get github.com/RobertWHurst/navaros

Quick Start

package main

import (
	"net/http"
	"github.com/RobertWHurst/navaros"
)

func main() {
	router := navaros.NewRouter()

	router.Get("/hello/:name", func(ctx *navaros.Context) {
		name := ctx.Params().Get("name")
		ctx.Body = "Hello, " + name + "!"
	})

	http.ListenAndServe(":8080", router)
}

Core Concepts

Context

The Context is the central object passed to every handler and middleware. It provides unified access to the HTTP request, response, route parameters, and implements Go's context.Context interface for cancellation support.

The context gives handlers everything they need to process a request and build a response. You can access request data like headers and query parameters, set response data like status codes and body content, and store per-request values for passing data between middleware and handlers.

Storage: Use Set and Get to store per-request data. This is useful for passing information between middleware and handlers, such as authenticated user details or request IDs.

router.Use(func(ctx *navaros.Context) {
	ctx.Set("requestID", generateID())
	ctx.Next()
})

router.Get("/user/:id", func(ctx *navaros.Context) {
	requestID := ctx.MustGet("requestID").(string)
	userID := ctx.Params().Get("id")
	ctx.Body = "User " + userID + " (Request: " + requestID + ")"
})
Middleware

Middleware functions execute before and after handlers in a composable chain. They can inspect and modify the context, perform authentication, log requests, or short-circuit the chain by not calling Next().

Middleware runs in the order it's registered. Each middleware can perform work before calling Next() (pre-processing) and after Next() returns (post-processing). This allows middleware to wrap handler execution with setup and teardown logic.

You can register middleware globally to run for all routes, or scope it to specific path patterns. When you provide a path to Use(), it automatically matches that path and all sub-paths - no need to add /** explicitly.

router.Use(func(ctx *navaros.Context) {
	start := time.Now()
	ctx.Next()
	duration := time.Since(start)
	log.Printf("%s %s - %dms", ctx.Method(), ctx.Path(), duration.Milliseconds())
})

router.Use("/api", func(ctx *navaros.Context) {
	token := ctx.RequestHeaders().Get("Authorization")
	if token == "" {
		ctx.Status = http.StatusUnauthorized
		ctx.Body = "Unauthorized"
		return
	}
	ctx.Next()
})

router.Get("/api/users", func(ctx *navaros.Context) {
	ctx.Body = "User list"
})
Context Lifecycle

Important: Context objects are pooled and reused for performance. When a handler returns, its context is immediately returned to the pool and may be reused for a different request. This means handlers must block until all operations using the context are complete.

If you spawn a goroutine or set up a callback that references the context after the handler returns, those operations will fail with an error: "context cannot be used after handler returns - handlers must block until all operations complete".

Wrong - Don't do this:

// ❌ This will fail - goroutine uses context after handler returns
router.Get("/async", func(ctx *navaros.Context) {
	go func() {
		time.Sleep(time.Second)
		ctx.Body = "Done" // ERROR: context already returned to pool
	}()
	// Handler returns immediately - context is freed
})

Right - Handler blocks:

// ✓ Correct - handler blocks until operation completes
router.Get("/stream", func(ctx *navaros.Context) {
	ctx.Headers.Set("Content-Type", "text/event-stream")
	
	ticker := time.NewTicker(time.Second)
	defer ticker.Stop()
	
	// Handler blocks here, keeping context alive
	for {
		select {
		case <-ctx.Done():
			return // Client disconnected, clean exit
		case <-ticker.C:
			ctx.Write([]byte("data: ping\n\n"))
			ctx.Flush()
		}
	}
})

Handlers only need to block as long as they need to use the context. For typical request/response handlers, this means they return immediately after setting the response. For streaming responses, they block until the stream completes. The key rule: don't return from the handler while operations that use the context are still pending.

Routing

Route Patterns

Navaros supports fairly powerful route patterns. The following is a list of supported pattern segment types.

  • Static - /a/b/c - Matches the exact path
  • Wildcard - /a/*/c - Pattern segments with a single * match any path segment
  • Dynamic - /a/:b/c - Pattern segments prefixed with : capture values. /users/:id matches /users/123, and ctx.Params().Get("id") returns "123"

Pattern segments can also be suffixed with additional modifiers.

  • ? - Optional - /a/:b?/c - Matches /a/c and /a/1/c
  • * - Greedy - /a/:b*/c - Matches /a/c and /a/1/2/3/c
  • + - One or more - /a/:b+/c - Matches /a/1/c and /a/1/2/3/c but not /a/c

You can also provide a regular expression to restrict matches for a pattern segment.

  • /a/:b(\\d+)/c - Matches /a/1/c and /a/2/c but not /a/b/c

You can escape any of the special characters used by these operators by prefixing them with a \\.

  • /a/\\:b/c - Matches /a/:b/c

And all of these can be combined.

  • /a/:b(\\d+)/*?/(d|e)+ - Matches /a/1/d, /a/1/e, /a/2/c/d/e/f/g, and /a/3/1/d but not /a/b/c, /a/1, or /a/1/c/f

Register more specific patterns before general ones to ensure correct matching.

router.Get("/users/:id(\\d+)", func(ctx *navaros.Context) {
	ctx.Body = "Numeric user ID: " + ctx.Params().Get("id")
})

router.Get("/users/:slug", func(ctx *navaros.Context) {
	ctx.Body = "User slug: " + ctx.Params().Get("slug")
})

router.Get("/files/:path+", func(ctx *navaros.Context) {
	ctx.Body = "File path: " + ctx.Params().Get("path")
})
HTTP Methods

Routes can be registered for specific HTTP methods using method-specific functions like Get(), Post(), Put(), Patch(), Delete(), Options(), and Head(). Each function takes a pattern and one or more handlers to execute when both the pattern and method match.

The All() method registers handlers that run for any HTTP method on the given pattern. This is useful for cross-cutting concerns like logging middleware that should run regardless of the request method, or for APIs that handle multiple methods on the same endpoint.

Routes are matched in registration order within each method. If you register both method-specific and All() handlers for the same pattern, all matching handlers will run in the order they were registered.

router.All("/api/users", func(ctx *navaros.Context) {
	log.Printf("%s /api/users", ctx.Method())
	ctx.Next()
})

router.Get("/api/users", func(ctx *navaros.Context) {
	ctx.Body = []User{{Name: "Alice"}, {Name: "Bob"}}
})

router.Post("/api/users", func(ctx *navaros.Context) {
	var user User
	ctx.UnmarshalRequestBody(&user)
	ctx.Status = http.StatusCreated
	ctx.Body = user
})
Route Parameters

Parameters are captured from the request path based on the route pattern. They're accessed through the context's Params() method, which returns a map-like object.

Parameters are always strings since they come from the URL path. If you need other types, parse the parameter value in your handler.

router.Get("/users/:id", func(ctx *navaros.Context) {
	userID := ctx.Params().Get("id")
	
	id, err := strconv.Atoi(userID)
	if err != nil {
		ctx.Status = http.StatusBadRequest
		ctx.Body = "Invalid user ID"
		return
	}
	
	ctx.Body = fmt.Sprintf("User ID: %d", id)
})

Request Handling

Accessing Request Data

The context provides direct access to all request data. You can read headers, query parameters, cookies, the request body, and URL components.

Headers are accessed as a standard http.Header map. You can read individual headers or iterate over all of them.

Query parameters come from the URL's query string. They're accessed as a url.Values map, which handles multiple values for the same parameter.

Cookies can be read by name. The context returns an *http.Cookie and an error if the cookie doesn't exist.

URL components like the protocol, host, and path are available directly through the context.

TLS information is available if the request came over HTTPS. This includes certificate details and negotiated protocol versions.

router.Get("/info", func(ctx *navaros.Context) {
	userAgent := ctx.RequestHeaders().Get("User-Agent")
	search := ctx.Query().Get("search")
	
	sessionCookie, err := ctx.RequestCookie("session")
	if err == nil {
		log.Printf("Session: %s", sessionCookie.Value)
	}
	
	if tls := ctx.RequestTLS(); tls != nil {
		ctx.Body = fmt.Sprintf("Secure connection from %s searching for %s", userAgent, search)
	} else {
		ctx.Body = "Insecure connection"
	}
})
Request Body

Most APIs use ctx.UnmarshalRequestBody(&value) with middleware like JSON, MessagePack, or Protocol Buffers. The middleware reads and decodes the body for you.

For large uploads like files, use ctx.RequestBodyReader() to stream the body without loading it all into memory. The reader respects MaxRequestBodySize limits (default 10MB) to prevent memory exhaustion. You can change the limit globally with navaros.MaxRequestBodySize or per-request with ctx.MaxRequestBodySize. Set to -1 to disable the limit.

You can set custom unmarshallers with ctx.SetRequestBodyUnmarshaller() for other content types.

import "github.com/RobertWHurst/navaros/middleware/json"

router.Use(json.Middleware(nil))

router.Post("/users", func(ctx *navaros.Context) {
	var user User
	if err := ctx.UnmarshalRequestBody(&user); err != nil {
		ctx.Status = http.StatusBadRequest
		ctx.Body = "Invalid request body"
		return
	}
	
	ctx.Status = http.StatusCreated
	ctx.Body = user
})

// Allow larger uploads for this route
router.Post("/upload", func(ctx *navaros.Context) {
	ctx.MaxRequestBodySize = 100 * 1024 * 1024 // 100MB
	
	reader := ctx.RequestBodyReader()
	defer reader.Close()
	
	file, _ := os.Create("/tmp/upload")
	defer file.Close()
	
	io.Copy(file, reader)
	ctx.Status = http.StatusOK
	ctx.Body = "File uploaded"
})

Response Handling

Setting Response Data

Handlers build responses by setting fields on the context. The status code, headers, cookies, and body are all set directly on the context.

Status codes are set as integers. If you don't set a status code, Navaros will infer one based on the response body: 200 if there's a body, 404 if there's no body.

Headers are set as a standard http.Header map. Headers set on the context are written to the response before the status code.

Cookies are added as http.Cookie pointers. They're written to the response as Set-Cookie headers.

router.Get("/set-cookie", func(ctx *navaros.Context) {
	ctx.Status = http.StatusOK
	ctx.Headers.Set("Content-Type", "text/plain")
	ctx.Cookies = append(ctx.Cookies, &http.Cookie{
		Name:  "session",
		Value: "abc123",
		Path:  "/",
	})
	ctx.Body = "Cookie set"
})
Response Body

The response body can be set in several ways depending on your needs. Navaros handles the details of writing the body to the client based on what type of value you provide.

Simple bodies like strings and byte slices are written directly to the response with no additional processing. Set ctx.Body = "Hello World" or ctx.Body = []byte{...} and Navaros writes it as-is. This is the fastest approach for static content or when you've already formatted the response.

Structured data can be set as any Go value like ctx.Body = User{Name: "Alice"}. Middleware will marshal it to the appropriate format - the JSON middleware marshals values to JSON by setting a marshaller with ctx.SetResponseBodyMarshaller(). This is the easiest approach for APIs since you just set the body to your response struct and let middleware handle encoding.

Streaming responses use ctx.Write() to send bytes directly to the client without buffering. The context implements io.WriteCloser, making it compatible with standard library functions like io.Copy(ctx, reader) for streaming from any source. This is essential for large responses like file downloads or server-sent events that don't fit in memory. Your handler must block until streaming completes.

io.Reader bodies like ctx.Body = file allow you to set any reader as the response body. Navaros will copy from the reader to the response, closing it if it implements io.Closer. This is useful for proxying responses or serving files without loading them entirely into memory.

You can set custom marshallers with ctx.SetResponseBodyMarshaller() for other content types or special encoding requirements. The marshaller function receives your body value and returns an io.Reader that Navaros will copy to the response.

import "github.com/RobertWHurst/navaros/middleware/json"

router.Use(json.Middleware(nil))

router.Get("/string", func(ctx *navaros.Context) {
	ctx.Body = "Hello World"
})

router.Get("/json", func(ctx *navaros.Context) {
	ctx.Body = User{Name: "Alice", Email: "[email protected]"}
})

router.Get("/stream", func(ctx *navaros.Context) {
	for i := 0; i < 10; i++ {
		ctx.Write([]byte(fmt.Sprintf("Chunk %d\n", i)))
		ctx.Flush()
		time.Sleep(100 * time.Millisecond)
	}
})

router.Get("/file", func(ctx *navaros.Context) {
	file, _ := os.Open("/path/to/file.pdf")
	ctx.Headers.Set("Content-Type", "application/pdf")
	ctx.Body = file
})
Redirects

Redirects are created using the Redirect type. Set it as the response body and Navaros will handle the Location header and status code automatically.

Relative redirects are resolved against the current request URL. Absolute redirects are used as-is.

router.Get("/old-path", func(ctx *navaros.Context) {
	ctx.Body = navaros.Redirect{To: "/new-path"}
})

router.Get("/login", func(ctx *navaros.Context) {
	ctx.Status = http.StatusMovedPermanently
	ctx.Body = navaros.Redirect{To: "https://auth.example.com/login"}
})

Built-in Middleware

JSON Middleware

The JSON middleware automatically marshals and unmarshals JSON request and response bodies. It sets up the context's unmarshal and marshal functions to handle JSON encoding.

For requests with Content-Type: application/json, it reads the body and provides an unmarshal function that decodes JSON into Go values. For responses, it marshals any non-reader body value to JSON before writing it.

Pass nil for default configuration, or use &json.Options{} to customize:

  • DisableRequestBodyUnmarshaller - Skip setting up request unmarshalling
  • DisableResponseBodyMarshaller - Skip setting up response marshalling
import "github.com/RobertWHurst/navaros/middleware/json"

// Default configuration
router.Use(json.Middleware(nil))

// Custom configuration - response marshalling only
router.Use(json.Middleware(&json.Options{
	DisableRequestBodyUnmarshaller: true,
}))

router.Post("/api/users", func(ctx *navaros.Context) {
	var user User
	if err := ctx.UnmarshalRequestBody(&user); err != nil {
		ctx.Status = http.StatusBadRequest
		ctx.Body = "Invalid JSON"
		return
	}
	
	ctx.Status = http.StatusCreated
	ctx.Body = user
})
MessagePack Middleware

The MessagePack middleware provides binary serialization support using MessagePack format. It automatically handles request unmarshalling and response marshalling for Content-Type: application/msgpack.

MessagePack is more compact and faster than JSON, making it ideal for high-performance APIs or bandwidth-constrained environments.

Pass nil for default configuration, or use &msgpack.Options{} to customize:

  • DisableRequestBodyUnmarshaller - Skip setting up request unmarshalling
  • DisableResponseBodyMarshaller - Skip setting up response marshalling
import "github.com/RobertWHurst/navaros/middleware/msgpack"

router.Use(msgpack.Middleware(nil))

router.Post("/api/users", func(ctx *navaros.Context) {
	var user User
	if err := ctx.UnmarshalRequestBody(&user); err != nil {
		ctx.Status = http.StatusBadRequest
		ctx.Body = msgpack.Error("Invalid MessagePack")
		return
	}
	
	ctx.Status = http.StatusCreated
	ctx.Body = user
})

Like the JSON middleware, MessagePack middleware supports special response types:

  • msgpack.Error - Returns {"error": "message"}
  • msgpack.FieldError - Returns validation error format
  • msgpack.M - Shorthand for map[string]any
Protocol Buffers Middleware

The Protocol Buffers middleware provides efficient binary serialization using Protocol Buffers. It handles Content-Type: application/protobuf.

Protocol Buffers require you to define .proto schemas and generate Go code with protoc. The middleware works with any proto.Message implementation.

Pass nil for default configuration, or use &protobuf.Options{} to customize:

  • DisableRequestBodyUnmarshaller - Skip setting up request unmarshalling
  • DisableResponseBodyMarshaller - Skip setting up response marshalling
import (
	"github.com/RobertWHurst/navaros/middleware/protobuf"
	"your-project/api/userpb"
)

router.Use(protobuf.Middleware(nil))

router.Post("/api/users", func(ctx *navaros.Context) {
	var req userpb.CreateUserRequest
	if err := ctx.UnmarshalRequestBody(&req); err != nil {
		ctx.Status = http.StatusBadRequest
		ctx.Body = "Invalid protobuf"
		return
	}
	
	ctx.Status = http.StatusCreated
	ctx.Body = &userpb.CreateUserResponse{
		Id:   123,
		Name: req.Name,
	}
})

The middleware automatically sets Content-Type headers and validates that request/response bodies implement proto.Message.

Set Middleware Variants

The Set middleware family lets you store values on the context as middleware. This is useful for setting up common values that multiple handlers need. Each variant takes a key and value/function as parameters.

set stores static values on every request - takes a key and value.

setfn calls a function on every request and stores the result - takes a key and function. This is useful for values that change per request, like request IDs or timestamps.

setvalue dereferences a pointer and stores the value - takes a key and pointer. This is useful when the value might change between requests but you want to capture the current value.

import (
	"github.com/RobertWHurst/navaros/middleware/set"
	"github.com/RobertWHurst/navaros/middleware/setfn"
	"github.com/RobertWHurst/navaros/middleware/setvalue"
)

router.Use(set.Middleware("version", "1.0.0"))

router.Use(setfn.Middleware("requestID", func() string {
	return uuid.New().String()
}))

maxItems := 100
router.Use(setvalue.Middleware("maxItems", &maxItems))

// Later, maxItems can be changed and setvalue captures the current value per request
maxItems = 200

router.Get("/info", func(ctx *navaros.Context) {
	version := ctx.MustGet("version").(string)
	requestID := ctx.MustGet("requestID").(string)
	maxItems := ctx.MustGet("maxItems").(int) // Gets the dereferenced int value
	
	ctx.Body = fmt.Sprintf("v%s (request: %s, max: %d)", version, requestID, maxItems)
})

Advanced Usage

Nested Routers

Routers can be nested to organize routes modularly. Create separate routers for different parts of your application, then mount them on the main router.

Sub-routers inherit middleware from their parent, and you can add sub-router-specific middleware. This allows you to build up middleware stacks that apply to specific sections of your application.

apiRouter := navaros.NewRouter()
apiRouter.Use(authMiddleware)

apiRouter.Get("/users", func(ctx *navaros.Context) {
	ctx.Body = []User{{Name: "Alice"}}
})

apiRouter.Get("/posts", func(ctx *navaros.Context) {
	ctx.Body = []Post{{Title: "Hello"}}
})

mainRouter := navaros.NewRouter()
mainRouter.Use(loggingMiddleware)
mainRouter.Use("/api", apiRouter)

mainRouter.Get("/", func(ctx *navaros.Context) {
	ctx.Body = "Welcome"
})
Authentication

Authentication is typically implemented as middleware. The middleware runs before handlers, checks credentials, and either continues the chain or returns an error response.

Pattern-specific middleware lets you protect specific routes or route groups. Store authenticated user details on the context so handlers can access them.

func authMiddleware(ctx *navaros.Context) {
	token := ctx.RequestHeaders().Get("Authorization")
	if token == "" {
		ctx.Status = http.StatusUnauthorized
		ctx.Body = "Unauthorized"
		return
	}
	
	user, err := validateToken(token)
	if err != nil {
		ctx.Status = http.StatusForbidden
		ctx.Body = "Forbidden"
		return
	}
	
	ctx.Set("user", user)
	ctx.Next()
}

router.Use("/api", authMiddleware)

router.Get("/api/profile", func(ctx *navaros.Context) {
	user := ctx.MustGet("user").(*User)
	ctx.Body = user
})
Error Handling

Navaros automatically recovers from panics in handlers. When a panic occurs, the context's Error and ErrorStack fields are set, and a 500 status code is returned.

You can implement custom error handling middleware that runs after handlers, checks the Error field, and returns appropriate error responses. This lets you control error formatting and logging.

func errorHandler(ctx *navaros.Context) {
	ctx.Next()
	
	if ctx.Error != nil {
		log.Printf("Handler error: %v\n%s", ctx.Error, ctx.ErrorStack)
		ctx.Status = http.StatusInternalServerError
		ctx.Body = map[string]string{
			"error": "Internal server error",
		}
	}
}

router.Use(errorHandler)

router.Get("/panic", func(ctx *navaros.Context) {
	panic("something went wrong")
})
Custom Middleware

Middleware is any function that takes a context pointer. It can perform work before calling Next(), after calling Next(), or both.

To short-circuit the chain, don't call Next(). This is useful for middleware that handles certain requests completely, like authentication middleware that returns 401 responses.

Middleware registered earlier runs first. Global middleware runs before pattern-specific middleware.

func corsMiddleware(ctx *navaros.Context) {
	ctx.Headers.Set("Access-Control-Allow-Origin", "*")
	ctx.Next()
}

func rateLimitMiddleware(ctx *navaros.Context) {
	if !checkRateLimit(ctx.RequestRemoteAddress()) {
		ctx.Status = http.StatusTooManyRequests
		ctx.Body = "Too many requests"
		return
	}
	ctx.Next()
}

router.Use(corsMiddleware)
router.Use(rateLimitMiddleware)

router.Get("/api/data", func(ctx *navaros.Context) {
	ctx.Body = "Data"
})

Integration with HTTP Servers

Navaros implements the standard http.Handler interface, so it works seamlessly with any Go HTTP server or framework.

For the standard net/http server, pass the router to http.ListenAndServe or register it with http.Handle.

Navaros also works with third-party frameworks. Most frameworks provide a way to wrap an http.Handler, letting you use Navaros as the routing layer.

router := navaros.NewRouter()

router.Get("/hello", func(ctx *navaros.Context) {
	ctx.Body = "Hello World"
})

http.ListenAndServe(":8080", router)

Microservices

Navaros works with Zephyr, a microservice framework that routes HTTP requests over message transports like NATS. This lets you write services as regular HTTP handlers while getting service discovery and routing.

Note: The examples below require a NATS server. See NATS documentation for installation.

Public vs Private Routes

Navaros distinguishes between public and private routes using Public*() methods:

  • Public routes (PublicGet, PublicPost, etc.) - Accessible through a gateway from external clients
  • Private routes (Get, Post, etc.) - Only accessible via service-to-service communication
router := navaros.NewRouter()

// Public route - accessible via gateway
router.PublicGet("/api/users", func(ctx *navaros.Context) {
    ctx.Body = []User{{Name: "Alice"}}
})

// Private route - only accessible to other services
router.Get("/internal/stats", func(ctx *navaros.Context) {
    ctx.Body = getInternalStats()
})
Gateway Pattern

A Zephyr gateway sits at the edge of your service network, routing external HTTP requests to the appropriate services based on their registered routes.

// Gateway service
conn, _ := nats.Connect("nats://localhost:4222")
gateway := zephyr.NewGateway("api-gateway", natstransport.New(conn))
gateway.Start()

http.ListenAndServe(":8080", gateway)
Creating Services

Services register themselves with the gateway and handle requests. When using Navaros, public routes are automatically discovered and registered.

// User service
router := navaros.NewRouter()
router.Use(json.Middleware(nil))

router.PublicGet("/users", func(ctx *navaros.Context) {
    ctx.Body = []User{{ID: 1, Name: "Alice"}}
})

router.PublicPost("/users", func(ctx *navaros.Context) {
    var user User
    ctx.UnmarshalRequestBody(&user)
    ctx.Status = http.StatusCreated
    ctx.Body = user
})

conn, _ := nats.Connect("nats://localhost:4222")
service := zephyr.NewService("user-service", natstransport.New(conn), router)
service.Start()
Service-to-Service Communication

Services can call each other directly using Zephyr's client, which can access both public and private routes.

// Order service calling user service
conn, _ := nats.Connect("nats://localhost:4222")
client := zephyr.NewClient(natstransport.New(conn))

router := navaros.NewRouter()

router.PublicPost("/orders", func(ctx *navaros.Context) {
    // Call user service to verify user exists
    resp, _ := client.Service("user-service").Get("/users/123")
    if resp.StatusCode == http.StatusNotFound {
        ctx.Status = http.StatusBadRequest
        ctx.Body = "User not found"
        return
    }
    
    // Create order...
    ctx.Status = http.StatusCreated
})

service := zephyr.NewService("order-service", natstransport.New(conn), router)
service.Start()
Client as Handler

Zephyr clients implement navaros.Handler, allowing you to proxy requests from one service to another directly in your routing:

client := zephyr.NewClient(natstransport.New(conn))
router := navaros.NewRouter()

// Proxy all /users requests to user-service
router.PublicGet("/users/**", client.Service("user-service"))

// This service now acts as a proxy/facade
service := zephyr.NewService("api-facade", natstransport.New(conn), router)
service.Start()
Benefits
  • Write services like HTTP servers - No special message handling code
  • Automatic service discovery - Services find each other through the transport
  • Public/private routes - Control what's exposed externally vs internally
  • Standard HTTP - Use all standard HTTP features (methods, headers, status codes)
  • Transport agnostic - Works with NATS, or implement custom transports

For complete documentation, see Zephyr on GitHub.

WebSockets

Navaros can be combined with Velaros, a WebSocket router that brings the same routing and middleware patterns to WebSocket connections. This lets you serve both HTTP and WebSocket traffic from the same server with a consistent API.

Note: Velaros cannot be used behind Zephyr services. WebSocket connections require persistent TCP connections that Zephyr's message-based architecture doesn't support. If you need WebSockets in a microservices architecture, either:

  • Use Velaros at a socket gateway that sits in front of your Zephyr services
  • Wait for the release of Eurus, the WebSocket equivalent of Zephyr that will provide service discovery and routing for WebSocket connections
Mounting WebSocket Routes

Velaros routers provide a Middleware() method that returns a Navaros handler, allowing you to mount WebSocket routes on any path:

import (
	"github.com/RobertWHurst/navaros"
	"github.com/RobertWHurst/navaros/middleware/json"
	"github.com/RobertWHurst/velaros"
	vjson "github.com/RobertWHurst/velaros/middleware/json"
)

// Create HTTP router
httpRouter := navaros.NewRouter()
httpRouter.Use(json.Middleware(nil))

// Create WebSocket router
wsRouter := velaros.NewRouter()
wsRouter.Use(vjson.Middleware())

// Add HTTP routes
httpRouter.Get("/api/users", func(ctx *navaros.Context) {
	ctx.Body = []User{{Name: "Alice"}}
})

// Add WebSocket routes
wsRouter.Bind("/chat/message", func(ctx *velaros.Context) {
	var msg ChatMessage
	ctx.Unmarshal(&msg)
	ctx.Reply(ChatResponse{Status: "received"})
})

// Mount WebSocket router using Middleware() method
httpRouter.Use("/ws", wsRouter.Middleware())

http.ListenAndServe(":8080", httpRouter)
Shared Patterns and Concepts

Both routers use identical pattern syntax and middleware concepts:

Routing Patterns:

// HTTP routing
httpRouter.Get("/users/:id", getUserHandler)
httpRouter.Post("/files/**", uploadHandler)

// WebSocket routing - same pattern syntax
wsRouter.Bind("/users/:id", getUserMessageHandler)
wsRouter.Bind("/files/**", fileMessageHandler)

Middleware:

// HTTP middleware
httpRouter.Use("/admin", func(ctx *navaros.Context) {
	if !authenticated(ctx) {
		ctx.Status = http.StatusUnauthorized
		return
	}
	ctx.Next()
})

// WebSocket middleware - same structure
wsRouter.Use("/admin", func(ctx *velaros.Context) {
	if !authenticated(ctx) {
		ctx.Send(ErrorResponse{Error: "unauthorized"})
		return
	}
	ctx.Next()
})

Context Storage:

Both routers provide context storage with similar semantics:

// HTTP: per-request storage (cleared after request completes)
httpRouter.Use(func(ctx *navaros.Context) {
	ctx.Set("requestID", generateID())
	ctx.Next()
})

// WebSocket: per-message storage (cleared after message processing completes)
wsRouter.Use(func(ctx *velaros.Context) {
	ctx.Set("messageID", generateID())
	ctx.Next()
})

// WebSocket: per-connection storage (persists for the connection lifetime)
wsRouter.UseOpen(func(ctx *velaros.Context) {
	ctx.SetOnSocket("sessionID", generateID())
})
Real-Time Applications

Velaros enables real-time features while Navaros handles REST APIs:

type ChatServer struct {
	httpRouter *navaros.Router
	wsRouter   *velaros.Router
	broadcast  chan ChatMessage
	clients    sync.Map
}

func NewChatServer() *ChatServer {
	s := &ChatServer{
		httpRouter: navaros.NewRouter(),
		wsRouter:   velaros.NewRouter(),
		broadcast:  make(chan ChatMessage, 100),
	}

	// Start broadcast handler
	go s.handleBroadcasts()

	// HTTP API for message history
	s.httpRouter.Get("/api/messages", func(ctx *navaros.Context) {
		ctx.Body = getMessageHistory()
	})

	// WebSocket connection setup
	s.wsRouter.UseOpen(func(ctx *velaros.Context) {
		socketID := ctx.SocketID()
		msgChan := make(chan ChatMessage, 10)
		s.clients.Store(socketID, msgChan)
	})

	s.wsRouter.UseClose(func(ctx *velaros.Context) {
		socketID := ctx.SocketID()
		if ch, ok := s.clients.LoadAndDelete(socketID); ok {
			close(ch.(chan ChatMessage))
		}
	})

	// Listen for broadcast messages and send to client
	s.wsRouter.Bind("/chat/listen", func(ctx *velaros.Context) {
		socketID := ctx.SocketID()
		msgChan, ok := s.clients.Load(socketID)
		if !ok {
			return
		}

		// Handler blocks - continuously send messages to client
		for {
			select {
			case msg := <-msgChan.(chan ChatMessage):
				if err := ctx.Send(msg); err != nil {
					return
				}
			case <-ctx.Done():
				return
			}
		}
	})

	// Receive message from client and broadcast
	s.wsRouter.Bind("/chat/send", func(ctx *velaros.Context) {
		var msg ChatMessage
		ctx.Unmarshal(&msg)

		// Send to broadcast channel
		s.broadcast <- msg

		ctx.Reply(ChatResponse{Status: "sent"})
	})

	s.httpRouter.Use("/ws", s.wsRouter.Middleware())

	return s
}

func (s *ChatServer) handleBroadcasts() {
	for msg := range s.broadcast {
		// Send to all connected clients
		s.clients.Range(func(key, value any) bool {
			if msgChan, ok := value.(chan ChatMessage); ok {
				select {
				case msgChan <- msg:
				default:
					// Channel full, skip this client
				}
			}
			return true
		})
	}
}

func (s *ChatServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	s.httpRouter.ServeHTTP(w, r)
}
WebSocket Message Routing

While HTTP uses request methods and paths, WebSocket uses message paths for routing. Messages contain a path field that determines which handler processes them:

// Client connects via HTTP upgrade
const ws = new WebSocket('ws://localhost:8080/ws');

// Once connected, send messages with paths
ws.send(JSON.stringify({
	path: '/chat/send',       // Routes to handler
	id: 'msg-123',            // For request/reply
	data: {                   // Message payload
		text: 'Hello!'
	}
}));

// Receive responses
ws.onmessage = (event) => {
	const msg = JSON.parse(event.data);
	console.log('Reply to', msg.id, ':', msg.data);
};
Benefits of Using Both
  • Consistent API - Same routing patterns, middleware structure, and context methods
  • Unified Server - Serve HTTP and WebSocket from one server on one port
  • Complementary Features - REST APIs for CRUD, WebSockets for real-time updates
  • Type-Safe Communication - Both support JSON, MessagePack, and Protocol Buffers

For complete Velaros documentation, see Velaros on GitHub.

Performance

Navaros is designed for high performance with minimal overhead.

Context pooling reuses context objects to reduce allocations. Contexts are reset and returned to a pool after each request.

Pre-built handler chains are constructed at registration time, not per-request. Each route's middleware and handler sequence is a linked list ready to execute.

Zero allocations in hot paths mean Navaros doesn't create garbage during request handling, reducing GC pressure.

Minimal overhead from simple, direct code paths. No reflection in request handling, no hidden costs.

Architecture

Navaros uses a simple, predictable architecture based on sequential pattern matching and middleware chains.

Routes are matched in the order they're registered. This makes routing behavior predictable and easy to reason about, though you should register more specific patterns before general ones.

The middleware chain is built at registration time and executed sequentially. Each middleware and handler gets the context, performs its work, and optionally calls the next function in the chain.

Context pooling provides performance without complexity. Contexts are reset and reused, keeping allocations low while maintaining simplicity.

Testing

Test your handlers using httptest from the standard library. Create a test request, record the response, and assert on the results.

import (
	"net/http"
	"net/http/httptest"
	"testing"
	
	"github.com/RobertWHurst/navaros"
)

func TestHandler(t *testing.T) {
	router := navaros.NewRouter()
	
	router.Get("/hello", func(ctx *navaros.Context) {
		ctx.Body = "Hello World"
	})
	
	req := httptest.NewRequest("GET", "/hello", nil)
	rec := httptest.NewRecorder()
	
	router.ServeHTTP(rec, req)
	
	if rec.Code != http.StatusOK {
		t.Errorf("expected 200, got %d", rec.Code)
	}
	if rec.Body.String() != "Hello World" {
		t.Errorf("expected 'Hello World', got '%s'", rec.Body.String())
	}
}

Run tests:

go test ./...
go test -cover ./...

Help Welcome

If you want to support this project by throwing me some coffee money, it's greatly appreciated.

sponsor

If you're interested in providing feedback or would like to contribute, please feel free to do so. I recommend first opening an issue expressing your feedback or intent to contribute a change, from there we can consider your feedback or guide your contribution efforts. Any and all help is greatly appreciated since this is an open source effort after all.

Thank you!

License

MIT License - see LICENSE for details.

Velaros - WebSocket router built as a companion to Navaros. Uses the same routing patterns and middleware concepts for WebSocket message handling.

Zephyr - Microservice framework that brings HTTP directly to your services. Works seamlessly with Navaros for service-to-service communication and gateway routing.

Documentation

Overview

Package navaros is a lightweight, flexible HTTP router for Go.

It provides powerful route matching, middleware support, and efficient request handling with zero external dependencies. Designed for simplicity and performance, Navaros implements the standard http.Handler interface and supports nested routers, parameterized routes, streaming bodies, and context cancellation.

Quick Start

router := navaros.NewRouter()

router.Get("/users/:id", func(ctx *navaros.Context) {
    id := ctx.Params().Get("id")
    ctx.Status = 200
    ctx.Body = map[string]string{"id": id}
})

http.ListenAndServe(":8080", router)

Features

- Sequential pattern matching with regex for dynamic segments - Middleware chain with Next() for pre/post processing - Parameter extraction with wildcards, regex, optional/greedy modifiers - JSON body parsing via middleware - Panic recovery and error handling - Context implements context.Context for cancellation - Nestable sub-routers for modular code - Streaming request/response bodies - No dependencies beyond stdlib

Middleware

Use middleware for auth, logging, body parsing:

router.Use(func(ctx *navaros.Context) {
    // Pre-handler logic
    ctx.Next()
    // Post-handler logic
})

Built-in JSON middleware: import "github.com/RobertWHurst/navaros/middleware/json"; router.Use(json.Middleware(nil))

Route Patterns

- Static: /users - Param: /users/:id - Wildcard: /files/* - Optional: /users/:id? - Regex: /users/:id(\\d+) - Combine: /api/:v(\\d+)/:action*/static

Specific patterns before general.

Context

Unified access to request/response/params:

ctx.Params().Get("id") ctx.UnmarshalRequestBody(&user) ctx.Status = 200 ctx.Body = user

Supports Set/Get for per-request data.

See https://pkg.go.dev/github.com/RobertWHurst/navaros for full API docs.

Index

Constants

This section is empty.

Variables

View Source
var MaxRequestBodySize int64 = 1024 * 1024 * 10 // 10MB

MaxRequestBodySize is the maximum size of a request body. Changing this value will affect all requests. Set to -1 to disable the limit. If MaxRequestBodySize is set on the context it will override this value. This setting is useful for preventing denial of service attacks. It is not recommended to set this value to -1 unless you know what you are doing!!!

View Source
var PrintHandlerErrors = false

Functions

func CtxDeleteParam added in v1.8.2

func CtxDeleteParam(ctx *Context, key string)

CtxDeleteParam allows tests to delete a parameter from the context.

func CtxFinalize

func CtxFinalize(ctx *Context)

CtxFinalize allows libraries to call the finalize method on a context. finalize figures out the final status code, headers, and body for the response. This is normally called by the router, but can be called by libraries which wish to extend or encapsulate the functionality of Navaros.

func CtxFree added in v1.8.0

func CtxFree(ctx *Context)

CtxFree allows libraries to free a context they created manually. This is important, DO NOT FORGET TO CALL THIS IF YOU CREATE A CONTEXT MANUALLY.

func CtxInhibitResponse

func CtxInhibitResponse(ctx *Context)

CtxInhibitResponse prevents the context from sending a response. This is useful for libraries which wish to handle the response themselves. For example, a upgraded websocket connection cannot have response headers written to it.

func CtxSetDeadline

func CtxSetDeadline(ctx *Context, deadline time.Time)

CtxSetDeadline sets a deadline for the context. This allows libraries to limit the amount of time a handler can take to process a request.

func CtxSetParam added in v1.8.2

func CtxSetParam(ctx *Context, key, value string)

CtxGetParam enables tests to set parameters on a context. This is needed because tests often use NewContext to create a context to use with the handler being tested. Because the context wasn't matched against a real path, it will have no parameters. This function allows tests to set parameters on the context.

func SetPrintHandlerErrors added in v1.5.2

func SetPrintHandlerErrors(enable bool)

SetPrintHandlerErrors toggles the printing of handler errors.

Types

type Context

type Context struct {
	Status  int
	Headers http.Header
	Cookies []*http.Cookie
	Body    any

	MaxRequestBodySize int64

	Error           error
	ErrorStack      string
	FinalError      error
	FinalErrorStack string
	// contains filtered or unexported fields
}

Context represents a request and response. Navaros handlers access the request and build the response through the context.

func NewContext

func NewContext(res http.ResponseWriter, req *http.Request, handlers ...any) *Context

NewContext creates a new Context from go's http.ResponseWriter and http.Request. It also takes a variadic list of handlers. This is useful for creating a new Context outside a router, and can be used by libraries which wish to extend or encapsulate the functionality of Navaros.

func NewContextWithNode

func NewContextWithNode(res http.ResponseWriter, req *http.Request, firstHandlerNode *HandlerNode) *Context

NewContextWithNode creates a new Context from go's http.ResponseWriter and http.Request. It also takes a HandlerNode - a link in a chain of handlers. This is useful for creating a new Context outside a router, and can be used by libraries which wish to extend or encapsulate the functionality of Navaros. For example, implementing a custom router.

func NewSubContextWithNode

func NewSubContextWithNode(ctx *Context, firstHandlerNode *HandlerNode) *Context

NewSubContextWithNode creates a new Context from an existing Context. This is useful when you want to create a new Context from an existing one, but with a different handler chain. Note that when the end of the sub context's handler chain is reached, the parent context's handler chain will continue.

func (*Context) Close added in v1.5.0

func (c *Context) Close() error

Close simply aliases Flush. It's implemented to satisfy the io.Closer interface.

func (*Context) Deadline

func (c *Context) Deadline() (time.Time, bool)

Deadline returns the deadline of the request. Deadline is part of the go context.Context interface. This method is thread-safe.

func (*Context) Delete added in v1.13.0

func (c *Context) Delete(key string)

Delete removes a value attached to the context with Set. This method is thread-safe.

func (*Context) Done

func (c *Context) Done() <-chan struct{}

Done added for compatibility with go's context.Context. Done is part of the go context.Context interface. Returns a channel that will be closed once the response has been finalized and sent to the client, or if if the request has been aborted. This method is thread-safe.

func (*Context) Err

func (c *Context) Err() error

Err returns the final error of the request. Will be nil if the request is still being served even if an error has occurred. Populated once the request is done. Err is part of the go context.Context interface. This method is thread-safe.

func (*Context) Flush

func (c *Context) Flush()

Flush sends any bytes buffered in the response body to the client. Buffering is controlled by go's http.ResponseWriter.

func (*Context) Get

func (c *Context) Get(key string) (any, bool)

Get retrieves a value attached to the context with Set. This method is thread-safe.

func (*Context) Method

func (c *Context) Method() HTTPMethod

Method returns the HTTP method of the request.

func (*Context) MustGet added in v1.12.0

func (c *Context) MustGet(key string) any

MustGet retrieves a value attached to the context with Set. It panics if the value does not exist. This method is thread-safe.

func (*Context) Next

func (c *Context) Next()

Next calls the next handler in the chain. This is useful for creating middleware style handlers that work on the context before and/or after the responding handler.

func (*Context) Params

func (c *Context) Params() RequestParams

Params returns the parameters of the request. These are defined by the route pattern used to bind each handler, and may be different for each time next is called. This method is thread-safe and returns a copy of the params map.

func (*Context) Path

func (c *Context) Path() string

Path returns the path of the request.

func (*Context) Protocol

func (c *Context) Protocol() string

Protocol returns the http protocol version of the request.

func (*Context) ProtocolMajor

func (c *Context) ProtocolMajor() int

ProtocolMajor returns the major number in http protocol version.

func (*Context) ProtocolMinor

func (c *Context) ProtocolMinor() int

ProtocolMinor returns the minor number in http protocol version.

func (*Context) Query

func (c *Context) Query() url.Values

Query returns the query parameters of the request.

func (*Context) Request

func (c *Context) Request() *http.Request

Request returns the underlying http.Request object.

func (*Context) RequestBodyReader

func (c *Context) RequestBodyReader() io.ReadCloser

RequestBodyReader returns a reader setup to read in the request body. This is useful for streaming the request body, or for middleware which decodes the request body. Without body handling middleware, the request body reader is the only way to access request body data.

func (*Context) RequestContentLength

func (c *Context) RequestContentLength() int64

RequestContentLength returns the length of the request body if provided by the client.

func (*Context) RequestCookie

func (c *Context) RequestCookie(name string) (*http.Cookie, error)

RequestCookie returns the value of a request cookie by name. Returns nil if the cookie does not exist.

func (*Context) RequestHeaders

func (c *Context) RequestHeaders() http.Header

RequestHeaders returns the request headers.

func (*Context) RequestHost

func (c *Context) RequestHost() string

RequestHost returns the host of the request. Useful for determining the source of the request.

func (*Context) RequestRawURI

func (c *Context) RequestRawURI() string

RequestRawURI returns the raw URI of the request. This will be the original value from the request headers.

func (*Context) RequestRemoteAddress

func (c *Context) RequestRemoteAddress() string

RequestRemoteAddress returns the remote address of the request. Useful for determining the source of the request.

func (*Context) RequestTLS

func (c *Context) RequestTLS() *tls.ConnectionState

RequestTLS returns the TLS connection state of the request if the request is using TLS.

func (*Context) RequestTrailers

func (c *Context) RequestTrailers() http.Header

RequestTrailers returns the trailing headers of the request if set.

func (*Context) RequestTransferEncoding

func (c *Context) RequestTransferEncoding() []string

RequestTransferEncoding returns the transfer encoding of the request

func (*Context) ResponseStatus added in v1.7.0

func (c *Context) ResponseStatus() int

ResponseStatus returns the HTTP status code that will be sent to the client given the state of the body and status properties. This is useful for middleware that needs to check what the status code will be before the response is finalized.

func (*Context) ResponseWriter

func (c *Context) ResponseWriter() http.ResponseWriter

ResponseWriter returns the underlying http.ResponseWriter object.

func (*Context) Set

func (c *Context) Set(key string, value any)

Set attaches a value to the context. It can later be retrieved with Get. This method is thread-safe.

func (*Context) SetRequestBodyReader

func (c *Context) SetRequestBodyReader(reader io.Reader)

SetRequestBodyReader Allows middleware to intercept the request body reader and replace it with their own. This is useful transformers that re-write the request body in a streaming fashion. It's also useful for transformers that re-encode the request body.

func (*Context) SetRequestBodyUnmarshaller

func (c *Context) SetRequestBodyUnmarshaller(unmarshaller func(ctx *Context, into any) error)

SetRequestBodyUnmarshaller sets the request body unmarshaller. Middleware that parses request bodies should call this method to set the unmarshaller.

func (*Context) SetResponseBodyMarshaller

func (c *Context) SetResponseBodyMarshaller(marshaller func(ctx *Context, from any) (io.Reader, error))

SetResponseBodyMarshaller sets the response body marshaller. Middleware that encodes response bodies should call this method to set the marshaller.

func (*Context) URL

func (c *Context) URL() *url.URL

URL returns the URL of the request.

func (*Context) UnmarshalRequestBody

func (c *Context) UnmarshalRequestBody(into any) error

UnmarshalRequestBody unmarshals the request body into a given value. Note that is method requires SetRequestBodyUnmarshaller to be called first. This likely is done by middleware for parsing request bodies.

func (*Context) Value

func (c *Context) Value(any) any

Value is a noop for compatibility with go's context.Context.

func (*Context) Write

func (c *Context) Write(bytes []byte) (int, error)

Write writes bytes to the response body. This is useful for streaming the response body, or for middleware which encodes the response body.

type ContextResponseWriter added in v1.5.2

type ContextResponseWriter struct {
	// contains filtered or unexported fields
}

func (*ContextResponseWriter) Header added in v1.5.2

func (c *ContextResponseWriter) Header() http.Header

func (*ContextResponseWriter) Hijack added in v1.14.4

func (*ContextResponseWriter) Write added in v1.5.2

func (c *ContextResponseWriter) Write(bytes []byte) (int, error)

func (*ContextResponseWriter) WriteHeader added in v1.5.2

func (c *ContextResponseWriter) WriteHeader(status int)

type HTTPMethod

type HTTPMethod string

HTTPMethod represents an HTTP method.

const (
	// All represents all HTTP methods.
	All HTTPMethod = "ALL"
	// Post represents the HTTP POST method.
	Post HTTPMethod = "POST"
	// Get represents the HTTP GET method.
	Get HTTPMethod = "GET"
	// Put represents the HTTP PUT method.
	Put HTTPMethod = "PUT"
	// Patch represents the HTTP PATCH method.
	Patch HTTPMethod = "PATCH"
	// Delete represents the HTTP DELETE method.
	Delete HTTPMethod = "DELETE"
	// Options represents the HTTP OPTIONS method.
	Options HTTPMethod = "OPTIONS"
	// Head represents the HTTP HEAD method.
	Head HTTPMethod = "HEAD"
)

func HTTPMethodFromString

func HTTPMethodFromString(method string) (HTTPMethod, error)

HTTPMethodFromString converts a string to an HTTPMethod. If the string is not a valid HTTP method, an error is returned.

type Handler

type Handler interface {
	Handle(ctx *Context)
}

Handler is a handler object interface. Any object that implements this interface can be used as a handler in a handler chain.

type HandlerFunc

type HandlerFunc func(ctx *Context)

HandlerFunc is a function that can be used as a handler with Navaros.

type HandlerNode

type HandlerNode struct {
	Method                  HTTPMethod
	Pattern                 *Pattern
	HandlersAndTransformers []any
	Next                    *HandlerNode
}

HandlerNode is used to build the handler chains used by the context. The router builds a chain from these objects then attaches them to the context. It then calls Next on the context to execute the chain.

type MetadataOption added in v1.17.0

type MetadataOption struct {
	// contains filtered or unexported fields
}

MetadataOption is a RouteOption that carries arbitrary metadata to be attached to the route descriptor.

func WithMetadata added in v1.17.0

func WithMetadata(value any) MetadataOption

WithMetadata creates a RouteOption that attaches arbitrary metadata to the route descriptor. This metadata can be used by gateways and middleware to implement features like rate limiting, auth requirements, etc.

type Pattern

type Pattern struct {
	// contains filtered or unexported fields
}

Pattern is used to compare and match request paths to route patterns. Patterns are used by the router to determine which handlers to execute for a given request.

func NewPattern

func NewPattern(patternStr string) (*Pattern, error)

NewPattern creates a new pattern from a string. The string should be a valid route pattern. If the string is not a valid route pattern, an error is returned.

func (*Pattern) Match

func (p *Pattern) Match(path string) (RequestParams, bool)

Match compares a path to the pattern and returns a map of named parameters extracted from the path as per the pattern. If the path matches the pattern, the second return value will be true. If the path does not match the pattern, the second return value will be false.

func (*Pattern) MatchInto

func (p *Pattern) MatchInto(path string, params *RequestParams) bool

MatchInto takes a path and a request params map and extracts the named parameters from the path into the map if the path matches the pattern. True will also be returned. If the path does not match the pattern, false will be returned, and no changes will be made to the map.

func (*Pattern) Path added in v1.9.0

func (p *Pattern) Path(params RequestParams, wildcards []string) (string, error)

Path creates a path string from the pattern by replacing dynamic segments with the provided parameters. If a required parameter is missing, an error is returned. Optional segments are only included if their parameters are provided. Wildcard segments are replaced with values from the wildcards slice in order. If there are more wildcard segments than values in the slice, an error is returned.

func (*Pattern) String

func (p *Pattern) String() string

String returns the string representation of the pattern.

type Redirect

type Redirect struct {
	To string
}

Redirect represents an HTTP redirect. If you want to redirect a request in a handler, you can initialize a Redirect with a target relative path or absolute URL, and set it as the body of the context. This will cause Navaros to send the client a Location header with the redirect url, and a 302 status code. The status code can be changed by setting the Status field of the context.

type RequestParams

type RequestParams map[string]string

RequestParams represents the parameters extracted from the request path. Parameters are extracted from the path by matching the request path to the route pattern for the handler node. These may change each time Next is called on the context.

func (RequestParams) Get

func (p RequestParams) Get(key string) string

Get returns the value of a given parameter key. If the key does not exist, an empty string is returned.

type RouteDescriptor

type RouteDescriptor struct {
	Method   HTTPMethod
	Pattern  *Pattern
	Metadata any
}

RouteDescriptor is a struct that is generated by the router when the public variants of the handler binding methods (named after their respective http method) are called. The router has a RouteDescriptors method which returns these objects, and can be used to build a api map, or pre-filter requests before they are passed to the router. This is most useful for libraries that wish to extend the functionality of Navaros.

func (*RouteDescriptor) MarshalJSON

func (r *RouteDescriptor) MarshalJSON() ([]byte, error)

MarshalJSON returns the JSON representation of the route descriptor.

func (*RouteDescriptor) UnmarshalJSON

func (r *RouteDescriptor) UnmarshalJSON(data []byte) error

UnmarshalJSON parses the JSON representation of the route descriptor.

type RouteOption added in v1.17.0

type RouteOption interface {
	// contains filtered or unexported methods
}

RouteOption is an interface for options that can be passed alongside handlers and transformers when binding routes. Route options are extracted before handler validation and do not participate in the handler chain.

type Router

type Router struct {
	// contains filtered or unexported fields
}

Router is the main component of Navaros. It is an HTTP handler that can be used to handle requests, and route them to the appropriate handlers. It implements the http.Handler interface, and can be used as a handler in standard http servers. It also implements Navaros' own Handler interface, which allows nesting routers for better code organization.

func NewRouter

func NewRouter() *Router

NewRouter creates a new router.

func (*Router) All

func (r *Router) All(path string, handlersAndTransformers ...any)

All allows binding handlers to all HTTP methods at a given route path pattern.

func (*Router) Delete

func (r *Router) Delete(path string, handlersAndTransformers ...any)

Delete allows binding handlers to the DELETE HTTP method at a given route path pattern.

func (*Router) Get

func (r *Router) Get(path string, handlersAndTransformers ...any)

Get allows binding handlers to the GET HTTP method at a given route path pattern.

func (*Router) Handle

func (r *Router) Handle(ctx *Context)

Handle is for the purpose of taking an existing context, and running it through the mux's handler chain. If the last handler calls next, it will call next on the original context.

func (*Router) Head

func (r *Router) Head(path string, handlersAndTransformers ...any)

Head allows binding handlers to the HEAD HTTP method at a given route path pattern.

func (*Router) Lookup added in v1.9.0

func (r *Router) Lookup(handlerOrTransformer any) (HTTPMethod, *Pattern, bool)

Lookup takes a handler or transformer and looks up what HTTP method and route pattern it is bound to. Returns the method, pattern, and true if found, or empty string, nil, and false if not found.

func (*Router) Options

func (r *Router) Options(path string, handlersAndTransformers ...any)

Options allows binding handlers to the OPTIONS HTTP method at a given route path pattern.

func (*Router) Patch

func (r *Router) Patch(path string, handlersAndTransformers ...any)

Patch allows binding handlers to the PATCH HTTP method at a given route path pattern.

func (*Router) Post

func (r *Router) Post(path string, handlersAndTransformers ...any)

Post allows binding handlers to the POST HTTP method at a given route path pattern.

func (*Router) PublicAll

func (r *Router) PublicAll(path string, handlersAndTransformers ...any)

PublicAll is the same as All, but it also adds the route descriptor to the router's list of public route descriptors.

func (*Router) PublicDelete

func (r *Router) PublicDelete(path string, handlersAndTransformers ...any)

PublicDelete is the same as Delete, but it also adds the route descriptor to the router's list of public route descriptors.

func (*Router) PublicGet

func (r *Router) PublicGet(path string, handlersAndTransformers ...any)

PublicGet is the same as Get, but it also adds the route descriptor to the router's list of public route descriptors.

func (*Router) PublicHead

func (r *Router) PublicHead(path string, handlersAndTransformers ...any)

PublicHead is the same as Head, but it also adds the route descriptor to the router's list of public route descriptors.

func (*Router) PublicOptions

func (r *Router) PublicOptions(path string, handlersAndTransformers ...any)

PublicOptions is the same as Options, but it also adds the route descriptor to the router's list of public route descriptors.

func (*Router) PublicPatch

func (r *Router) PublicPatch(path string, handlersAndTransformers ...any)

PublicPatch is the same as Patch, but it also adds the route descriptor to the router's list of public route descriptors.

func (*Router) PublicPost

func (r *Router) PublicPost(path string, handlersAndTransformers ...any)

PublicPost is the same as Post, but it also adds the route descriptor to the router's list of public route descriptors.

func (*Router) PublicPut

func (r *Router) PublicPut(path string, handlersAndTransformers ...any)

PublicPut is the same as Put, but it also adds the route descriptor to the router's list of public route descriptors.

func (*Router) Put

func (r *Router) Put(path string, handlersAndTransformers ...any)

Put allows binding handlers to the PUT HTTP method at a given route path pattern.

func (*Router) RouteDescriptors

func (r *Router) RouteDescriptors() []*RouteDescriptor

RouteDescriptors returns a list of all the route descriptors that this router is responsible for. Useful for gateway configuration.

func (*Router) ServeHTTP

func (r *Router) ServeHTTP(res http.ResponseWriter, req *http.Request)

ServeHTTP allows the router to be used as a handler in standard go http servers. It handles the incoming request - creating a context and running the handler chain over it, then finalizing the response.

func (*Router) Use

func (r *Router) Use(handlersAndTransformers ...any)

Use is for middleware handlers. It allows the handlers to be executed on every request. If a path is provided, the middleware will only be executed on requests that match the path.

Note that routers are middleware handlers, and so can be passed to Use to attach them as sub-routers. It's also important to know that if you provide a path with a router, the router will set the mount path as the base path for all of it's handlers. This means that if you have a router with a path of "/foo", and you bind a handler with a path of "/bar", the handler will only be executed on requests with a path of "/foo/bar".

type RouterHandler

type RouterHandler interface {
	RouteDescriptors() []*RouteDescriptor
	Handle(ctx *Context)
	Lookup(handlerOrTransformer any) (HTTPMethod, *Pattern, bool)
}

RouterHandler is handled nearly identically to a Handler, but it also provides a list of route descriptors which are collected by the router. These will be merged with the other route descriptors already collected. This use for situation where a handler may do more sub-routing, and the allows the handler to report the sub-routes to the router, rather than it's base path.

type Transformer

type Transformer interface {
	TransformRequest(ctx *Context)
	TransformResponse(ctx *Context)
}

Transformer is a special type of handler object that can be used to transform the context before and after handlers have processed the request. This is most useful for modifying or re-encoding the request and response bodies.

Directories

Path Synopsis
middleware
set

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL