1. 🌐 Pengenalan Web Development di Go
Go memiliki standard library yang sangat kuat untuk web development. Package net/http bisa digunakan untuk membuat web server production-ready tanpa framework pihak ketiga. Namun, framework seperti Gin, Echo, dan Fiber menyediakan fitur tambahan yang mempercepat development.
Go Web Framework Populer
| Framework | Kelebihan | Stars GitHub |
|---|---|---|
| net/http | Built-in, zero dependency, sangat stabil | Standard Library |
| Gin | Cepat, API clean, middleware lengkap, komunitas besar | 78k+ |
| Echo | Performa tinggi, fitur lengkap, auto TLS | 30k+ |
| Fiber | Inspired Express.js, sangat cepat (fasthttp) | 33k+ |
| Chi | Lightweight, composable, net/http compatible | 18k+ |
| Gorilla Mux | Router powerful, banyak fitur URL matching | 20k+ |
Kapan Pakai Framework vs Standard Library?
| Aspek | net/http (stdlib) | Framework (Gin, dll) |
|---|---|---|
| Dependency | Zero external deps | Tambah dependency |
| Routing | Basic (path matching) | Advanced (path params, groups) |
| JSON Binding | Manual parsing | Auto binding + validation |
| Middleware | Manual implementasi | Built-in + banyak pihak ketiga |
| Performa | 🟢 Sangat cepat | 🟢 Sangat cepat (Gin/Fiber) |
| Cocok untuk | Microservice sederhana, library | REST API kompleks, production |
┌─────────────────────────────────────────────────────────┐ │ GO WEB SERVER │ │ │ │ Client ──→ HTTP Request │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ Router │ ← URL matching, path params │ │ └────────┬────────┘ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ Middleware │ ← Auth, CORS, Logging, Rate Limit │ │ └────────┬────────┘ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ Handler │ ← Business logic │ │ └────────┬────────┘ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ Response │ ← JSON, HTML, File │ │ └────────┬────────┘ │ │ ▼ │ │ Client ←── HTTP Response │ └─────────────────────────────────────────────────────────┘
2. net/http: Web Server Dasar
Package net/http adalah standard library Go untuk HTTP. Sangat powerful — bahkan bisa untuk production tanpa framework.
package main
import (
"fmt"
"log"
"net/http"
"time"
)
// Handler function — menerima ResponseWriter dan Request
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Halo, dunia! 🌐\n")
fmt.Fprintf(w, "Method: %s\n", r.Method)
fmt.Fprintf(w, "URL: %s\n", r.URL.Path)
fmt.Fprintf(w, "Time: %s\n", time.Now().Format(time.RFC3339))
}
func aboutHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Tentang BeebaneLabs\n")
fmt.Fprintf(w, "Tutorial Go Web Development")
}
func main() {
// Register handlers
http.HandleFunc("/", helloHandler)
http.HandleFunc("/about", aboutHandler)
// Start server
fmt.Println("🚀 Server berjalan di http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
// Jalankan: go run main.go
// Buka: http://localhost:8080
// Buka: http://localhost:8080/about
Serving Static Files & HTML
package main
import (
"html/template"
"log"
"net/http"
)
// Struct untuk template data
type PageData struct {
Title string
Message string
Items []string
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
// Parse template
tmpl, err := template.ParseFiles("templates/index.html")
if err != nil {
http.Error(w, "Template tidak ditemukan", http.StatusInternalServerError)
return
}
// Data untuk template
data := PageData{
Title: "Beranda",
Message: "Selamat datang di Go Web!",
Items: []string{"Go", "Gin", "net/http", "REST API"},
}
tmpl.Execute(w, data)
}
func main() {
// Serve static files (CSS, JS, images)
fs := http.FileServer(http.Dir("./static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
http.HandleFunc("/", homeHandler)
log.Println("🚀 Server di http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Custom Server Configuration
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"time"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
})
// Custom server dengan konfigurasi
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}
// Graceful shutdown
go func() {
log.Println("🚀 Server berjalan di :8080")
if err := server.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
}
}()
// Tunggu signal untuk shutdown
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
<-quit
log.Println("⏳ Shutting down...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
server.Shutdown(ctx)
log.Println("✅ Server stopped gracefully")
}
3. Routing dengan net/http
Sejak Go 1.22, net/http mendukung method-based routing dan path parameters secara built-in!
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
type Response struct {
Status string `json:"status"`
Message string `json:"message,omitempty"`
Data interface{} `json:"data,omitempty"`
}
func main() {
mux := http.NewServeMux()
// Method-based routing (Go 1.22+)
mux.HandleFunc("GET /api/users", getUsers)
mux.HandleFunc("POST /api/users", createUser)
mux.HandleFunc("GET /api/users/{id}", getUserByID)
mux.HandleFunc("PUT /api/users/{id}", updateUser)
mux.HandleFunc("DELETE /api/users/{id}", deleteUser)
// Health check
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(Response{Status: "ok"})
})
log.Println("🚀 API Server di http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}
func getUsers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(Response{
Status: "ok",
Data: []string{"Budi", "Ani", "Dimas"},
})
}
func createUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(Response{
Status: "ok",
Message: "User berhasil dibuat",
})
}
func getUserByID(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
id := r.PathValue("id") // Path parameter (Go 1.22+)
json.NewEncoder(w).Encode(Response{
Status: "ok",
Data: map[string]string{"id": id, "name": "Budi"},
})
}
func updateUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
id := r.PathValue("id")
json.NewEncoder(w).Encode(Response{
Status: "ok",
Message: fmt.Sprintf("User %s berhasil diupdate", id),
})
}
func deleteUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
id := r.PathValue("id")
json.NewEncoder(w).Encode(Response{
Status: "ok",
Message: fmt.Sprintf("User %s berhasil dihapus", id),
})
}
4. JSON API dengan net/http
Go memiliki package encoding/json untuk serialisasi dan deserialisasi JSON. Digunakan bersama net/http untuk membuat REST API.
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"sync"
)
// Model
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
Stock int `json:"stock"`
}
// In-memory database
var (
products = map[int]*Product{
1: {ID: 1, Name: "Laptop", Price: 15000000, Stock: 10},
2: {ID: 2, Name: "Mouse", Price: 150000, Stock: 50},
}
mu sync.RWMutex
nextID = 3
)
// Helper: JSON response
func jsonResponse(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}
// Helper: error response
func errorResponse(w http.ResponseWriter, status int, message string) {
jsonResponse(w, status, map[string]string{
"error": message,
})
}
func productHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
mu.RLock()
defer mu.RUnlock()
list := make([]*Product, 0, len(products))
for _, p := range products {
list = append(list, p)
}
jsonResponse(w, http.StatusOK, list)
case http.MethodPost:
var p Product
if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
errorResponse(w, http.StatusBadRequest, "Invalid JSON")
return
}
mu.Lock()
p.ID = nextID
nextID++
products[p.ID] = &p
mu.Unlock()
jsonResponse(w, http.StatusCreated, p)
default:
errorResponse(w, http.StatusMethodNotAllowed, "Method not allowed")
}
}
func productByIDHandler(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
errorResponse(w, http.StatusBadRequest, "Invalid ID")
return
}
switch r.Method {
case http.MethodGet:
mu.RLock()
p, ok := products[id]
mu.RUnlock()
if !ok {
errorResponse(w, http.StatusNotFound, "Product not found")
return
}
jsonResponse(w, http.StatusOK, p)
case http.MethodDelete:
mu.Lock()
if _, ok := products[id]; !ok {
mu.Unlock()
errorResponse(w, http.StatusNotFound, "Product not found")
return
}
delete(products, id)
mu.Unlock()
jsonResponse(w, http.StatusOK, map[string]string{
"message": fmt.Sprintf("Product %d deleted", id),
})
default:
errorResponse(w, http.StatusMethodNotAllowed, "Method not allowed")
}
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /api/products", productHandler)
mux.HandleFunc("POST /api/products", productHandler)
mux.HandleFunc("GET /api/products/{id}", productByIDHandler)
mux.HandleFunc("DELETE /api/products/{id}", productByIDHandler)
log.Println("🚀 Product API di http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}
5. Middleware Pattern
Middleware adalah fungsi yang membungkus handler untuk menambahkan behavior seperti logging, authentication, CORS, dan rate limiting.
package main
import (
"log"
"net/http"
"time"
)
// Middleware type
type Middleware func(http.Handler) http.Handler
// Logging middleware
func Logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
log.Printf("→ %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
log.Printf("← %s %s (%v)", r.Method, r.URL.Path, time.Since(start))
})
}
// CORS middleware
func CORS(next http.Handler) http.Handler {
return http.HandlerFunc(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")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
// Auth middleware (sederhana)
func Auth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// Validasi token di sini...
next.ServeHTTP(w, r)
})
}
// Chain middleware
func Chain(handler http.Handler, middlewares ...Middleware) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
handler = middlewares[i](handler)
}
return handler
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Public endpoint 🌐"))
})
mux.HandleFunc("GET /admin", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Admin area 🔒"))
})
// Apply middleware
logged := Chain(mux, Logging, CORS)
log.Println("🚀 Server di http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", logged))
}
6. Gin Framework: Pengenalan
Gin adalah HTTP web framework paling populer untuk Go. Menyediakan routing, middleware, JSON binding, validation, dan banyak lagi dengan API yang sangat clean.
Instalasi Gin
# Inisialisasi module go mod init my-api # Install Gin go get -u github.com/gin-gonic/gin # Verifikasi di go.mod: # require github.com/gin-gonic/gin v1.10.0
Hello World dengan Gin
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
// Buat Gin router (default dengan Logger & Recovery)
r := gin.Default()
// Route sederhana
r.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Halo, Gin! 🐝",
"status": "ok",
})
})
// HTML response
r.GET("/hello", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", gin.H{
"title": "Hello Gin",
})
})
// String response
r.GET("/ping", func(c *gin.Context) {
c.String(http.StatusOK, "pong 🏓")
})
// Redirect
r.GET("/old-path", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "/")
})
r.Run(":8080") // Start server
}
7. Routing dengan Gin
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// Path parameters
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{"user_id": id})
})
// Query parameters: /search?q=golang&page=1
r.GET("/search", func(c *gin.Context) {
query := c.DefaultQuery("q", "")
page := c.DefaultQuery("page", "1")
c.JSON(http.StatusOK, gin.H{
"query": query,
"page": page,
})
})
// Multiple path parameters
r.GET("/users/:userId/posts/:postId", func(c *gin.Context) {
userID := c.Param("userId")
postID := c.Param("postId")
c.JSON(http.StatusOK, gin.H{
"user_id": userID,
"post_id": postID,
})
})
// Route groups — organisasi URL
v1 := r.Group("/api/v1")
{
v1.GET("/users", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"version": "v1", "resource": "users"})
})
v1.GET("/products", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"version": "v1", "resource": "products"})
})
}
v2 := r.Group("/api/v2")
{
v2.GET("/users", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"version": "v2", "resource": "users"})
})
}
// Nested groups
admin := r.Group("/admin")
admin.Use(func(c *gin.Context) {
// Auth middleware untuk admin
token := c.GetHeader("X-Admin-Token")
if token != "secret" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"error": "Unauthorized",
})
return
}
c.Next()
})
{
admin.GET("/dashboard", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "Admin Dashboard"})
})
admin.GET("/settings", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "Admin Settings"})
})
}
// No route handler
r.NoRoute(func(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{
"error": fmt.Sprintf("Route %s not found", c.Request.URL.Path),
})
})
r.Run(":8080")
}
8. JSON API dengan Gin
Gin menyediakan JSON binding dan validation yang sangat mudah digunakan. Tidak perlu parsing manual!
package main
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
)
// Request struct dengan validation tags
type CreateProductReq struct {
Name string `json:"name" binding:"required,min=2,max=100"`
Price float64 `json:"price" binding:"required,gt=0"`
Stock int `json:"stock" binding:"gte=0,lte=10000"`
Category string `json:"category" binding:"required,oneof=electronics food clothing"`
}
// Response struct
type ProductResp struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
Stock int `json:"stock"`
Category string `json:"category"`
}
func main() {
r := gin.Default()
// POST — JSON binding dengan validasi
r.POST("/api/products", func(c *gin.Context) {
var req CreateProductReq
if err := c.ShouldBindJSON(&req); err != nil {
// Format validation errors
errors := make(map[string]string)
if ve, ok := err.(validator.ValidationErrors); ok {
for _, e := range ve {
errors[e.Field()] = validationMessage(e)
}
}
c.JSON(http.StatusBadRequest, gin.H{"errors": errors})
return
}
product := ProductResp{
ID: 1,
Name: req.Name,
Price: req.Price,
Stock: req.Stock,
Category: req.Category,
}
c.JSON(http.StatusCreated, gin.H{
"status": "success",
"product": product,
})
})
// GET — Query binding
r.GET("/api/products", func(c *gin.Context) {
var req struct {
Category string `form:"category"`
MinPrice float64 `form:"min_price"`
MaxPrice float64 `form:"max_price"`
Page int `form:"page,default=1"`
Limit int `form:"limit,default=10"`
}
if err := c.ShouldBindQuery(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"filters": req,
"products": []ProductResp{
{ID: 1, Name: "Laptop", Price: 15000000, Stock: 10, Category: "electronics"},
{ID: 2, Name: "Mouse", Price: 150000, Stock: 50, Category: "electronics"},
},
})
})
r.Run(":8080")
}
func validationMessage(e validator.FieldError) string {
switch e.Tag() {
case "required":
return "Field ini wajib diisi"
case "min":
return "Minimal " + e.Param() + " karakter"
case "max":
return "Maksimal " + e.Param() + " karakter"
case "gt":
return "Harus lebih besar dari " + e.Param()
case "oneof":
return "Harus salah satu dari: " + e.Param()
default:
return "Validasi gagal: " + e.Tag()
}
}
9. Middleware di Gin
package main
import (
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
// Custom logger middleware
func RequestLogger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
c.Next() // Proses request
latency := time.Since(start)
status := c.Writer.Status()
log.Printf("[%d] %s %s (%v)",
status, c.Request.Method, path, latency)
}
}
// Auth middleware
func AuthRequired() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"error": "Token diperlukan",
})
return
}
// Simulasi validasi token
if token != "Bearer my-secret-token" {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
"error": "Token tidak valid",
})
return
}
// Set user info ke context
c.Set("userID", 1)
c.Set("userRole", "admin")
c.Next()
}
}
// Rate limiter middleware (sederhana)
func RateLimit(requests int, duration time.Duration) gin.HandlerFunc {
// Implementasi rate limiting di sini
return func(c *gin.Context) {
c.Next()
}
}
func main() {
r := gin.New() // Tanpa default middleware
// Global middleware
r.Use(gin.Recovery()) // Panic recovery
r.Use(RequestLogger()) // Request logging
// Public routes
r.GET("/api/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "healthy"})
})
r.POST("/api/login", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"token": "my-secret-token"})
})
// Protected routes
api := r.Group("/api", AuthRequired())
{
api.GET("/profile", func(c *gin.Context) {
userID, _ := c.Get("userID")
userRole, _ := c.Get("userRole")
c.JSON(http.StatusOK, gin.H{
"user_id": userID,
"role": userRole,
})
})
api.PUT("/profile", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "Profile updated"})
})
}
r.Run(":8080")
}
10. Membangun REST API Lengkap
Mari gabungkan semua yang sudah dipelajari untuk membangun REST API CRUD yang production-ready.
package main
import (
"net/http"
"strconv"
"sync"
"time"
"github.com/gin-gonic/gin"
)
// === MODELS ===
type User struct {
ID int `json:"id"`
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"required,email"`
CreatedAt time.Time `json:"created_at"`
}
// === STORE (in-memory) ===
type UserStore struct {
mu sync.RWMutex
users map[int]*User
nextID int
}
func NewUserStore() *UserStore {
return &UserStore{
users: make(map[int]*User),
nextID: 1,
}
}
func (s *UserStore) GetAll() []*User {
s.mu.RLock()
defer s.mu.RUnlock()
list := make([]*User, 0, len(s.users))
for _, u := range s.users {
list = append(list, u)
}
return list
}
func (s *UserStore) GetByID(id int) (*User, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
u, ok := s.users[id]
return u, ok
}
func (s *UserStore) Create(u *User) {
s.mu.Lock()
defer s.mu.Unlock()
u.ID = s.nextID
u.CreatedAt = time.Now()
s.users[u.ID] = u
s.nextID++
}
func (s *UserStore) Delete(id int) bool {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.users[id]; !ok {
return false
}
delete(s.users, id)
return true
}
// === HANDLERS ===
type UserHandler struct {
store *UserStore
}
func (h *UserHandler) List(c *gin.Context) {
users := h.store.GetAll()
c.JSON(http.StatusOK, gin.H{
"count": len(users),
"users": users,
})
}
func (h *UserHandler) Get(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
return
}
user, ok := h.store.GetByID(id)
if !ok {
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
return
}
c.JSON(http.StatusOK, user)
}
func (h *UserHandler) Create(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
h.store.Create(&user)
c.JSON(http.StatusCreated, user)
}
func (h *UserHandler) Delete(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
return
}
if !h.store.Delete(id) {
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
return
}
c.JSON(http.StatusOK, gin.H{"message": "User deleted"})
}
// === MAIN ===
func main() {
store := NewUserStore()
handler := &UserHandler{store: store}
r := gin.Default()
// CORS middleware
r.Use(func(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
c.Next()
})
// Routes
api := r.Group("/api/v1")
{
api.GET("/users", handler.List)
api.GET("/users/:id", handler.Get)
api.POST("/users", handler.Create)
api.DELETE("/users/:id", handler.Delete)
}
r.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"status": "ok",
"time": time.Now().Format(time.RFC3339),
})
})
r.Run(":8080")
}
// Test dengan curl:
// GET → curl http://localhost:8080/api/v1/users
// POST → curl -X POST http://localhost:8080/api/v1/users -d '{"name":"Budi","email":"budi@mail.com"}'
// GET → curl http://localhost:8080/api/v1/users/1
// DELETE → curl -X DELETE http://localhost:8080/api/v1/users/1
Untuk proyek yang lebih besar, pisahkan kode menjadi package: handlers/, models/, middleware/, store/, routes/. Gunakan dependency injection untuk memudahkan testing dan menghindari tight coupling antar komponen.
11. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang Go Web Development: