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
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's just the basics. If you need more deep learning about Go/Golang, you can take the following cheap course:
- Go - The Complete Guide
- NEW-Comprehensive Go Bootcamp with gRPC and Protocol Buffers
- Backend Master Class [Golang + Postgres + Kubernetes + gRPC]
- Complete Microservices with Go
- Backend Engineering with Go
- Introduction to AI and Machine Learning with Go (Golang)
- Working with Concurrency in Go (Golang)
- Introduction to Testing in Go (Golang)
- Design Patterns in Go
- Go Bootcamp: Master Golang with 1000+ Exercises and Projects
Thanks!