fasthttp #

fasthttp adalah HTTP implementation low-level untuk Go yang dirancang untuk performa tinggi. Tidak seperti net/http standar, fasthttp menggunakan object pooling agresif, zero-allocation parsing, dan reuse buffer untuk mengurangi GC pressure. Hasilnya, fasthttp bisa melayani 100K+ request per detik pada hardware consumer-grade, jauh di atas net/http standar. Framework seperti Fiber dan Gear dibangun di atas fasthttp.

Tapi bekerja langsung dengan fasthttp itu berbeda dari framework. Kamu tidak punya router, middleware composition, atau context helper — semuanya harus kamu tulis atau pilih sendiri. Artikel ini membahas setup Docker Compose untuk local development fasthttp, dari Dockerfile, hot reload, hingga pattern routing kustom dan best practice.

Prasyarat #

Pastikan sudah terinstall:

  • Docker dan Docker Compose versi terbaru
  • Go 1.22+ (opsional, untuk development di host)
  • Git

Struktur project fasthttp:

my-fasthttp-app/
├── cmd/
│   └── server/
│       └── main.go
├── internal/
│   ├── router/
│   │   └── router.go
│   ├── handler/
│   │   ├── user.go
│   │   └── health.go
│   ├── middleware/
│   │   ├── logger.go
│   │   └── recover.go
│   ├── service/
│   │   └── user.go
│   └── model/
│       └── user.go
├── go.mod
├── go.sum
├── Dockerfile
├── Dockerfile.dev
├── docker-compose.yml
├── .air.toml
├── .env
└── .dockerignore

Apa yang Membuat fasthttp Berbeda #

Sebelum masuk ke teknis, pahami dulu posisi fasthttp di ekosistem Go.

Aspek net/http fasthttp
Throughput ~50K req/s 100K+ req/s
Object pooling Standar Agresif (zero-allocation)
API style Standar Method-rich, non-standard
Kompabilitas middleware 100% Tidak kompatibel tanpa adaptor
Type Handler http.Handler fasthttp.RequestHandler
Request/Response http.Request, http.ResponseWriter fasthttp.RequestCtx
Body parsing r.Body.Read() ctx.PostBody()
Header r.Header.Get() ctx.Request.Header.Peek()
Query r.URL.Query().Get() ctx.QueryArgs().Peek()

Performa tinggi datang dengan harga: API non-standard, dan banyak middleware ekosistem Go (OpenTelemetry, prometheus, dll) tidak kompatibel langsung. Untuk kebanyakan aplikasi, net/http (atau Gin/Chi) sudah cukup. Pakai fasthttp hanya kalau kamu butuh throughput ekstrim.

Dockerfile Multi-Stage #

# syntax=docker/dockerfile:1.6
FROM golang:1.22-alpine AS builder

WORKDIR /app

RUN apk add --no-cache git ca-certificates

COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /out/server ./cmd/server

FROM alpine:3.19

RUN apk add --no-cache ca-certificates tzdata
WORKDIR /app

RUN addgroup -S app && adduser -S app -G app
USER app:app

COPY --from=builder /out/server /app/server

EXPOSE 8080

ENTRYPOINT ["/app/server"]

Dockerfile untuk Development #

# Dockerfile.dev
FROM golang:1.22-alpine

WORKDIR /app

RUN apk add --no-cache git curl bash

RUN go install github.com/air-verse/air@latest

COPY go.mod go.sum ./
RUN go mod download

EXPOSE 8080

CMD ["air", "-c", ".air.toml"]

Konfigurasi Air (.air.toml):

root = "."
tmp_dir = "tmp"

[build]
  bin = "./tmp/main"
  cmd = "go build -o ./tmp/main ./cmd/server"
  delay = 1000
  exclude_dir = ["assets", "tmp", "vendor", "testdata"]
  include_dir = ["cmd", "internal"]
  include_ext = ["go", "yaml", "yml"]
  exclude_regex = ["_test.go"]
  exclude_unchanged = true

[log]
  time = true

docker-compose.yml #

