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 [email protected]. I’d greatly appreciate your feedback! :)