{"id":23508961,"url":"https://github.com/maxbolgarin/servex","last_synced_at":"2025-07-26T12:11:52.685Z","repository":{"id":267925389,"uuid":"894432832","full_name":"maxbolgarin/servex","owner":"maxbolgarin","description":"Lightweight net/http server without boilerplate","archived":false,"fork":false,"pushed_at":"2025-04-25T20:15:25.000Z","size":63,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-25T21:24:33.622Z","etag":null,"topics":["go","golang","http","http-server","https"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/maxbolgarin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-11-26T10:52:04.000Z","updated_at":"2025-04-25T20:15:30.000Z","dependencies_parsed_at":"2025-02-16T19:45:19.555Z","dependency_job_id":"6d070c08-50f6-483a-81f5-81a80d04a560","html_url":"https://github.com/maxbolgarin/servex","commit_stats":null,"previous_names":["maxbolgarin/servex"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxbolgarin%2Fservex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxbolgarin%2Fservex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxbolgarin%2Fservex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxbolgarin%2Fservex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maxbolgarin","download_url":"https://codeload.github.com/maxbolgarin/servex/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253971097,"owners_count":21992647,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","golang","http","http-server","https"],"created_at":"2024-12-25T11:36:12.297Z","updated_at":"2025-05-13T15:35:56.548Z","avatar_url":"https://github.com/maxbolgarin.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Servex - Lightweight HTTP(S) Server Package\n\n[![Go Version][version-img]][doc] [![GoDoc][doc-img]][doc] [![Build][ci-img]][ci] [![GoReport][report-img]][report]\n\n**Servex** is a lightweight HTTP(S) server package built using Go's [net/http](https://pkg.go.dev/net/http) and [gorilla/mux](https://github.com/gorilla/mux). This package is designed to easy integrate into existing `net/http` servers. By using `gorilla/mux`, it offers flexible routing capabilities with the integrated middleware for logging, authentication and panic recovery.\n\n\n## Table of Contents\n- [Why Servex](#why-servex)\n- [Installation](#installation)\n- [Usage](#usage)\n  - [Starting a Server](#starting-a-server)\n  - [Using Context in Handlers](#using-context-in-handlers)\n  - [Authentication](#authentication)\n  - [Rate Limiter](#rate-limiter)\n- [Configuration Options](#configuration-options)\n- [Key Features](#key-features)\n- [Pros and Cons](#pros-and-cons)\n- [Contributing](#contributing)\n- [License](#license)\n\n\n## Why Servex\n\nImage you have a web appiction with vanila `net/http` handler:\n\n```go\nfunc handler(w http.ResponseWriter, r *http.Request) {\n    bodyBytes, err := io.ReadAll(r.Body)\n    if err != nil {\n        http.Error(w, \"cannot read request body\", http.StatusBadRequest)\n        return\n    }\n\n    var request Request\n    if err := json.Unmarshal(bodyBytes, \u0026request); err != nil {\n        http.Error(w, \"invalid request body\", http.StatusBadRequest)\n        return\n    }\n\n    // ... do something with request\n\n    respBytes, err := json.Marshal(resp)\n    if err != nil {\n        http.Error(w, \"cannot marshal response\", http.StatusInternalServerError)\n        return\n    }\n\n    w.Header().Set(\"Content-Type\", \"application/json\")\n    w.Header().Set(\"Content-Length\", strconv.Itoa(len(respBytes)))\n\n    if _, err := w.Write(respBytes); err != nil {\n        http.Error(w, \"cannot write response\", http.StatusInternalServerError)\n        return\n    }\n}\n```\n\nWith **Servex** you can write your handler like this:\n\n```go\nfunc handler(w http.ResponseWriter, r *http.Request) {\n    ctx := servex.C(w, r)\n\n    request, err := servex.ReadJSON[Request](r)\n    if err != nil {\n        ctx.BadRequest(err, \"invalid request body\")\n        return\n    }\n\n    // ... do something with request\n\n    ctx.Response(http.StatusOK, resp)\n}\n```\n\nLess code, more focus on business logic. Implement in your handler just by calling `servex.C(w, r)`. Look at the usage examples below to see options for starting a server with **Servex** in easy way.\n\n\n## Installation\n\nTo install the package, use the following `go get` command:\n\n```shell\ngo get -u github.com/maxbolgarin/servex\n```\n\n\n## Usage\n\n### Starting a Server\n\nThere are multiple ways to set up a Servex server:\n\n#### 1. Quick Start with Configuration\n\n```go\nctx, cancel := context.WithCancel(context.Background())\ndefer cancel()\n\n// Configure the server\nconfig := servex.BaseConfig{\n    HTTP: \":8080\", // HTTP address\n    HTTPS: \":8443\", // HTTPS address\n    CertFile: \"cert.pem\", // TLS certificate file\n    KeyFile: \"key.pem\", // TLS key file\n}\n\n// Set up routes\nroutes := func(r *mux.Router) {\n    r.HandleFunc(\"/hello\", func(w http.ResponseWriter, r *http.Request) {\n        fmt.Fprintf(w, \"Hello, world!\")\n    }).Methods(http.MethodGet)\n}\n\n// Initialize and start the server\nerr := servex.StartWithShutdown(ctx, config, routes, servex.WithLogger(slog.Default()))\nif err != nil {\n    log.Fatalf(\"failed to start servers: %v\", err)\n}\n\n// ... some code ...\n\ncancel() // Shutdown the server\n```\n\n#### 2. Using the Server Object\n\n```go\n// Initialize and start the server\nsrv := servex.New(\n    servex.WithReadTimeout(10*time.Second),\n    servex.WithLogger(slog.Default()), \n    servex.WithCertificate(cert),\n)\n\nsrv.HandleFunc(\"/hello\", helloHandler)\nsrv.HandleFunc(\"/world\", worldHandler)\n\nif err := srv.Start(\":8080\", \":8443\"); err != nil {\n    log.Fatalf(\"failed to start servers: %v\", err)\n}\n\n// ... some code ...\n\nsrv.Shutdown(ctx)\n```\n\n#### 3. Server with Graceful Shutdown\n\n```go\nsrv := servex.New(servex.WithLogger(slog.Default()))\n\n// Register routes\nsrv.R().HandleFunc(\"/api/v1/health\", healthHandler).Methods(http.MethodGet)\nsrv.R(\"/api/v1\").HandleFunc(\"/users\", usersHandler).Methods(http.MethodGet)\n\n// Start with automatic shutdown on context cancellation\nctx, cancel := context.WithCancel(context.Background())\ndefer cancel()\n\nif err := srv.StartWithShutdown(ctx, \":8080\", \"\"); err != nil {\n    log.Fatalf(\"failed to start server: %v\", err)\n}\n\n// Server will shut down automatically when context is canceled\n```\n\n### Using Context in Handlers\n\nServex can be integrated into existing `net/http` servers - you can create `servex.Context` based on the `http.Request` and `http.ResponseWriter` objects and use it in your handlers.\n\n```go\nfunc (app *App) CreateUserHandler(w http.ResponseWriter, r *http.Request) {\n    ctx := servex.C(w, r)\n\n    userRequest, err := servex.ReadJSON[User](r)\n    if err != nil {\n        ctx.BadRequest(err, \"invalid user\")\n        return\n    }\n\n    userIDResponse, err := app.CreateUser(ctx, userRequest);\n    if err != nil {\n        ctx.InternalServerError(err, \"cannot create user\")\n        return\n    }\n\n    ctx.Response(http.StatusCreated, userIDResponse)\n}\n```\n\nWith `servex.Context` you can get the experience of working in an HTTP framework like [echo](https://github.com/labstack/echo) inside plain `net/http` servers.\n\n#### Context Helpers\n\nServex's Context provides many helper methods:\n\n```go\nfunc exampleHandler(w http.ResponseWriter, r *http.Request) {\n    ctx := servex.C(w, r)\n    \n    // Get request information\n    requestID := ctx.RequestID()\n    apiVersion := ctx.APIVersion() // Extracts 'v1' from paths like /api/v1/...\n    userID := ctx.Path(\"id\")       // Path parameters from URL\n    sort := ctx.Query(\"sort\")      // Query parameters ?sort=asc\n    \n    // Read and validate request bodies\n    user, err := servex.ReadJSON[User](r)\n    // OR with validation if User implements Validate() method\n    user, err := servex.ReadAndValidate[User](r)\n    \n    // Handle cookies\n    cookie, _ := ctx.Cookie(\"session\")\n    ctx.SetCookie(\"session\", \"token123\", 3600, true, true)\n    \n    // Send responses with proper headers\n    ctx.Response(http.StatusOK, map[string]string{\"status\": \"success\"})\n    \n    // Or handle errors with consistent formatting\n    ctx.BadRequest(err, \"invalid input: %s\", err.Error())\n    ctx.NotFound(err, \"user not found\")\n    ctx.InternalServerError(err, \"database error\")\n}\n```\n\n### Authentication\n\nServex includes a built-in JWT-based authentication system:\n\n#### 1. Setting Up Authentication\n\n```go\n// Create an in-memory auth database\nmemoryDB := servex.NewMemoryAuthDatabase()\n\n// Configure the server with authentication\nsrv := servex.New(\n    servex.WithAuth(servex.AuthConfig{\n        Database: memoryDB,\n        RolesOnRegister: []servex.UserRole{\"user\"}, // Default roles for new users\n        AccessTokenDuration: 15 * time.Minute,\n        RefreshTokenDuration: 7 * 24 * time.Hour,\n        InitialUsers: []servex.InitialUser{\n            {Username: \"admin\", Password: \"admin123\", Roles: []servex.UserRole{\"admin\", \"user\"}},\n        },\n    }),\n)\n\n// Authentication routes are automatically registered under /auth/...\n// - POST /auth/register - Register new user\n// - POST /auth/login - Login and get tokens\n// - POST /auth/refresh - Refresh access token\n// - POST /auth/logout - Logout (invalidate refresh token)\n// - GET /auth/me - Get current user info (requires authentication)\n\n// Create protected routes\nsrv.HandleFuncWithAuth(\"/admin\", adminHandler, \"admin\") // Only users with \"admin\" role can access\nsrv.HFA(\"/protected\", protectedHandler, \"user\")         // Short form for HandleFuncWithAuth\n```\n\n#### 2. Authentication in Existing Handlers\n\n```go\nfunc (app *App) AdminOnlyHandler(w http.ResponseWriter, r *http.Request) {\n    ctx := servex.C(w, r)\n    \n    // Get user ID from context (set by auth middleware)\n    userID, ok := r.Context().Value(servex.UserContextKey{}).(string)\n    if !ok {\n        ctx.Unauthorized(errors.New(\"not authenticated\"), \"authentication required\")\n        return\n    }\n    \n    // Get user roles \n    roles, _ := r.Context().Value(servex.RoleContextKey{}).([]servex.UserRole)\n    \n    // ... handle the request\n    \n    ctx.Response(http.StatusOK, result)\n}\n```\n\n#### 3. Login Flow Example\n\n```go\n// Client sends login request\n// POST /auth/login\n// {\"username\": \"user1\", \"password\": \"pass123\"}\n\n// Server responds with:\n// 200 OK\n// {\n//   \"id\": \"user-id-123\",\n//   \"username\": \"user1\",\n//   \"roles\": [\"user\"],\n//   \"accessToken\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\"\n// }\n// Set-Cookie: refresh_token=token123; HttpOnly; Secure; SameSite=Strict\n\n// Client uses accessToken in Authorization header\n// GET /api/protected\n// Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\n\n// When access token expires, client sends refresh request\n// POST /auth/refresh\n// (refresh_token cookie is sent automatically)\n```\n\n### Rate Limiter\n\nServex includes a built-in rate limiting middleware:\n\n```go\n// Configure and start server with rate limiting\nsrv := servex.New(\n    servex.WithRPS(100), // 100 requests per second\n    servex.WithRateLimitExcludePaths(\"/health\", \"/docs\")\n)\n\n\n// Rate limiting by username for login attempts\ncustomKeyFunc := func(r *http.Request) string {\n    // Extract username from request for more accurate rate limiting\n    // on login endpoints\n    if r.URL.Path == \"/login\" {\n        username := r.FormValue(\"username\")\n        if username != \"\" {\n            return \"user:\" + username\n        }\n    }\n    // Fall back to IP-based limiting\n    return r.RemoteAddr\n}\n\nsrv := servex.New(\n    servex.WithRateLimitConfig(servex.RateLimitConfig{\n        RequestsPerInterval: 5,\n        Interval:            time.Minute,\n        KeyFunc:             customKeyFunc,\n    }),\n)\n```\n\n## Configuration Options\n\nServex allows customization through options passed during server instantiation. Here's how you can configure it:\n\n- **WithCertificate**: Set TLS certificate for HTTPS.\n- **WithReadTimeout**: Customize read timeout duration.\n- **WithIdleTimeout**: Customize idle timeout duration.\n- **WithAuthToken**: Set an authorization token for middleware-based authentication.\n- **WithMetrics**: Attach a metrics handler to track requests.\n- **WithLogger**: Specify a custom logging mechanism.\n- **WithRequestLogger**: Customizes request logging separately from server logging.\n- **WithAuth**: Configure JWT-based authentication with roles.\n- **WithRateLimitConfig**: Configure rate limiting with custom options.\n- **WithRPM**: Set rate limit to requests per minute.\n- **WithRPS**: Set rate limit to requests per second.\n\nExample:\n\n```go\noptions := []servex.Option{\n    servex.WithReadTimeout(30 * time.Second),\n    servex.WithIdleTimeout(120 * time.Second),\n    servex.WithAuthToken(\"s3cret\"),\n    servex.WithRPS(10), // Limit to 10 requests per second\n}\n\n// Create server with options\nserver := servex.NewWithOptions(servex.Options{\n    ReadTimeout:   30 * time.Second,\n    IdleTimeout:   120 * time.Second,\n    AuthToken:     \"s3cret\",\n    RateLimit: servex.RateLimitConfig{\n        RequestsPerInterval: 10,\n        Interval:            time.Second,\n        BurstSize:           20, // Allow bursts of up to 20 requests\n        ExcludePaths:        []string{\"/health\", \"/metrics\"},\n    },\n})\n```\n\n## Key Features\n\n- **Simplified HTTP Handling**: Context-based request/response handling with type safety\n- **Flexible Routing**: Powered by gorilla/mux with subrouters, path variables, etc.\n- **Built-in Authentication**: JWT-based authentication with refresh tokens and role-based access\n- **Rate Limiting**: Built-in middleware for restricting request frequency with customizable options\n- **Structured Error Responses**: Consistent error formatting across all endpoints\n- **Graceful Shutdown**: Clean shutdown capabilities for both HTTP and HTTPS servers\n- **TLS Support**: Easy HTTPS configuration with certificate management\n- **Integrated Middleware**: Logging, authentication, panic recovery out of the box\n- **Type-Safe JSON Handling**: Generic functions for reading and validating JSON requests\n\n## Pros and Cons\n\n### Pros\n- **Lightweight**: Minimal overhead, quick to integrate.\n- **Flexible Routing**: Powered by `gorilla/mux`, allowing for precise routing and path handling.\n- **Built-in Middleware**: Includes logging, authentication, and panic recovery.\n- **Context-based Request Handling**: No more boilerplate for reading requests and sending responses.\n- **Type Safety**: Generics support for JSON handling with less boilerplate.\n- **Roles-Based Auth**: Built-in JWT authentication with refresh tokens and role-based access control.\n\n### Cons\n- **Basic Documentation**: Might require reading docs and understanding of underlying `gorilla/mux` for advanced use cases.\n- **Limited Database Options**: Currently supports only in-memory database for auth out of the box. You should implement it's own database it you want to use auth.\n- **Auth limitations**: Auth handling is very basic.\n\n## Contributing\n\nIf you'd like to contribute to **servex**, submit a pull request or open an issue.\n\n## License\n\nServex is licensed under the MIT License. See the [LICENSE](LICENSE) file for more information.\n\n[version-img]: https://img.shields.io/badge/Go-%3E%3D%201.24-%23007d9c\n[doc-img]: https://pkg.go.dev/badge/github.com/maxbolgarin/servex\n[doc]: https://pkg.go.dev/github.com/maxbolgarin/servex\n[ci-img]: https://github.com/maxbolgarin/servex/actions/workflows/go.yml/badge.svg\n[ci]: https://github.com/maxbolgarin/servex/actions\n[report-img]: https://goreportcard.com/badge/github.com/maxbolgarin/servex\n[report]: https://goreportcard.com/report/github.com/maxbolgarin/servex\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxbolgarin%2Fservex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaxbolgarin%2Fservex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxbolgarin%2Fservex/lists"}