Request Bodies Guide¶
This guide covers all request body types and how to decode them with Schema.
Body Types Overview¶
Schema supports three main body types, each optimized for different use cases:
| Body Type | Use Case | Content-Type | Field Type |
|---|---|---|---|
structured |
JSON, XML, Forms | application/json, application/xml, application/x-www-form-urlencoded |
struct, map |
file |
Single file upload | Any (typically application/octet-stream) |
[]byte, io.ReadCloser |
multipart |
Form with files | multipart/form-data |
struct with mixed fields |
Structured Bodies¶
Use body:"structured" for JSON, XML, or form-encoded data.
JSON Bodies¶
The most common use case - REST APIs with JSON:
type CreateUserRequest struct {
Body struct {
Name string `schema:"name"`
Email string `schema:"email"`
Age int `schema:"age"`
Tags []string `schema:"tags"`
Settings map[string]any `schema:"settings"`
} `body:"structured"`
}
func handler(w http.ResponseWriter, r *http.Request) {
var req CreateUserRequest
codec.DecodeRequest(r, nil, &req)
// req.Body.Name = "Alice"
// req.Body.Email = "alice@example.com"
// req.Body.Age = 30
}
Nested JSON Structures¶
Handle complex nested objects:
type CreateOrderRequest struct {
Body struct {
Customer struct {
Name string `schema:"name"`
Email string `schema:"email"`
Address struct {
Street string `schema:"street"`
City string `schema:"city"`
Country string `schema:"country"`
} `schema:"address"`
} `schema:"customer"`
Items []struct {
ProductID string `schema:"product_id"`
Quantity int `schema:"quantity"`
Price float64 `schema:"price"`
} `schema:"items"`
Total float64 `schema:"total"`
} `body:"structured"`
}
XML Bodies¶
Full support for XML with struct tags:
type ImportDataRequest struct {
Body struct {
Name string `xml:"name"`
Email string `xml:"email"`
Items []struct {
ID string `xml:"id,attr"` // Attribute
Name string `xml:"name"` // Element
} `xml:"item"`
} `body:"structured"`
}
func handler(w http.ResponseWriter, r *http.Request) {
var req ImportDataRequest
codec.DecodeRequest(r, nil, &req)
// XML is unmarshaled using Go's encoding/xml
}
XML Notes:
- Use
xmlstruct tags to define XML element/attribute names - Target field must be a struct, slice, or string
map[string]anyis not supported for XML- Full support for Go's
encoding/xmlfeatures (attributes, CDATA, etc.)
URL-Encoded Form Bodies¶
Handle traditional HTML form submissions:
type LoginRequest struct {
Body struct {
Username string `schema:"username"`
Password string `schema:"password"`
Remember bool `schema:"remember"`
} `body:"structured"`
}
func handler(w http.ResponseWriter, r *http.Request) {
var req LoginRequest
codec.DecodeRequest(r, nil, &req)
// req.Body.Username = "alice"
// req.Body.Password = "secret"
// req.Body.Remember = true
}
Content-Type Auto-Detection¶
Schema automatically detects the content type:
type FlexibleRequest struct {
Body UserData `body:"structured"`
}
// Works with:
// - Content-Type: application/json → JSON decoding
// - Content-Type: application/xml → XML decoding
// - Content-Type: application/x-www-form-urlencoded → Form decoding
// - No Content-Type → Defaults to JSON
File Uploads¶
Use body:"file" for single file uploads.
Small Files (In Memory)¶
Load entire file into memory as bytes:
type UploadAvatarRequest struct {
// Query parameter for metadata
Filename string `schema:"filename,location=query"`
// File content as bytes
Body []byte `body:"file"`
}
func handler(w http.ResponseWriter, r *http.Request) {
var req UploadAvatarRequest
codec.DecodeRequest(r, nil, &req)
// req.Body contains entire file
fmt.Printf("Uploaded %s (%d bytes)\n", req.Filename, len(req.Body))
// Save to disk
os.WriteFile("/uploads/" + req.Filename, req.Body, 0644)
}
⚠️ Warning: Only use []byte for small files (< 10MB). Large files will consume excessive memory.
Large Files (Memory Loaded)¶
For []byte fields, files are read entirely into memory:
type UploadImageRequest struct {
Filename string `schema:"filename,location=query"`
Body []byte `body:"file"`
}
func handler(w http.ResponseWriter, r *http.Request) {
var req UploadImageRequest
codec.DecodeRequest(r, nil, &req)
// Write to disk
err := os.WriteFile("/uploads/"+req.Filename, req.Body, 0644)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "Uploaded %d bytes\n", len(req.Body))
}
Large Files (Streaming)¶
For io.ReadCloser fields, the request body is streamed without loading into memory:
type UploadVideoRequest struct {
Filename string `schema:"filename,location=query"`
Body io.ReadCloser `body:"file"`
}
func handler(w http.ResponseWriter, r *http.Request) {
var req UploadVideoRequest
codec.DecodeRequest(r, nil, &req)
defer req.Body.Close()
// Stream to disk
file, err := os.Create("/uploads/" + req.Filename)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer file.Close()
// Copy streams data chunk by chunk
written, err := io.Copy(file, req.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "Uploaded %d bytes\n", written)
}
File Upload with Metadata¶
Combine file upload with other parameters:
type DocumentUploadRequest struct {
// Path parameter
ProjectID string `schema:"project_id,location=path"`
// Query parameters
Title string `schema:"title,location=query"`
Description string `schema:"description,location=query"`
// Header
APIKey string `schema:"X-API-Key,location=header"`
// File content
Body []byte `body:"file"`
}
// Route: /projects/{project_id}/documents
r.Post("/projects/{project_id}/documents", handler)
Multipart Forms¶
Use body:"multipart" for forms with both text fields and file uploads.
Basic Multipart Form¶
type UploadFormRequest struct {
Body struct {
// Text fields
Title string `schema:"title"`
Description string `schema:"description"`
Category string `schema:"category"`
// Single file
Document io.ReadCloser `schema:"document"`
} `body:"multipart"`
}
func handler(w http.ResponseWriter, r *http.Request) {
var req UploadFormRequest
codec.DecodeRequest(r, nil, &req)
defer req.Body.Document.Close()
// req.Body.Title = "My Document"
// req.Body.Description = "Important file"
// req.Body.Document = (file content stream)
// Save file
file, _ := os.Create("/uploads/" + req.Body.Title)
defer file.Close()
io.Copy(file, req.Body.Document)
}
Multiple File Uploads¶
Handle multiple files in a single request:
type GalleryUploadRequest struct {
Body struct {
// Text fields
AlbumName string `schema:"album_name"`
Description string `schema:"description"`
// Multiple files
Images []io.ReadCloser `schema:"images"`
} `body:"multipart"`
}
func handler(w http.ResponseWriter, r *http.Request) {
var req GalleryUploadRequest
codec.DecodeRequest(r, nil, &req)
// Close all files when done
defer func() {
for _, img := range req.Body.Images {
img.Close()
}
}()
fmt.Printf("Album: %s\n", req.Body.AlbumName)
fmt.Printf("Received %d images\n", len(req.Body.Images))
// Process each file
for i, img := range req.Body.Images {
filename := fmt.Sprintf("/uploads/%s_%d.jpg", req.Body.AlbumName, i)
file, _ := os.Create(filename)
io.Copy(file, img)
file.Close()
}
}
File Metadata with FileHeader¶
Use *multipart.FileHeader when you need access to the file's metadata (filename, size, content-type headers) before reading the file content. This is useful for validating file size or type before consuming any bytes:
type DocumentUploadRequest struct {
Body struct {
Title string `schema:"title"`
File *multipart.FileHeader `schema:"file"`
} `body:"multipart"`
}
func handler(w http.ResponseWriter, r *http.Request) {
var req DocumentUploadRequest
codec.DecodeRequest(r, nil, &req)
// Validate before reading
if req.Body.File.Size > 50<<20 { // 50MB limit
http.Error(w, "file too large", http.StatusRequestEntityTooLarge)
return
}
contentType := req.Body.File.Header.Get("Content-Type")
if contentType != "application/pdf" {
http.Error(w, "only PDF files accepted", http.StatusBadRequest)
return
}
fmt.Printf("File: %s (%d bytes, %s)\n",
req.Body.File.Filename, req.Body.File.Size, contentType)
// Open when ready to read
f, _ := req.Body.File.Open()
defer f.Close()
io.Copy(os.Stdout, f)
}
For multiple files, use []*multipart.FileHeader:
type BatchUploadRequest struct {
Body struct {
Files []*multipart.FileHeader `schema:"files"`
} `body:"multipart"`
}
func handler(w http.ResponseWriter, r *http.Request) {
var req BatchUploadRequest
codec.DecodeRequest(r, nil, &req)
for _, fh := range req.Body.Files {
fmt.Printf("File: %s (%d bytes)\n", fh.Filename, fh.Size)
f, _ := fh.Open()
// process file...
f.Close()
}
}
When to use which file type:
| Field Type | Use When |
|---|---|
io.ReadCloser |
You just need to stream the file content |
[]io.ReadCloser |
Multiple files, stream-only |
*multipart.FileHeader |
You need filename, size, or content-type before reading |
[]*multipart.FileHeader |
Multiple files with metadata access |
[]byte |
Small files loaded entirely into memory |
Optional Files¶
Use pointers for optional file uploads:
type ProfileUpdateRequest struct {
Body struct {
Name string `schema:"name"`
Email string `schema:"email"`
// Optional avatar (nil if not uploaded)
Avatar *io.ReadCloser `schema:"avatar"`
} `body:"multipart"`
}
func handler(w http.ResponseWriter, r *http.Request) {
var req ProfileUpdateRequest
codec.DecodeRequest(r, nil, &req)
if req.Body.Avatar != nil {
defer (*req.Body.Avatar).Close()
// Process avatar upload
} else {
// No avatar provided - keep existing
}
}
Body Type Selection Guide¶
Choose the right body type for your use case:
Decision Matrix¶
| Scenario | Body Type | Field Type | Example Use Case |
|---|---|---|---|
| REST API with JSON | structured |
struct |
User CRUD operations |
| XML web service | structured |
struct with xml tags |
Legacy system integration |
| HTML form submission | structured |
struct |
Login, contact forms |
| Profile picture upload | file |
[]byte |
Small image < 10MB |
| Video upload | file |
io.ReadCloser |
Large media files (streaming) |
| Document with metadata | multipart |
struct with mixed fields |
PDF with title/description |
| Photo gallery upload | multipart |
struct with []io.ReadCloser |
Multiple images + album info |
| Upload with size/type validation | multipart |
struct with *multipart.FileHeader |
Reject oversized or wrong-type files before reading |
| Batch upload with metadata | multipart |
struct with []*multipart.FileHeader |
Multiple files with filename/size inspection |
Next Steps¶
- Type Conversion Guide - Understand type handling
- Serialization Guide - Learn about OpenAPI styles