services:
  api:
    build:
      context: .
      dockerfile: Dockerfile.dev
    image: fasthttp-app:dev
    container_name: fasthttp-api
    command: air -c .air.toml
    ports:
      - "8080:8080"
    volumes:
      - ./cmd:/app/cmd
      - ./internal:/app/internal
      - ./go.mod:/app/go.mod
      - ./go.sum:/app/go.sum
      - go-build:/app/tmp
    environment:
      - APP_ENV=development
      - SERVER_PORT=8080
      - DATABASE_URL=postgres://app:dev@db:5432/fasthttpapp?sslmode=disable
      - REDIS_URL=redis://cache:6379/0
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_healthy

  db:
    image: postgres:16-alpine
    container_name: fasthttp-db
    environment:
      - POSTGRES_USER=app
      - POSTGRES_PASSWORD=dev
      - POSTGRES_DB=fasthttpapp
    volumes:
      - db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d fasthttpapp"]
      interval: 10s
      timeout: 5s
      retries: 5
    ports:
      - "5432:5432"

  cache:
    image: redis:7-alpine
    container_name: fasthttp-cache
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3
    volumes:
      - cache-data:/data
    ports:
      - "6379:6379"

volumes:
  db-data:
  cache-data:
  go-build:

Contoh Aplikasi fasthttp #

Router Sederhana (internal/router/router.go):

fasthttp tidak punya router built-in, jadi kita tulis pattern matching sendiri. Untuk routing kompleks, tambahkan library github.com/fasthttp/router atau github.com/buaazp/fasthttprouter.

package router

import (
    "github.com/buaazp/fasthttprouter"
    "myapp/internal/handler"
    "myapp/internal/middleware"
)

func New(userHandler *handler.UserHandler, healthHandler *handler.HealthHandler) *fasthttprouter.Router {
    r := fasthttprouter.New()

    // Global middleware
    r.GlobalOPTIONS = middleware.CORSHandler

    // Health
    r.GET("/health", healthHandler.Liveness)
    r.GET("/healthz", healthHandler.Healthz)
    r.GET("/readyz", healthHandler.Readyz)

    // API v1
    r.POST("/api/v1/users", middleware.JSONContentType(userHandler.Create))
    r.GET("/api/v1/users/:id", middleware.JSONContentType(userHandler.Get))
    r.GET("/api/v1/users", middleware.JSONContentType(userHandler.List))

    return r
}

Handler (internal/handler/user.go):

package handler

import (
    "encoding/json"
    "errors"
    "strconv"

    "github.com/buaazp/fasthttprouter"
    "github.com/valyala/fasthttp"

    "myapp/internal/service"
)

type UserHandler struct {
    svc *service.UserService
}

func NewUserHandler(svc *service.UserService) *UserHandler {
    return &UserHandler{svc: svc}
}

type createUserRequest struct {
    Email    string `json:"email"`
    Name     string `json:"name"`
    Password string `json:"password"`
}

type errorResponse struct {
    Error string `json:"error"`
}

// Create: POST /api/v1/users
func (h *UserHandler) Create(ctx *fasthttp.RequestCtx) {
    var req createUserRequest
    if err := json.Unmarshal(ctx.PostBody(), &req); err != nil {
        ctx.SetStatusCode(fasthttp.StatusBadRequest)
        json.NewEncoder(ctx).Encode(errorResponse{Error: "invalid body"})
        return
    }
    if req.Email == "" || req.Name == "" || len(req.Password) < 8 {
        ctx.SetStatusCode(fasthttp.StatusBadRequest)
        json.NewEncoder(ctx).Encode(errorResponse{Error: "validation failed"})
        return
    }
    u, err := h.svc.Register(&ctx, req.Email, req.Name, req.Password)
    if err != nil {
        ctx.SetStatusCode(fasthttp.StatusBadRequest)
        json.NewEncoder(ctx).Encode(errorResponse{Error: err.Error()})
        return
    }
    ctx.SetStatusCode(fasthttp.StatusCreated)
    json.NewEncoder(ctx).Encode(u)
}

