Skip to main content

Gin File Uploads

Introduction​

File uploading is an essential feature for many web applications. Whether you're building a social media platform that allows users to share photos, a document management system, or a simple profile picture uploader, you need to know how to handle file uploads securely and efficiently.

In this tutorial, we'll explore how to implement file uploads in Gin, a popular web framework for Go. We'll cover single file uploads, multiple file uploads, file validation, and examine practical examples.

Setting Up Your Gin Project​

Before we dive into handling file uploads, make sure you have Gin installed:

go
go get -u github.com/gin-gonic/gin

Let's create a basic Gin server to work with:

go
package main

import (
"net/http"
"github.com/gin-gonic/gin"
)

func main() {
router := gin.Default()

// We'll add our file upload handlers here

router.Run(":8080")
}

Single File Upload​

Let's start with the most common case: uploading a single file.

Basic Implementation​

Here's how to handle a single file upload in Gin:

go
func main() {
router := gin.Default()

// Set a lower memory limit for multipart forms (default is 32 MiB)
router.MaxMultipartMemory = 8 << 20 // 8 MiB

router.POST("/upload", func(c *gin.Context) {
// Single file
file, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}

// Save the file to a specific location
dst := "./uploads/" + file.Filename
if err := c.SaveUploadedFile(file, dst); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": err.Error(),
})
return
}

c.JSON(http.StatusOK, gin.H{
"message": "File uploaded successfully",
"filename": file.Filename,
"size": file.Size,
})
})

router.Run(":8080")
}

HTML Form for File Upload​

Here's a simple HTML form to test the file upload:

html
<!DOCTYPE html>
<html>
<head>
<title>Upload Files</title>
</head>
<body>
<h1>Upload Single File</h1>
<form action="http://localhost:8080/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" value="Upload" />
</form>
</body>
</html>

When you submit this form, the file will be uploaded to the server, and you should receive a JSON response confirming the successful upload.

Multiple File Uploads​

In many applications, you might want to allow users to upload multiple files at once. Gin makes this process straightforward:

go
func main() {
router := gin.Default()
router.MaxMultipartMemory = 8 << 20 // 8 MiB

router.POST("/upload-multiple", func(c *gin.Context) {
// Multipart form
form, err := c.MultipartForm()
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}

files := form.File["files"]
fileInfos := []gin.H{}

for _, file := range files {
// Save each file
dst := "./uploads/" + file.Filename
if err := c.SaveUploadedFile(file, dst); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": err.Error(),
})
return
}

fileInfos = append(fileInfos, gin.H{
"filename": file.Filename,
"size": file.Size,
})
}

c.JSON(http.StatusOK, gin.H{
"message": fmt.Sprintf("%d files uploaded successfully", len(files)),
"files": fileInfos,
})
})

router.Run(":8080")
}

The HTML form for multiple file uploads would look like this:

html
<!DOCTYPE html>
<html>
<head>
<title>Upload Files</title>
</head>
<body>
<h1>Upload Multiple Files</h1>
<form action="http://localhost:8080/upload-multiple" method="post" enctype="multipart/form-data">
<input type="file" name="files" multiple />
<input type="submit" value="Upload" />
</form>
</body>
</html>

File Validation​

When handling file uploads, validation is crucial to ensure security and proper functionality. Let's implement some basic validation:

  1. File size limits
  2. File type validation
  3. Custom file naming
go
func main() {
router := gin.Default()
router.MaxMultipartMemory = 8 << 20 // 8 MiB

router.POST("/upload-with-validation", func(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

// 1. Validate file size (max 5 MB)
if file.Size > 5<<20 {
c.JSON(http.StatusBadRequest, gin.H{"error": "File is too large (max 5MB)"})
return
}

// 2. Validate file type by checking extension
ext := filepath.Ext(file.Filename)
allowedExts := map[string]bool{
".jpg": true, ".jpeg": true, ".png": true, ".gif": true,
}

if !allowedExts[strings.ToLower(ext)] {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Only image files (jpg, jpeg, png, gif) are allowed",
})
return
}

// 3. Generate a unique filename to prevent overwriting
filename := uuid.New().String() + ext
dst := "./uploads/" + filename

if err := c.SaveUploadedFile(file, dst); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

c.JSON(http.StatusOK, gin.H{
"message": "File uploaded successfully with validation",
"original_filename": file.Filename,
"saved_as": filename,
})
})

router.Run(":8080")
}

For the UUID generation, you'll need to import:

