Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 7 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,69 +12,25 @@ cleaner, more maintainable code with reduced boilerplate.
- **Modular Design:** Each component (Request, Validation, Response) can be used independently,
enhancing testability and flexibility.

> **Note:** Currently it only supports Chi.
### Supported routers

- Gorilla MUX
- Chi
- Go Standard
- ...maybe more? Submit a PR with an example.

## Installation

To install **httpsuite**, run:

```
go get github.com/rluders/httpsuite
go get github.com/rluders/httpsuite/v2
```

## Usage

### Request Parsing with URL Parameters

Easily parse incoming requests and set URL parameters:

```go
package main

import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/rluders/httpsuite"
"log"
"net/http"
)

type SampleRequest struct {
Name string `json:"name" validate:"required,min=3"`
Age int `json:"age" validate:"required,min=1"`
}

func (r *SampleRequest) SetParam(fieldName, value string) error {
switch fieldName {
case "name":
r.Name = value
}
return nil
}

func main() {
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)

r.Post("/submit/{name}", func(w http.ResponseWriter, r *http.Request) {
// Step 1: Parse the request and validate it
req, err := httpsuite.ParseRequest[*SampleRequest](w, r, "name")
if err != nil {
log.Printf("Error parsing or validating request: %v", err)
return
}

// Step 2: Send a success response
httpsuite.SendResponse[SampleRequest](w, http.StatusOK, *req, nil, nil)
})

log.Println("Starting server on :8080")
http.ListenAndServe(":8080", r)
}

```

Check out the [example folder for a complete project](./examples) demonstrating how to integrate **httpsuite** into
your Go microservices.

Expand Down
20 changes: 20 additions & 0 deletions examples/chi/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module chi_example

go 1.23

require (
github.com/go-chi/chi/v5 v5.2.0
github.com/rluders/httpsuite/v2 v2.0.0
)

require (
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.24.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
)
30 changes: 30 additions & 0 deletions examples/chi/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
77 changes: 77 additions & 0 deletions examples/chi/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package main

import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/rluders/httpsuite/v2"
"log"
"net/http"
"strconv"
)

type SampleRequest struct {
ID int `json:"id" validate:"required"`
Name string `json:"name" validate:"required,min=3"`
Age int `json:"age" validate:"required,min=1"`
}

type SampleResponse struct {
ID int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}

func (r *SampleRequest) SetParam(fieldName, value string) error {
switch fieldName {
case "id":
id, err := strconv.Atoi(value)
if err != nil {
return err
}
r.ID = id
}
return nil
}

func ChiParamExtractor(r *http.Request, key string) string {
return chi.URLParam(r, key)
}

// You can test it using:
//
// curl -X POST http://localhost:8080/submit/123 \
// -H "Content-Type: application/json" \
// -d '{"name": "John Doe", "age": 30}'
//
// And you should get:
//
// {"data":{"id":123,"name":"John Doe","age":30}}
func main() {
// Creating the router with Chi
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)

// Define the endpoint POST
r.Post("/submit/{id}", func(w http.ResponseWriter, r *http.Request) {
// Using the function for parameter extraction to the ParseRequest
req, err := httpsuite.ParseRequest[*SampleRequest](w, r, ChiParamExtractor, "id")
if err != nil {
log.Printf("Error parsing or validating request: %v", err)
return
}

resp := &SampleResponse{
ID: req.ID,
Name: req.Name,
Age: req.Age,
}

// Sending success response
httpsuite.SendResponse[SampleResponse](w, http.StatusOK, *resp, nil, nil)
})

// Starting the server
log.Println("Starting server on :8080")
http.ListenAndServe(":8080", r)
}
20 changes: 20 additions & 0 deletions examples/gorillamux/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module gorillamux_example

go 1.23

require (
github.com/gorilla/mux v1.8.1
github.com/rluders/httpsuite/v2 v2.0.0
)

require (
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.24.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
)
30 changes: 30 additions & 0 deletions examples/gorillamux/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
66 changes: 66 additions & 0 deletions examples/gorillamux/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package main

import (
"github.com/gorilla/mux"
"github.com/rluders/httpsuite/v2"
"log"
"net/http"
"strconv"
)

type SampleRequest struct {
ID int `json:"id" validate:"required"`
Name string `json:"name" validate:"required,min=3"`
Age int `json:"age" validate:"required,min=1"`
}

type SampleResponse struct {
ID int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}

func (r *SampleRequest) SetParam(fieldName, value string) error {
switch fieldName {
case "id":
id, err := strconv.Atoi(value)
if err != nil {
return err
}
r.ID = id
}
return nil
}

func GorillaMuxParamExtractor(r *http.Request, key string) string {
return mux.Vars(r)[key] // Extracts parameter using Gorilla Mux
}

// Test the server using:
// curl -X POST http://localhost:8080/submit/123 -H "Content-Type: application/json" -d '{"name": "John Doe", "age": 30}'
func main() {
// Creating the router with Gorilla Mux
r := mux.NewRouter()

r.HandleFunc("/submit/{id}", func(w http.ResponseWriter, r *http.Request) {
// Using the function for parameter extraction to the ParseRequest
req, err := httpsuite.ParseRequest[*SampleRequest](w, r, GorillaMuxParamExtractor, "id")
if err != nil {
log.Printf("Error parsing or validating request: %v", err)
return
}

resp := &SampleResponse{
ID: req.ID,
Name: req.Name,
Age: req.Age,
}

// Sending success response
httpsuite.SendResponse[SampleResponse](w, http.StatusOK, *resp, nil, nil)
}).Methods("POST")

// Starting the server
log.Println("Starting server on :8080")
http.ListenAndServe(":8080", r)
}
43 changes: 0 additions & 43 deletions examples/main.go

This file was deleted.

17 changes: 17 additions & 0 deletions examples/stdmux/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module stdmux_example

go 1.23

require github.com/rluders/httpsuite/v2 v2.0.0

require (
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.24.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
)
Loading