// Get: GET /api/v1/users/:id
func (h *UserHandler) Get(ctx *fasthttp.RequestCtx) {
    idStr := ctx.UserValue("id").(string)
    id, err := strconv.ParseUint(idStr, 10, 64)
    if err != nil {
        ctx.SetStatusCode(fasthttp.StatusBadRequest)
        json.NewEncoder(ctx).Encode(errorResponse{Error: "invalid id"})
        return
    }
    u, err := h.svc.Get(&ctx, id)
    if err != nil {
        ctx.SetStatusCode(fasthttp.StatusInternalServerError)
        json.NewEncoder(ctx).Encode(errorResponse{Error: err.Error()})
        return
    }
    if u == nil {
        ctx.SetStatusCode(fasthttp.StatusNotFound)
        json.NewEncoder(ctx).Encode(errorResponse{Error: "user not found"})
        return
    }
    json.NewEncoder(ctx).Encode(u)
}

// List: GET /api/v1/users?limit=10&offset=0
func (h *UserHandler) List(ctx *fasthttp.RequestCtx) {
    limit := 20
    offset := 0
    if v := ctx.QueryArgs().Peek("limit"); len(v) > 0 {
        if n, err := strconv.Atoi(string(v)); err == nil {
            limit = n
        }
    }
    if v := ctx.QueryArgs().Peek("offset"); len(v) > 0 {
        if n, err := strconv.Atoi(string(v)); err == nil {
            offset = n
        }
    }
    users, err := h.svc.List(&ctx, limit, offset)
    if err != nil {
        ctx.SetStatusCode(fasthttp.StatusInternalServerError)
        json.NewEncoder(ctx).Encode(errorResponse{Error: err.Error()})
        return
    }
    response := map[string]interface{}{
        "data":   users,
        "limit":  limit,
        "offset": offset,
    }
    json.NewEncoder(ctx).Encode(response)
}

Perhatikan beberapa perbedaan utama dengan net/http:

  • ctx.PostBody() untuk body, bukan r.Body.Read()
  • ctx.UserValue("id") untuk path parameter, bukan chi.URLParam()
  • ctx.QueryArgs().Peek("key") untuk query string, bukan r.URL.Query().Get("key")
  • ctx.SetStatusCode() untuk set status, bukan w.WriteHeader()
  • json.NewEncoder(ctx).Encode(...) langsung ke RequestCtx

Middleware #

Middleware di fasthttp mengikuti pola func(fasthttp.RequestHandler) fasthttp.RequestHandler. Ini sedikit berbeda dari net/http yang pakai func(http.Handler) http.Handler.

Logger Middleware (internal/middleware/logger.go):

package middleware

import (
    "log"
    "time"

    "github.com/valyala/fasthttp"
)

// Logger: log setiap request dengan method, path, status, dan latency
func Logger(h fasthttp.RequestHandler) fasthttp.RequestHandler {
    return func(ctx *fasthttp.RequestCtx) {
        start := time.Now()
        h(ctx)
        log.Printf(
            "%s %s -> %d (%v)",
            ctx.Method(),
            ctx.Path(),
            ctx.Response.StatusCode(),
            time.Since(start),
        )
    }
}

Recover Middleware (internal/middleware/recover.go):

package middleware

import (
    "fmt"
    "runtime/debug"

    "github.com/valyala/fasthttp"
)

func Recover(h fasthttp.RequestHandler) fasthttp.RequestHandler {
    return func(ctx *fasthttp.RequestCtx) {
        defer func() {
            if r := recover(); r != nil {
                log.Printf("panic: %v\n%s", r, debug.Stack())
                ctx.SetStatusCode(fasthttp.StatusInternalServerError)
                ctx.SetBodyString(`{"error":"internal server error"}`)
            }
        }()
        h(ctx)
    }
}

// JSONContentType: set Content-Type ke application/json
func JSONContentType(h fasthttp.RequestHandler) fasthttp.RequestHandler {
    return func(ctx *fasthttp.RequestCtx) {
        ctx.Response.Header.SetContentType("application/json")
        h(ctx)
    }
}

// CORSHandler: handle preflight dan set CORS header
func CORSHandler(ctx *fasthttp.RequestCtx) {
    ctx.Response.Header.Set("Access-Control-Allow-Origin", "*")
    ctx.Response.Header.Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
    ctx.Response.Header.Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
    if ctx.Method() == "OPTIONS" {
        ctx.SetStatusCode(fasthttp.StatusNoContent)
        return
    }
}