go
import (
// other imports
"github.com/google/uuid"
"path/filepath"
"strings"
)

And install the package:

bash
go get github.com/google/uuid

Practical Examples​

Let's explore a couple of real-world examples to demonstrate file uploads in action.

Profile Picture Upload​

This example shows how to implement a profile picture upload feature:

go
func main() {
router := gin.Default()
router.MaxMultipartMemory = 8 << 20

// Serve static files from the uploads directory
router.Static("/profile-pictures", "./uploads/profiles")

router.POST("/update-profile", func(c *gin.Context) {
// Get user ID (in a real app, this would come from authentication)
userID := c.PostForm("user_id")
if userID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "User ID is required"})
return
}

// Get profile picture
file, err := c.FormFile("profile_picture")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

// Validate image file
ext := filepath.Ext(file.Filename)
allowedExts := map[string]bool{
".jpg": true, ".jpeg": true, ".png": true,
}

if !allowedExts[strings.ToLower(ext)] {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Only image files (jpg, jpeg, png) are allowed",
})
return
}

// Create uploads/profiles directory if it doesn't exist
os.MkdirAll("./uploads/profiles", os.ModePerm)

// Save with userID to easily identify the profile picture
filename := userID + ext
dst := "./uploads/profiles/" + filename

if err := c.SaveUploadedFile(file, dst); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

c.JSON(http.StatusOK, gin.H{
"message": "Profile picture updated successfully",
"profile_url": "/profile-pictures/" + filename,
})
})

router.Run(":8080")
}

Document Management System​

Here's a more complex example for a document management system that handles multiple file types:

go
func main() {
router := gin.Default()
router.MaxMultipartMemory = 16 << 20 // 16 MiB

// Serve static files
router.Static("/documents", "./uploads/documents")

router.POST("/upload-document", func(c *gin.Context) {
// Get document metadata
docType := c.PostForm("type")
category := c.PostForm("category")

if docType == "" || category == "" {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Document type and category are required",
})
return
}

file, err := c.FormFile("document")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

// Validate file type based on document type
ext := strings.ToLower(filepath.Ext(file.Filename))

var allowedExts map[string]bool

switch docType {
case "contract":
allowedExts = map[string]bool{".pdf": true, ".docx": true}
case "image":
allowedExts = map[string]bool{".jpg": true, ".jpeg": true, ".png": true}
case "spreadsheet":
allowedExts = map[string]bool{".xlsx": true, ".csv": true}
default:
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid document type"})
return
}

if !allowedExts[ext] {
c.JSON(http.StatusBadRequest, gin.H{
"error": "File type not allowed for this document type",
})
return
}

// Create directory structure
dirPath := fmt.Sprintf("./uploads/documents/%s/%s", category, docType)
os.MkdirAll(dirPath, os.ModePerm)

// Generate unique filename
filename := uuid.New().String() + ext
dst := dirPath + "/" + filename

if err := c.SaveUploadedFile(file, dst); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

// In a real system, you might save metadata to a database here

c.JSON(http.StatusOK, gin.H{
"message": "Document uploaded successfully",
"document_url": fmt.Sprintf("/documents/%s/%s/%s", category, docType, filename),
"original_name": file.Filename,
"document_type": docType,
"category": category,
})
})

router.Run(":8080")
}

Best Practices for File Uploads​

When implementing file uploads, consider these best practices:

  1. Always validate files: Check file types, sizes, and content to prevent security issues.
  2. Use unique filenames: Prevent accidental file overwrites by generating unique identifiers.
  3. Set upload limits: Use MaxMultipartMemory to control memory usage during uploads.
  4. Structure your upload directories: Organize files in a logical structure for easy management.
  5. Consider using cloud storage: For production apps, consider using services like AWS S3 or Google Cloud Storage.

Summary​

In this tutorial, we've covered how to handle file uploads in the Gin web framework, including:

  • Setting up basic single file uploads
  • Implementing multiple file uploads
  • Adding file validation (size, type)
  • Creating practical examples for profile pictures and document management

File uploads are a critical component of many web applications. By understanding how to implement them properly in Gin, you can build more robust and feature-rich web applications.

Additional Resources​

Exercises​

  1. Modify the profile picture upload example to resize images to a standard size before saving.
  2. Implement progress tracking for large file uploads using WebSockets.
  3. Create a file uploading service that can handle both local storage and cloud storage (AWS S3).
  4. Add virus scanning functionality to the document management system (hint: use a third-party API or library).
  5. Implement a file chunking system for large file uploads to improve reliability.


If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)