DEV Community

ahmet gedik
ahmet gedik

Posted on

High-Performance Video Streaming API in Go

Why Go for a Video API

When DailyWatch needed an API that could handle thousands of concurrent requests with minimal latency, Go was a natural fit. Its standard library includes a production-grade HTTP server, goroutines handle concurrency without callback hell, and a single compiled binary simplifies deployment.

Basic Server Setup

Go's net/http is all you need — no framework required:

package main

import (
    "database/sql"
    "encoding/json"
    "log"
    "net/http"
    "time"

    _ "github.com/lib/pq"
)

type Video struct {
    VideoID      string   `json:"video_id"`
    Title        string   `json:"title"`
    Channel      string   `json:"channel_title"`
    Views        int64    `json:"views"`
    Likes        int64    `json:"likes"`
    ThumbnailURL string   `json:"thumbnail_url"`
    Regions      []string `json:"regions"`
}

type Server struct {
    db     *sql.DB
    router *http.ServeMux
}

func NewServer(db *sql.DB) *Server {
    s := &Server{db: db, router: http.NewServeMux()}
    s.routes()
    return s
}

func (s *Server) routes() {
    s.router.HandleFunc("GET /api/trending/{region}", s.handleTrending)
    s.router.HandleFunc("GET /api/search", s.handleSearch)
    s.router.HandleFunc("GET /api/video/{id}", s.handleVideoDetail)
    s.router.HandleFunc("GET /health", s.handleHealth)
}

func main() {
    db, err := sql.Open("postgres", "host=localhost dbname=dailywatch sslmode=disable")
    if err != nil {
        log.Fatal(err)
    }
    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(10)
    db.SetConnMaxLifetime(5 * time.Minute)
    defer db.Close()

    srv := NewServer(db)

    server := &http.Server{
        Addr:         ":8080",
        Handler:      srv.router,
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
        IdleTimeout:  120 * time.Second,
    }

    log.Println("Starting server on :8080")
    log.Fatal(server.ListenAndServe())
}
Enter fullscreen mode Exit fullscreen mode

Connection pooling is built into database/sql. Setting MaxOpenConns, MaxIdleConns, and ConnMaxLifetime prevents connection leaks and keeps the pool healthy under load.

Request Handlers

func (s *Server) handleTrending(w http.ResponseWriter, r *http.Request) {
    region := r.PathValue("region")
    if len(region) != 2 {
        http.Error(w, "invalid region code", http.StatusBadRequest)
        return
    }

    rows, err := s.db.QueryContext(r.Context(), `
        SELECT v.video_id, v.title, v.channel_title, v.views, v.likes,
               v.thumbnail_url, array_agg(vr.region) as regions
        FROM videos v
        JOIN video_regions vr ON v.video_id = vr.video_id
        WHERE vr.region = $1
        GROUP BY v.video_id
        ORDER BY v.views DESC
        LIMIT 50
    `, region)
    if err != nil {
        http.Error(w, "database error", http.StatusInternalServerError)
        log.Printf("trending query error: %v", err)
        return
    }
    defer rows.Close()

    var videos []Video
    for rows.Next() {
        var v Video
        var regions []byte
        if err := rows.Scan(&v.VideoID, &v.Title, &v.Channel,
            &v.Views, &v.Likes, &v.ThumbnailURL, &regions); err != nil {
            continue
        }
        json.Unmarshal(regions, &v.Regions)
        videos = append(videos, v)
    }

    w.Header().Set("Content-Type", "application/json")
    w.Header().Set("Cache-Control", "public, max-age=3600")
    json.NewEncoder(w).Encode(videos)
}
Enter fullscreen mode Exit fullscreen mode

Using r.Context() in the database query means if the client disconnects, the query cancels automatically. This prevents wasted database work on abandoned requests.

Middleware Chain

Middleware in Go is just function composition:

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        wrapped := &statusWriter{ResponseWriter: w, status: 200}
        next.ServeHTTP(wrapped, r)
        log.Printf("%s %s %d %s", r.Method, r.URL.Path,
            wrapped.status, time.Since(start))
    })
}

func corsMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "https://dailywatch.video")
        w.Header().Set("Access-Control-Allow-Methods", "GET")
        if r.Method == "OPTIONS" {
            w.WriteHeader(http.StatusNoContent)
            return
        }
        next.ServeHTTP(w, r)
    })
}

type statusWriter struct {
    http.ResponseWriter
    status int
}

func (w *statusWriter) WriteHeader(status int) {
    w.status = status
    w.ResponseWriter.WriteHeader(status)
}
Enter fullscreen mode Exit fullscreen mode

Stack them in main():

handler := loggingMiddleware(corsMiddleware(srv.router))
Enter fullscreen mode Exit fullscreen mode

Benchmarks

We benchmarked the Go API serving trending feeds for DailyWatch with wrk:

wrk -t4 -c100 -d30s http://localhost:8080/api/trending/US

Requests/sec:  12,450
Avg Latency:   3.2ms
P99 Latency:   12ms
Memory Usage:  ~18MB
Enter fullscreen mode Exit fullscreen mode

For comparison, our PHP API handling the same query averages 45ms per request. The Go version is 14x faster per request and uses a fraction of the memory. The compiled binary is 8MB — no runtime dependencies to install.

Graceful Shutdown

Production servers need clean shutdown:

quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

go func() {
    <-quit
    log.Println("Shutting down...")
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    server.Shutdown(ctx)
}()
Enter fullscreen mode Exit fullscreen mode

This lets in-flight requests complete before stopping, preventing dropped connections during deploys.


This article is part of the Building DailyWatch series. Check out DailyWatch to see these techniques in action.

Top comments (0)