Main Server #

cmd/server/main.go:

package main

import (
    "context"
    "encoding/json"
    "log"
    "os"
    "os/signal"
    "syscall"
    "time"

    "github.com/valyala/fasthttp"
    "github.com/redis/go-redis/v9"
    "gorm.io/driver/postgres"
    "gorm.io/gorm"

    "myapp/internal/handler"
    "myapp/internal/middleware"
    "myapp/internal/model"
    "myapp/internal/router"
    "myapp/internal/service"
)

func main() {
    // Database dengan retry
    var db *gorm.DB
    var err error
    dsn := os.Getenv("DATABASE_URL")
    for i := 0; i < 30; i++ {
        db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
        if err == nil {
            break
        }
        log.Printf("waiting for db: %v", err)
        time.Sleep(2 * time.Second)
    }
    if err != nil {
        log.Fatal("db connection failed: ", err)
    }
    if err := db.AutoMigrate(&model.User{}); err != nil {
        log.Fatal("migrate failed: ", err)
    }

    // Redis
    rdb := redis.NewClient(&redis.Options{Addr: os.Getenv("REDIS_URL")})
    _ = rdb

    // Layers
    userRepo := newUserRepo(db)
    userSvc := service.NewUserService(userRepo)
    userHandler := handler.NewUserHandler(userSvc)
    healthHandler := handler.NewHealthHandler(db)

    // Router
    r := router.New(userHandler, healthHandler)

    // Apply global middleware (composed manual)
    handler := middleware.Recover(middleware.Logger(r.Handler))

    // Server
    server := &fasthttp.Server{
        Handler:            handler,
        Name:               "my-fasthttp-app",
        ReadTimeout:        10 * time.Second,
        WriteTimeout:       10 * time.Second,
        MaxConnsPerIP:      1000,
        MaxRequestsPerConn: 1000,
        Concurrency:        10000,
    }

    // Graceful shutdown
    go func() {
        log.Printf("listening on :8080")
        if err := server.ListenAndServe(":8080"); err != nil {
            log.Printf("server stopped: %v", err)
        }
    }()

    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
    log.Println("shutting down...")

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    if err := server.ShutdownWithContext(ctx); err != nil {
        log.Fatal(err)
    }
}

// Helper untuk inject dependency (sesuaikan dengan struktur repository kamu)
func newUserRepo(db *gorm.DB) *service.UserRepo {
    return service.NewUserRepo(db)
}

// Health handler
type healthHandler struct {
    db *gorm.DB
}

func NewHealthHandler(db *gorm.DB) *healthHandler {
    return &healthHandler{db: db}
}

func (h *healthHandler) Liveness(ctx *fasthttp.RequestCtx) {
    ctx.SetStatusCode(200)
    json.NewEncoder(ctx).Encode(map[string]string{"status": "ok"})
}

func (h *healthHandler) Healthz(ctx *fasthttp.RequestCtx) {
    sqlDB, err := h.db.DB()
    if err != nil {
        ctx.SetStatusCode(503)
        json.NewEncoder(ctx).Encode(map[string]string{"error": "db unavailable"})
        return
    }
    if err := sqlDB.PingContext(ctx); err != nil {
        ctx.SetStatusCode(503)
        json.NewEncoder(ctx).Encode(map[string]string{"error": "db ping failed"})
        return
    }
    ctx.SetStatusCode(200)
    json.NewEncoder(ctx).Encode(map[string]string{"status": "ok", "database": "up"})
}

func (h *healthHandler) Readyz(ctx *fasthttp.RequestCtx) {
    ctx.SetStatusCode(200)
    json.NewEncoder(ctx).Encode(map[string]string{"status": "ready"})
}

Server Tuning #

fasthttp punya banyak opsi tuning. Kunci performa ada di sini.

