Skip to content

Go package for parsing HTTP request bodies (form-data, JSON, query params) into maps and structs with automatic type conversion and file upload support.

License

Notifications You must be signed in to change notification settings

ezartsh/inrequest

Repository files navigation

Go Inrequest

Go Version License Go Report Card

Golang package for transforming HTTP request body into Go maps and structs.

Supports multiple content types:

  • multipart/form-data (with file uploads)
  • application/x-www-form-urlencoded
  • application/json
  • Query string parameters

Installation

Requires Go 1.17+

go get github.com/ezartsh/inrequest

Import

import "github.com/ezartsh/inrequest"

Quick Start

package main

import (
    "fmt"
    "net/http"
    "github.com/ezartsh/inrequest"
)

func main() {
    http.HandleFunc("/submit", func(w http.ResponseWriter, r *http.Request) {
        // Parse form data
        req := inrequest.FormData(r)
        defer req.Cleanup() // Optional: clean up temp files
        
        fmt.Println(req.ToMap())
        // output: map[first_name:John last_name:Smith]
    })
    
    http.ListenAndServe(":8080", nil)
}

API Reference

Entry Functions

Function Description
FormData(r *http.Request) Parse multipart/form-data or url-encoded form
FormDataWithOptions(r *http.Request, opts Options) Parse form with custom options
Query(r *http.Request) Parse query string parameters
Json(r *http.Request) Parse JSON request body
Parse(r *http.Request) Auto-detect and parse based on Content-Type

Options

type Options struct {
    MaxMemory int64 // Max memory for multipart parsing (default: 32MB)
}

Request Interface

All request types implement the Request interface:

type Request interface {
    ToMap() RequestValue                    // Get as map[string]interface{}
    ToBind(model interface{}) error         // Bind to struct
    ToJsonByte() ([]byte, error)            // Get as JSON bytes
    ToJsonString() (string, error)          // Get as JSON string
}

FormRequest Additional Methods

// Cleanup removes temporary files created during multipart form parsing.
// Optional but recommended for high-traffic servers.
func (r FormRequest) Cleanup()

Types

// RequestValue is the parsed request data
type RequestValue = map[string]interface{}

// FileHeaders represents multiple uploaded files for a single field
type FileHeaders []*multipart.FileHeader

Request Body Types


1. Form Data

Basic Example

<form action="/submit" method="POST">
    <input type="text" value="John" name="first_name"/>
    <input type="text" value="Smith" name="last_name"/>
</form>
package main

import (
    "fmt"
    "net/http"
    "github.com/ezartsh/inrequest"
)

type BodyRequest struct {
    FirstName string `json:"first_name"`
    LastName  string `json:"last_name"`
}

func main() {
    http.HandleFunc("/submit", func(w http.ResponseWriter, r *http.Request) {
        req := inrequest.FormData(r)
        defer req.Cleanup()
        
        // Get as map
        fmt.Println(req.ToMap())
        // output: map[first_name:John last_name:Smith]
        
        // Bind to struct
        var body BodyRequest
        req.ToBind(&body)
        fmt.Printf("My name is %s %s\n", body.FirstName, body.LastName)
        // output: My name is John Smith
        
        // Get as JSON string
        if jsonStr, err := req.ToJsonString(); err == nil {
            fmt.Println(jsonStr)
            // output: {"first_name":"John","last_name":"Smith"}
        }
    })
    
    http.ListenAndServe(":8080", nil)
}

Nested Arrays and Objects

<form action="/submit" method="POST" enctype="multipart/form-data">
    <input type="text" value="John" name="names[0]"/>
    <input type="text" value="Smith" name="names[1]"/>
    <input type="text" value="Doe" name="names[2]"/>
    
    <input type="text" value="Title 1" name="attachments[0][title]"/>
    <textarea name="attachments[0][description]">Description 1</textarea>
    <input type="file" name="attachments[0][file]"/>
    
    <input type="text" value="Title 2" name="attachments[1][title]"/>
    <textarea name="attachments[1][description]">Description 2</textarea>
    <input type="file" name="attachments[1][file]"/>
</form>
req := inrequest.FormData(r)
defer req.Cleanup()

jsonStr, _ := req.ToJsonString()
fmt.Println(jsonStr)

Output:

{
    "names": ["John", "Smith", "Doe"],
    "attachments": [
        {
            "title": "Title 1",
            "description": "Description 1",
            "file": {
                "Filename": "document.pdf",
                "Header": {
                    "Content-Type": ["application/pdf"]
                },
                "Size": 13264
            }
        },
        {
            "title": "Title 2",
            "description": "Description 2",
            "file": {
                "Filename": "image.png",
                "Header": {
                    "Content-Type": ["image/png"]
                },
                "Size": 8192
            }
        }
    ]
}

