Building an Image Upload API in Go

I recently started learning Go (a.k.a. Golang) with the specific goal of writing backend APIs.
Go is a lightweight, compiled, and blazing-fast language.
For this project-based learning session, I decided to build something practical: a backend API to handle image uploads and store them locally.
Here’s the high-level process:
User uploads an image and metadata (like title and description) from the frontend via
multipart/form-data.The data is sent to an exposed API endpoint.
The server parses the request, validates the file, and stores it.
Project Structure
I wanted the codebase to be simple but easy to scale, so I split it into cmd and internal folders:
image-api/
cmd/
server/
server.go
internal/
handlers/
handlers.go
utils/
utils.go
cmd/server/server.go– Entry point. Sets up the HTTP server, defines API endpoints, and handles CORS.internal/handlers/handlers.go– Contains handler functions for each API route.internal/utils/utils.go– Utility functions (like file type validation).
Setting Up the Server (server.go)
package main
import (
"image_api/internal/handlers"
"log"
"net/http"
)
func main() {
http.HandleFunc("/image-upload", withCORS(handlers.HandleImageUpload))
http.HandleFunc("/delete", withCORS(handlers.HandleImageDelete))
http.HandleFunc("/update", withCORS(handlers.HandleImageUpdate))
http.HandleFunc("/image", withCORS(handlers.HandleImageUpdate))
log.Println("Listening on port: 8282")
err := http.ListenAndServe("localhost:8282", nil)
if err != nil {
log.Fatal(err)
}
}
func withCORS(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
h(w, r)
}
}
The withCORS wrapper ensures requests from different origins don’t fail with CORS errors — essential when connecting your API to a frontend.
Handling Image Uploads (handlers.go)
The heart of the project is the image upload handler:
func HandleImageUpload(w http.ResponseWriter, r *http.Request) {
// Limit upload size to 10MB
err := r.ParseMultipartForm(10 << 20)
if err != nil {
http.Error(w, "File is too big or invalid format", http.StatusBadRequest)
return
}
// Retrieve the file from form data
file, handler, err := r.FormFile("file")
if err != nil {
http.Error(w, "Could not read file", http.StatusBadRequest)
return
}
defer file.Close()
// Validate file type
fileBytes, err := io.ReadAll(file)
if err != nil {
http.Error(w, "Invalid file", http.StatusBadRequest)
return
}
if !utils.IsValidFileType(fileBytes) {
http.Error(w, "Invalid file type", http.StatusUnsupportedMediaType)
return
}
// Get additional form fields
title := r.FormValue("title")
description := r.FormValue("description")
uploadDir := `C:\Users\USER\GolandProjects\image_api\uploads`
if err := os.MkdirAll(uploadDir, 0755); err != nil {
http.Error(w, "Could not create upload directory", http.StatusInternalServerError)
return
}
// Save file to disk
filePath := filepath.Join(uploadDir, handler.Filename)
dst, err := os.Create(filePath)
if err != nil {
http.Error(w, "Could not save file", http.StatusInternalServerError)
return
}
defer dst.Close()
io.Copy(dst, bytes.NewReader(fileBytes))
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Uploaded: %s\nTitle: %s\nDescription: %s", handler.Filename, title, description)
}
Key points:
Size limit prevents huge uploads from consuming memory.
File type validation ensures only images are accepted.
os.MkdirAllguarantees the upload directory exists.Metadata capture allows storing more than just the image.
Utility Functions (utils.go)
func IsValidFileType(file []byte) bool {
fileType := http.DetectContentType(file)
return strings.HasPrefix(fileType, "image/")
}
This small function makes sure the uploaded file really is an image — no disguised .exe files slipping in.
Running the API
Clone or create the project.
Run:
go run ./cmd/serverSend a request via Postman or a frontend form with:
File field:
fileMetadata fields:
title,description
With this small project, I not only learned about Go’s net/http package but also got comfortable with handling multipart/form-data, file validation, and CORS.
It’s a solid first step toward building more complex APIs in Go.