server := &fasthttp.Server{
    Handler:            handler,
    Name:               "myapp",
    ReadTimeout:        10 * time.Second,
    WriteTimeout:       10 * time.Second,
    IdleTimeout:        60 * time.Second,
    MaxConnsPerIP:      1000,    // max concurrent connection per IP
    MaxRequestsPerConn: 1000,    // close connection setelah N request
    Concurrency:        10000,   // max concurrent handler
    ReadBufferSize:     4096,    // buffer size untuk read
    WriteBufferSize:    4096,    // buffer size untuk write
    DisableKeepalive:   false,   // keep-alive aktif
    KeepHijackedConns:  false,   // close hijacked conns
    TCPKeepalive:       true,    // TCP keep-alive
}

Untuk beban tinggi, naikkan Concurrency dan MaxConnsPerIP. Untuk API latency-sensitive, turunkan ReadTimeout dan WriteTimeout.

Routing Library Populer #

Bikin router sendiri itu pekerjaan yang berulang. Beberapa library yang bisa kamu pakai:

Library Kelebihan Kekurangan
fasthttp/router Resmi, simple, regex-based Tidak mendukung parameter typed
buaazp/fasthttprouter Lightweight, cepat Tidak se-feature lengkap Go-chi
@fiber/router (dari Fiber) Kaya fitur Bergantung pada Fiber ecosystem
Custom trie Performa tertinggi Banyak kode sendiri

Untuk kebanyakan kasus, github.com/buaazp/fasthttprouter cukup. Ia mendukung parameter :id, wildcard *, dan middleware.

Build dan Run #

# Build
docker compose build

# Jalankan
docker compose up -d

# Lihat log
docker compose logs -f api

# Benchmark (optional)
docker compose exec api wrk -t4 -c100 -d10s http://localhost:8080/health

# Stop
docker compose down

Akses:

  • API: http://localhost:8080
  • PostgreSQL: localhost:5432
  • Redis: localhost:6379

Test:

# Register
curl -X POST http://localhost:8080/api/v1/users \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","name":"Eko","password":"password123"}'

# Get user
curl http://localhost:8080/api/v1/users/1

Benchmark dengan wrk #

Untuk mengukur throughput, pakai wrk di dalam container atau di host.

# Install wrk di container
docker compose exec api apk add wrk
docker compose exec api wrk -t4 -c100 -d10s http://localhost:8080/health

Output:

Running 10s test @ http://localhost:8080/health
  4 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.13ms    1.45ms  25.46ms   78.91%
    Req/Sec    11.43k     1.32k   14.21k    78.95%
  456832 requests in 10.05s, 65.78MB read
Requests/sec:  45465.23
Transfer/sec:      6.55MB

45K req/s untuk endpoint health sederhana. Untuk endpoint yang melakukan query database, throughput turun signifikan (tergantung latency DB).

Kapan fasthttp Tepat #

Pakai fasthttp langsung jika:
  ✓ Butuh throughput ekstrim (>50K req/s)
  ✓ Punya expertise untuk handle API non-standard
  ✓ Microservice high-throughput (gateway, proxy)
  ✓ Benchmark menunjukkan net/http jadi bottleneck

Pakai framework di atas fasthttp (Fiber, Gear) jika:
  ✓ Suka API Express-like
  ✓ Butuh ekosistem middleware
  ✓ Produktivitas lebih penting dari kontrol rendah

Pakai net/http / Gin / Chi jika:
  ✓ Aplikasi bisnis biasa
  ✓ Butuh kompatibilitas middleware ekosistem
  ✓ Tim Go baru (idiomatic Go lebih mudah dipelajari)
  ✓ Throughput 5-20K req/s sudah cukup

Untuk 90% aplikasi web, net/http atau Gin sudah lebih dari cukup. fasthttp unggul di use case spesifik: API gateway, real-time proxy, atau service yang memang di-benchmark untuk throughput tinggi.

Best Practice #

Cache Dependency Module #

Copy go.mod + go.sum dulu, baru source code. Build inkremental super cepat.

Pakai Air untuk Hot Reload #

Tanpa hot reload, Go terasa lambat. Air memantau file dan rebuild otomatis.

Hati-hati dengan Object Lifecycle #

fasthttp menggunakan RequestCtx yang di-reuse. Jangan simpan reference ke ctx setelah handler return. Untuk data yang perlu persist, copy ke struct terpisah.

