APIs for File Conversion: Examples in Golang

Ahmed RazaAhmed Raza
4 min read

File conversion is an essential feature in many applications, enabling users to transform files between various formats, such as converting images from PNG to JPEG or documents from DOCX to PDF. Automating this process through an API can streamline workflows and integrate seamlessly with other software.

This guide delves into designing, implementing, and using a file conversion API, illustrated with a practical example built in Golang.


Key Features of a File Conversion API

To build a robust file conversion API, it must incorporate the following features:

  1. Format Support: Accept and convert between multiple formats (e.g., PNG to JPEG, TXT to PDF).

  2. Secure Upload and Download: Ensure files are securely uploaded and stored.

  3. Scalability: Handle large files and high-throughput environments efficiently.

  4. Error Handling: Gracefully manage unsupported formats or conversion failures.

  5. Authentication: Restrict access using API keys or tokens.


API Design

A well-designed API simplifies the integration process. Below is a proposed design for the file conversion API.

MethodEndpointDescription
POST/convertUpload a file and specify conversion parameters.
GET/status/{task_id}Check the status of a specific conversion task.
GET/download/{task_id}Download the converted file.

Request and Response Examples

  1. Conversion Request (POST /convert)

    Request:

     {
       "input_format": "png",
       "output_format": "jpg"
     }
    

    File is sent as multipart/form-data.

    Response:

     {
       "task_id": "123456",
       "status": "processing"
     }
    
  2. Check Conversion Status (GET /status/{task_id})

    Response:

     {
       "task_id": "123456",
       "status": "completed",
       "download_url": "/download/123456"
     }
    
  3. Download File (GET /download/{task_id})

    Returns the converted file as a response.


Building the API in Golang

To implement the API, Golang offers robust libraries for handling HTTP requests, file uploads, and background tasks. Below is a detailed walkthrough.

Dependencies

This implementation relies on the following libraries:

  • Mux โ€“ A powerful routing library for handling HTTP requests.
    ๐Ÿ“Œ github.com/gorilla/mux

  • os/exec โ€“ Provides functions for executing system commands, useful for file conversion tasks.
    ๐Ÿ“Œ pkg.go.dev/os/exec

  • io/ioutil โ€“ Used for file handling (โš ๏ธ Deprecated since Go 1.16; use io and os instead).
    ๐Ÿ“Œ pkg.go.dev/io/ioutil

  • uuid โ€“ Generates unique task IDs, ensuring reliable identification.
    ๐Ÿ“Œ github.com/google/uuid

Install these dependencies:

go get github.com/gorilla/mux
go get github.com/google/uuid

Code Implementation

Here is a simplified implementation:

package main

import (
    "encoding/json"
    "fmt"
    "github.com/gorilla/mux"
    "github.com/google/uuid"
    "io"
    "net/http"
    "os"
    "os/exec"
    "sync"
)

type ConversionTask struct {
    TaskID       string `json:"task_id"`
    InputFormat  string `json:"input_format"`
    OutputFormat string `json:"output_format"`
    Status       string `json:"status"`
    FilePath     string
}

var (
    taskStore = sync.Map{}
)

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/convert", handleConvert).Methods("POST")
    r.HandleFunc("/status/{task_id}", handleStatus).Methods("GET")
    r.HandleFunc("/download/{task_id}", handleDownload).Methods("GET")

    fmt.Println("Starting server on :8080")
    http.ListenAndServe(":8080", r)
}

func handleConvert(w http.ResponseWriter, r *http.Request) {
    r.ParseMultipartForm(10 << 20) // Limit upload size to 10MB

    inputFormat := r.FormValue("input_format")
    outputFormat := r.FormValue("output_format")

    file, _, err := r.FormFile("file")
    if err != nil {
        http.Error(w, "Failed to read file", http.StatusBadRequest)
        return
    }
    defer file.Close()

    tempFile, err := os.CreateTemp("uploads", "input-*."+inputFormat)
    if err != nil {
        http.Error(w, "Failed to save file", http.StatusInternalServerError)
        return
    }
    defer tempFile.Close()
    io.Copy(tempFile, file)

    taskID := uuid.New().String()
    task := ConversionTask{
        TaskID:       taskID,
        InputFormat:  inputFormat,
        OutputFormat: outputFormat,
        Status:       "processing",
        FilePath:     tempFile.Name(),
    }
    taskStore.Store(taskID, task)

    go convertFile(task)

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]string{"task_id": taskID, "status": "processing"})
}

func convertFile(task ConversionTask) {
    outputFile := fmt.Sprintf("output-%s.%s", task.TaskID, task.OutputFormat)
    cmd := exec.Command("convert", task.FilePath, outputFile) // ImageMagick

    err := cmd.Run()
    if err != nil {
        task.Status = "failed"
    } else {
        task.Status = "completed"
        task.FilePath = outputFile
    }
    taskStore.Store(task.TaskID, task)
}

func handleStatus(w http.ResponseWriter, r *http.Request) {
    taskID := mux.Vars(r)["task_id"]

    value, ok := taskStore.Load(taskID)
    if !ok {
        http.Error(w, "Task not found", http.StatusNotFound)
        return
    }
    task := value.(ConversionTask)

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(task)
}

func handleDownload(w http.ResponseWriter, r *http.Request) {
    taskID := mux.Vars(r)["task_id"]

    value, ok := taskStore.Load(taskID)
    if !ok {
        http.Error(w, "Task not found", http.StatusNotFound)
        return
    }
    task := value.(ConversionTask)

    if task.Status != "completed" {
        http.Error(w, "File not ready", http.StatusBadRequest)
        return
    }

    http.ServeFile(w, r, task.FilePath)
}

Example Usage

  1. Upload and Convert a File

     curl -X POST -F "file=@input.png" -F "input_format=png" -F "output_format=jpg" http://localhost:8080/convert
    
  2. Check Conversion Status

     curl http://localhost:8080/status/<task_id>
    
  3. Download Converted File

     curl -O http://localhost:8080/download/<task_id>
    

Enhancements for Production

  1. Authentication: Secure endpoints using JWT or API keys.

  2. Persistent Storage: Use a database to store task details and file metadata.

  3. Distributed Processing: Implement task queues (e.g., RabbitMQ) for scaling.

  4. Logging and Monitoring: Add structured logging and integrate monitoring tools like Prometheus.

  5. Error Handling: Enhance error messages and retry mechanisms.


Conclusion

This implementation serves as a solid foundation for building a scalable file conversion API in Go. With further enhancements, such as support for additional file formats, authentication, and optimized performance, it can evolve into a production-ready solution for various use cases.

I hope you found this article helpful! Happy coding! ๐Ÿš€ Thanks for reading, and see you in my next article on BuggedMind! ๐Ÿ˜Š

0
Subscribe to my newsletter

Read articles from Ahmed Raza directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ahmed Raza
Ahmed Raza

Ahmed Raza is a versatile full-stack developer with extensive experience in building APIs through both REST and GraphQL. Skilled in Golang, he uses gqlgen to create optimized GraphQL APIs, alongside Redis for effective caching and data management. Ahmed is proficient in a wide range of technologies, including YAML, SQL, and MongoDB for data handling, as well as JavaScript, HTML, and CSS for front-end development. His technical toolkit also includes Node.js, React, Java, C, and C++, enabling him to develop comprehensive, scalable applications. Ahmed's well-rounded expertise allows him to craft high-performance solutions that address diverse and complex application needs.