Multiple File Uploads

For multiple files with the same field name, use FileHeaders:

<input type="file" name="documents[]" multiple/>
type UploadRequest struct {
    Documents inrequest.FileHeaders `json:"documents"`
}

req := inrequest.FormData(r)
defer req.Cleanup()

var upload UploadRequest
req.ToBind(&upload)

for _, file := range upload.Documents {
    fmt.Printf("File: %s, Size: %d\n", file.Filename, file.Size)
}

Custom Memory Limit

req := inrequest.FormDataWithOptions(r, inrequest.Options{
    MaxMemory: 10 << 20, // 10 MB
})
defer req.Cleanup()

2. Query String

GET /search?utm_source=google&callback_url=http://localhost:3000&status=active
package main

import (
    "fmt"
    "net/http"
    "github.com/ezartsh/inrequest"
)

func main() {
    http.HandleFunc("/search", func(w http.ResponseWriter, r *http.Request) {
        req := inrequest.Query(r)
        
        fmt.Println(req.ToMap())
        // output: map[callback_url:http://localhost:3000 status:active utm_source:google]
        
        if jsonStr, err := req.ToJsonString(); err == nil {
            fmt.Println(jsonStr)
            // output: {"callback_url":"http://localhost:3000","status":"active","utm_source":"google"}
        }
    })
    
    http.ListenAndServe(":8080", nil)
}

Nested Query Parameters

GET /filter?filters[status]=active&filters[category]=tech&items[0]=a&items[1]=b
req := inrequest.Query(r)
fmt.Println(req.ToMap())
// output: map[filters:map[status:active category:tech] items:[a b]]

3. JSON Request

{
    "first_name": "John",
    "last_name": "Smith",
    "age": 31
}
package main

import (
    "fmt"
    "net/http"
    "github.com/ezartsh/inrequest"
)

type Person struct {
    FirstName string `json:"first_name"`
    LastName  string `json:"last_name"`
    Age       int    `json:"age"`
}

func main() {
    http.HandleFunc("/api/person", func(w http.ResponseWriter, r *http.Request) {
        req := inrequest.Json(r)
        
        fmt.Println(req.ToMap())
        // output: map[age:31 first_name:John last_name:Smith]
        
        var person Person
        req.ToBind(&person)
        fmt.Printf("%s is %d years old\n", person.FirstName, person.Age)
        // output: John is 31 years old
        
        if jsonStr, err := req.ToJsonString(); err == nil {
            fmt.Println(jsonStr)
            // output: {"age":31,"first_name":"John","last_name":"Smith"}
        }
    })
    
    http.ListenAndServe(":8080", nil)
}

4. Auto Parse

Use Parse() to automatically detect and parse based on Content-Type:

http.HandleFunc("/api/data", func(w http.ResponseWriter, r *http.Request) {
    req := inrequest.Parse(r)
    
    // Works with any content type:
    // - application/json
    // - multipart/form-data
    // - application/x-www-form-urlencoded
    // - Falls back to query string
    
    fmt.Println(req.ToMap())
})

Type Conversion

The library automatically converts string values to appropriate Go types:

Input Output Type
"123" int
"3.14" float64
"true", "false" bool
"null" nil
Other strings string

Binding to Structs

Use ToBind() to bind request data to a struct. The struct fields should have json tags:

type CreateUserRequest struct {
    Name     string                  `json:"name"`
    Email    string                  `json:"email"`
    Age      int                     `json:"age"`
    Active   bool                    `json:"active"`
    Avatar   *multipart.FileHeader   `json:"avatar"`      // Single file
    Photos   inrequest.FileHeaders   `json:"photos"`      // Multiple files
}

req := inrequest.FormData(r)
defer req.Cleanup()

var user CreateUserRequest
if err := req.ToBind(&user); err != nil {
    http.Error(w, err.Error(), http.StatusBadRequest)
    return
}

// Access file
if user.Avatar != nil {
    fmt.Printf("Avatar: %s\n", user.Avatar.Filename)
}

// Access multiple files
for _, photo := range user.Photos {
    fmt.Printf("Photo: %s\n", photo.Filename)
}

Performance

The library is optimized for performance:

Operation Time
Simple form (5 fields) ~5 µs
Complex nested form ~50 µs
Large form (100 fields) ~200 µs
File upload ~500 µs

Memory allocation is minimal, and temporary files are cleaned up with Cleanup().


Contributing

If you have a bug report or feature request, you can open an issue, and pull requests are also welcome.

License

inrequest is released under the MIT license. See LICENSE for details.

About

Go package for parsing HTTP request bodies (form-data, JSON, query params) into maps and structs with automatic type conversion and file upload support.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages