Skip to main content

Command Palette

Search for a command to run...

Building an Image Upload API in Go

Updated
3 min read
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:

  1. User uploads an image and metadata (like title and description) from the frontend via multipart/form-data.

  2. The data is sent to an exposed API endpoint.

  3. 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.MkdirAll guarantees 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

  1. Clone or create the project.

  2. Run:

     go run ./cmd/server
    
  3. Send a request via Postman or a frontend form with:

    • File field: file

    • Metadata 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.