go 46 lines · 8 steps

Handling multi-file uploads in Gin

A Gin handler parses a multipart form, validates each file's size, saves them to disk, and reports what landed.

Explained by highlit
1func UploadFiles(c *gin.Context) {
2 form, err := c.MultipartForm()
3 if err != nil {
4 c.JSON(http.StatusBadRequest, gin.H{"error": "invalid multipart form"})
5 return
6 }
7 
8 files := form.File["documents"]
9 if len(files) == 0 {
10 c.JSON(http.StatusBadRequest, gin.H{"error": "no files provided"})
11 return
12 }
13 
14 const maxSize = 10 << 20
15 uploaded := make([]gin.H, 0, len(files))
16 
17 for _, file := range files {
18 if file.Size > maxSize {
19 c.JSON(http.StatusRequestEntityTooLarge, gin.H{
20 "error": fmt.Sprintf("%s exceeds the 10MB limit", file.Filename),
21 })
22 return
23 }
24 
25 name := fmt.Sprintf("%d-%s", time.Now().UnixNano(), filepath.Base(file.Filename))
26 dst := filepath.Join("uploads", name)
27 
28 if err := c.SaveUploadedFile(file, dst); err != nil {
29 c.JSON(http.StatusInternalServerError, gin.H{
30 "error": fmt.Sprintf("failed to save %s", file.Filename),
31 })
32 return
33 }
34 
35 uploaded = append(uploaded, gin.H{
36 "original": file.Filename,
37 "stored": name,
38 "size": file.Size,
39 })
40 }
41 
42 c.JSON(http.StatusCreated, gin.H{
43 "count": len(uploaded),
44 "files": uploaded,
45 })
46}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Validate the whole request shape before touching any file so bad requests fail fast with clear status codes.
  2. 2Renaming uploads with a timestamp and filepath.Base prevents collisions and strips client-supplied path components.
  3. 3Accumulating results in a slice lets you return a single structured summary after all files succeed.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Handling multi-file uploads in Gin — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code