Build a File Upload API in Go with Gin Framework and S3 or Local Storage

by Didin J. on Jun 14, 2025 Build a File Upload API in Go with Gin Framework and S3 or Local Storage

Build a file upload API in Go using Gin with support for Amazon S3 and local storage. Secure, flexible, and easy to integrate with any frontend.

In this tutorial, you’ll learn how to build a simple and secure file upload API in Go using the Gin web framework. We’ll support two types of storage: Amazon S3 for cloud storage and local file system for development or offline use. This flexible setup allows you to switch storage backends with minimal changes.

Whether you're building an image uploader, document manager, or any system that handles file uploads, this tutorial will give you a solid foundation.


Prerequisites

  • Go 1.18+

  • Basic knowledge of Go and Gin

  • AWS account (for S3 setup)

  • AWS SDK v2 for Go

  • A REST client like Postman or cURL


Project Setup

1. Initialize the Go Module

mkdir go-upload-api
cd go-upload-api
go mod init github.com/yourusername/go-upload-api

2. Install Dependencies

go get github.com/gin-gonic/gin
go get github.com/aws/aws-sdk-go-v2
go get github.com/aws/aws-sdk-go-v2/config
go get github.com/aws/aws-sdk-go-v2/service/s3

3. Directory Structure

Build a File Upload API in Go with Gin Framework and S3 or Local Storage - directory

Configuration

config/config.go

package config

import "os"

var (
    StorageType = os.Getenv("STORAGE_TYPE") // "s3" or "local"

    S3Region    = os.Getenv("AWS_REGION")
    S3Bucket    = os.Getenv("AWS_BUCKET")
    S3AccessKey = os.Getenv("AWS_ACCESS_KEY_ID")
    S3SecretKey = os.Getenv("AWS_SECRET_ACCESS_KEY")

    UploadPath = "./uploads" // for local storage
)

You can use .env files with godotenv if desired.

Gin Server and Routes

main.go

package main

import (
	"github.com/gin-gonic/gin"
	"github.com/yourusername/go-upload-api/handlers"
)

func main() {
	router := gin.Default()
	router.POST("/upload", handlers.UploadHandler)
	router.Run(":8080")
}

Upload Handler

handlers/upload.go

package handlers

import (
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/yourusername/go-upload-api/storage"
)

func UploadHandler(c *gin.Context) {
	file, err := c.FormFile("file")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "File is required"})
		return
	}

	src, err := file.Open()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to open file"})
		return
	}
	defer src.Close()

	url, err := storage.UploadFile(file.Filename, src)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{"url": url})
}

Storage Layer

storage/interface.go (optional for abstraction)

package storage

import (
    "io"
)

var UploadFile func(string, io.Reader) (string, error)

storage/local.go

package storage

import (
	"fmt"
	"io"
	"os"
	"path/filepath"

	"github.com/yourusername/go-upload-api/config"
)

func init() {
	if config.StorageType == "local" {
		UploadFile = uploadLocal
	}
}

func uploadLocal(filename string, file io.Reader) (string, error) {
	path := filepath.Join(config.UploadPath, filename)
	os.MkdirAll(config.UploadPath, os.ModePerm)

	out, err := os.Create(path)
	if err != nil {
		return "", err
	}
	defer out.Close()

	_, err = io.Copy(out, file)
	if err != nil {
		return "", err
	}

	return fmt.Sprintf("/uploads/%s", filename), nil
}

storage/s3.go

package storage

import (
	"context"
	"fmt"
	"io"

	"github.com/aws/aws-sdk-go-v2/aws"
	awsConfig "github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/s3"
	"github.com/yourusername/go-upload-api/config"
)

var s3Client *s3.Client

func init() {
	if config.StorageType == "s3" {
		cfg, err := awsConfig.LoadDefaultConfig(context.TODO())
		if err != nil {
			panic(err)
		}
		s3Client = s3.NewFromConfig(cfg)
		UploadFile = uploadS3
	}
}

func uploadS3(filename string, file io.Reader) (string, error) {
	_, err := s3Client.PutObject(context.TODO(), &s3.PutObjectInput{
		Bucket: aws.String(config.S3Bucket),
		Key:    aws.String(filename),
		Body:   file,
		ACL:    "public-read",
	})
	if err != nil {
		return "", err
	}

	return fmt.Sprintf("https://%s.s3.%s.amazonaws.com/%s", config.S3Bucket, config.S3Region, filename), nil
}

Run the Go File Upload API

Once you've completed the setup (main.go, handlers, storage, and config), you can run the app like this:

1. Set Environment Variables

For Local Storage (e.g., during development):

export STORAGE_TYPE=local

For S3 Storage (example):

export STORAGE_TYPE=s3
export AWS_REGION=us-east-1
export AWS_BUCKET=your-bucket-name
export AWS_ACCESS_KEY_ID=your-access-key
export AWS_SECRET_ACCESS_KEY=your-secret-key

You can also put these in a .env file and use github.com/joho/godotenv to load them.

2. Install godotenv

go get github.com/joho/godotenv

3. Create a .env File in Your Root Directory

# .env
STORAGE_TYPE=s3

AWS_REGION=us-east-1
AWS_BUCKET=your-s3-bucket-name
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key

UPLOAD_PATH=./uploads

4. Load .env in main.go

Modify main.go to load the .env file before using os.Getenv.

package main

import (
    "log"

    "github.com/joho/godotenv"
    "github.com/gin-gonic/gin"
    "github.com/yourusername/go-upload-api/handlers"
)

func main() {
    // Load .env file
    if err := godotenv.Load(); err != nil {
        log.Println("No .env file found")
    }

    router := gin.Default()
    router.POST("/upload", handlers.UploadHandler)
    router.Run(":8080")
}

Now, all your environment variables like STORAGE_TYPE, AWS_BUCKET, etc., will be loaded automatically from .env.

5. Run the Server

Just run your app normally:

go run main.go

If it .env exists, it will override your shell environment variables. By default, this starts the server at:

http://localhost:8080

Conclusion

In this tutorial, you learned how to:

  • Set up a file upload API using Go and Gin

  • Upload files to Amazon S3 or local storage

  • Structure a modular and maintainable Go project

This flexible approach enables easy expansion into file validation, database-based metadata storage, or integration with a frontend UI.

You can get the full source code from our GitHub.

That is just the basics. If you need more deep learning about the Golang (Go) language and frameworks, you can take the following cheap course:

Thanks!