Core Guides
Stable v1.4.8
File Uploads
Learn about File Uploads in EchoNext.
File Uploads
EchoNext provides type-safe file upload support with automatic OpenAPI documentation.
Basic Usage
import (
"github.com/abdussamadbello/echonext"
"github.com/abdussamadbello/echonext/upload"
"github.com/labstack/echo/v4"
)
type AvatarRequest struct {
File *upload.File `form:"avatar" validate:"required"`
}
type AvatarResponse struct {
URL string `json:"url"`
Filename string `json:"filename"`
Size int64 `json:"size"`
}
func uploadAvatar(c echo.Context, req AvatarRequest) (AvatarResponse, error) {
// Access file metadata
fmt.Printf("Filename: %s\n", req.File.Filename)
fmt.Printf("Size: %d bytes\n", req.File.Size)
fmt.Printf("Content-Type: %s\n", req.File.ContentType)
// Save the file
destPath := "/uploads/" + req.File.Filename
if err := req.File.SaveTo(destPath); err != nil {
return AvatarResponse{}, echo.NewHTTPError(500, "Failed to save file")
}
return AvatarResponse{
URL: destPath,
Filename: req.File.Filename,
Size: req.File.Size,
}, nil
}
func main() {
app := echonext.New()
app.Upload("/avatar", uploadAvatar, echonext.Route{
Summary: "Upload avatar image",
Description: "Upload a user avatar image",
Tags: []string{"Users"},
})
app.Start(":8080")
}File Type
The upload.File type provides:
type File struct {
Filename string // Original filename
Size int64 // File size in bytes
ContentType string // MIME type
Header *multipart.FileHeader // Underlying multipart header
}
// Methods
func (f *File) Open() (multipart.File, error) // Open file for reading
func (f *File) Read() ([]byte, error) // Read entire file into memory
func (f *File) SaveTo(path string) error // Save to destination pathMultiple File Uploads
type DocumentsRequest struct {
Files []*upload.File `form:"documents" validate:"required,max=10"`
}
type DocumentsResponse struct {
Uploaded int `json:"uploaded"`
Files []string `json:"files"`
}
func uploadDocuments(c echo.Context, req DocumentsRequest) (DocumentsResponse, error) {
var filenames []string
for _, file := range req.Files {
destPath := "/uploads/documents/" + file.Filename
if err := file.SaveTo(destPath); err != nil {
return DocumentsResponse{}, echo.NewHTTPError(500, "Failed to save: "+file.Filename)
}
filenames = append(filenames, file.Filename)
}
return DocumentsResponse{
Uploaded: len(req.Files),
Files: filenames,
}, nil
}
app.Upload("/documents", uploadDocuments, echonext.Route{
Summary: "Upload multiple documents",
Tags: []string{"Documents"},
})File Upload Configuration
Configure file validation using FileUploadConfig:
app.Upload("/upload", handler, echonext.Route{
Summary: "Upload file with validation",
FileConfig: &echonext.FileUploadConfig{
// Maximum size per file (10MB)
MaxFileSize: 10 << 20,
// Maximum total size for all files (50MB)
MaxTotalSize: 50 << 20,
// Allowed MIME types
AllowedMIMETypes: []string{
"image/jpeg",
"image/png",
"image/gif",
"application/pdf",
},
// Allowed file extensions
AllowedExtensions: []string{
".jpg", ".jpeg", ".png", ".gif", ".pdf",
},
// Maximum number of files
MaxFiles: 5,
},
})Mixed Form Data
Combine file uploads with other form fields:
type ProfileUpdateRequest struct {
Name string `form:"name" validate:"required,min=2"`
Bio string `form:"bio" validate:"max=500"`
Avatar *upload.File `form:"avatar"`
}
type ProfileResponse struct {
Name string `json:"name"`
Bio string `json:"bio"`
AvatarURL string `json:"avatar_url,omitempty"`
}
func updateProfile(c echo.Context, req ProfileUpdateRequest) (ProfileResponse, error) {
response := ProfileResponse{
Name: req.Name,
Bio: req.Bio,
}
// Avatar is optional
if req.Avatar != nil {
destPath := "/uploads/avatars/" + req.Avatar.Filename
if err := req.Avatar.SaveTo(destPath); err != nil {
return ProfileResponse{}, echo.NewHTTPError(500, "Failed to save avatar")
}
response.AvatarURL = destPath
}
return response, nil
}Custom File Processing
Process files before saving:
import (
"image"
"image/jpeg"
_ "image/png"
)
func uploadAndResize(c echo.Context, req AvatarRequest) (AvatarResponse, error) {
// Open the uploaded file
src, err := req.File.Open()
if err != nil {
return AvatarResponse{}, echo.NewHTTPError(500, "Failed to open file")
}
defer src.Close()
// Decode image
img, _, err := image.Decode(src)
if err != nil {
return AvatarResponse{}, echo.NewHTTPError(400, "Invalid image format")
}
// Resize image (using your preferred library)
resized := resize(img, 200, 200)
// Save resized image
destPath := "/uploads/avatars/" + req.File.Filename
dst, err := os.Create(destPath)
if err != nil {
return AvatarResponse{}, echo.NewHTTPError(500, "Failed to create file")
}
defer dst.Close()
if err := jpeg.Encode(dst, resized, &jpeg.Options{Quality: 85}); err != nil {
return AvatarResponse{}, echo.NewHTTPError(500, "Failed to encode image")
}
return AvatarResponse{
URL: destPath,
Filename: req.File.Filename,
}, nil
}OpenAPI Documentation
File uploads are automatically documented in OpenAPI:
paths:
/avatar:
post:
summary: Upload avatar image
requestBody:
content:
multipart/form-data:
schema:
type: object
properties:
avatar:
type: string
format: binary
required:
- avatar
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/AvatarResponse'CLI Generator
Generate upload handler boilerplate:
echonext generate upload avatarThis creates:
internal/upload/avatar/
├── handler.go # Upload handler with validation
└── dto.go # Request/Response typesBest Practices
- Validate file types - Always validate MIME types and extensions
- Limit file sizes - Set appropriate size limits to prevent abuse
- Use unique filenames - Generate UUIDs or hashes to prevent overwrites
- Scan for malware - Consider integrating virus scanning for user uploads
- Store securely - Use cloud storage (S3, GCS) for production
- Set proper permissions - Ensure upload directories have correct permissions
Error Handling
func uploadWithErrors(c echo.Context, req AvatarRequest) (AvatarResponse, error) {
// Check file size
if req.File.Size > 5<<20 { // 5MB
return AvatarResponse{}, echo.NewHTTPError(400, "File too large (max 5MB)")
}
// Check content type
allowedTypes := map[string]bool{
"image/jpeg": true,
"image/png": true,
}
if !allowedTypes[req.File.ContentType] {
return AvatarResponse{}, echo.NewHTTPError(400, "Invalid file type")
}
// Save file
if err := req.File.SaveTo("/uploads/" + req.File.Filename); err != nil {
return AvatarResponse{}, echo.NewHTTPError(500, "Failed to save file")
}
return AvatarResponse{URL: "/uploads/" + req.File.Filename}, nil
}Example Project
See examples/upload-demo/ for a complete working example with:
- Single and multiple file uploads
- File validation
- Preview interface
- Health check endpoint
- Swagger documentation