Tuning Server untuk Beban #

Concurrency, MaxConnsPerIP, ReadTimeout, WriteTimeout — atur sesuai workload. Default cukup untuk kebanyakan kasus, tapi untuk high-throughput, naikkan Concurrency ke 10K+.

Pakai Connection Pool untuk Database #

GORM pakai database/sql di belakang layar. Set MaxOpenConns, MaxIdleConns, ConnMaxLifetime sesuai beban.

sqlDB, _ := db.DB()
sqlDB.SetMaxOpenConns(50)
sqlDB.SetMaxIdleConns(10)
sqlDB.SetConnMaxLifetime(time.Hour)

Healthcheck dengan DB Ping #

/healthz harus cek dependency. Return 503 kalau database down. /readyz return 200 kalau app siap.

Pakai Library Routing yang Matang #

Jangan tulis router sendiri kecuali kamu butuh fitur khusus. fasthttp/router atau buaazp/fasthttprouter sudah cukup.

Bind Mount Source Code di Dev #

Mount source code ke container. Air akan rebuild otomatis setiap save.

Troubleshooting #

Memory Leak #

fasthttp pakai object pooling. Kalau handler kamu menyimpan reference ke RequestCtx atau Response, itu akan ditahan di pool dan tidak pernah di-GC. Selalu copy data yang perlu persist.

Connection Refused #

Pakai depends_on: condition: service_healthy di Compose. Tambahkan retry loop di kode Go.

Server Timeout #

Naikkan ReadTimeout dan WriteTimeout di fasthttp.Server. Default 10s cukup untuk API normal, tapi untuk upload file besar, naikkan ke 60s+.

Routing Tidak Match #

fasthttprouter tidak mendukung method override atau implicit HEAD. Definisikan route eksplisit untuk method yang kamu butuh. Pakai r.GET, r.POST, dst.

CORS Preflight Gagal #

Set r.GlobalOPTIONS = middleware.CORSHandler agar preflight OPTIONS di-handle secara global.

Ringkasan #

  • fasthttp ideal untuk service Go yang butuh throughput ekstrim (>50K req/s).
  • fasthttp pakai API non-standardRequestCtx alih-alih http.Request/ResponseWriter. Tidak kompatibel langsung dengan middleware ekosistem Go.
  • Dockerfile multi-stage untuk production: builder stage dengan Go toolchain, runtime stage ramping. Image akhir < 20 MB.
  • Dockerfile.dev untuk development: full toolchain + Air untuk hot reload.
  • Hot reload dengan Air: pantau file .go, rebuild, restart. Konfigurasi lewat .air.toml.
  • Layer cache Go: copy go.mod + go.sum dulu, jalankan go mod download, baru copy source.
  • Middleware mengikuti pola func(fasthttp.RequestHandler) fasthttp.RequestHandler. Compose manual: Recover(Logger(handler)).
  • Routing library: github.com/buaazp/fasthttprouter untuk kebanyakan kasus. Pakai fasthttp/router resmi untuk yang lebih sederhana.
  • Server tuning: Concurrency, MaxConnsPerIP, ReadTimeout, WriteTimeout. Tune sesuai workload.
  • Hati-hati object lifecycle: RequestCtx di-reuse. Jangan simpan reference setelah handler return. Copy data yang perlu persist.
  • Connection pool untuk database: set MaxOpenConns, MaxIdleConns, ConnMaxLifetime.
  • Healthcheck cek dependency. Return 503 kalau database down.
  • Benchmark dengan wrk untuk verifikasi throughput: wrk -t4 -c100 -d10s http://localhost:8080/health.
  • Pakai fasthttp jika butuh throughput ekstrim atau microservice high-performance. Hindari jika API standar sudah cukup atau butuh ekosistem middleware.
  • Best practice: cache dependency, hot reload, hati-hati object lifecycle, tuning server, connection pool, healthcheck.
  • Alternatif: Fiber untuk API Express-like di atas fasthttp, Gin untuk kompatibilitas net/http, Chi untuk router ringan.

← Sebelumnya: Flask   Berikutnya: Rails →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact