265. Mini Project: REST API CRUD Server
Bangun REST API CRUD lengkap tanpa framework eksternal menggunakan net/http, encoding/json, dan in-memory store.
Mini Project: REST API CRUD tanpa Framework
Kita akan membangun REST API sederhana dengan fitur Create, Read, Update, Delete (CRUD) menggunakan hanya standard library Go β tanpa Gin, Echo, atau Fiber.
GET /productsβ Ambil semua produkGET /products/{id}β Ambil produk by IDPOST /productsβ Buat produk baruPUT /products/{id}β Update produkDELETE /products/{id}β Hapus produk
Struktur Data
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
Stock int `json:"stock"`
CreatedAt time.Time `json:"created_at"`
}
// In-memory store (production: ganti dengan database)
var store = struct {
sync.RWMutex
products map[int]Product
nextID int
}{products: make(map[int]Product), nextID: 1}
Teknik JSON Response Helper
func jsonResponse(w http.ResponseWriter, statusCode int, data any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
json.NewEncoder(w).Encode(data)
}
func jsonError(w http.ResponseWriter, statusCode int, message string) {
jsonResponse(w, statusCode, map[string]string{"error": message})
}
Routing Manual dengan ServeMux
mux := http.NewServeMux()
// Go 1.22+: mendukung method prefix dan path parameters
mux.HandleFunc("GET /products", listProducts)
mux.HandleFunc("GET /products/{id}", getProduct)
mux.HandleFunc("POST /products", createProduct)
mux.HandleFunc("PUT /products/{id}", updateProduct)
mux.HandleFunc("DELETE /products/{id}", deleteProduct)
Contoh program
package main
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"sync"
"time"
)
// ββ Data Model ββ
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
Stock int `json:"stock"`
CreatedAt time.Time `json:"created_at"`
}
// ββ In-Memory Store ββ
var store = struct {
sync.RWMutex
products map[int]Product
nextID int
}{products: make(map[int]Product), nextID: 1}
// ββ Helper Functions ββ
func jsonResponse(w http.ResponseWriter, code int, data any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
json.NewEncoder(w).Encode(data)
}
func getIDFromPath(path string) (int, error) {
parts := strings.Split(strings.Trim(path, "/"), "/")
if len(parts) < 2 {
return 0, fmt.Errorf("no id")
}
return strconv.Atoi(parts[len(parts)-1])
}
// ββ Handlers ββ
func listProducts(w http.ResponseWriter, r *http.Request) {
store.RLock()
defer store.RUnlock()
products := make([]Product, 0, len(store.products))
for _, p := range store.products {
products = append(products, p)
}
jsonResponse(w, 200, products)
}
func createProduct(w http.ResponseWriter, r *http.Request) {
var p Product
if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
jsonResponse(w, 400, map[string]string{"error": "invalid JSON"})
return
}
store.Lock()
defer store.Unlock()
p.ID = store.nextID
p.CreatedAt = time.Now()
store.products[p.ID] = p
store.nextID++
jsonResponse(w, 201, p)
}
func main() {
// Seed data awal
store.products[1] = Product{ID: 1, Name: "Laptop Go", Price: 15000000, Stock: 10, CreatedAt: time.Now()}
store.products[2] = Product{ID: 2, Name: "Go Book", Price: 150000, Stock: 50, CreatedAt: time.Now()}
store.nextID = 3
mux := http.NewServeMux()
mux.HandleFunc("/products", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
listProducts(w, r)
case "POST":
createProduct(w, r)
default:
jsonResponse(w, 405, map[string]string{"error": "method not allowed"})
}
})
fmt.Println("REST API Server berjalan di :8080")
fmt.Println("GET /products β list semua produk")
fmt.Println("POST /products β buat produk baru")
http.ListenAndServe(":8080", mux)
}
266. Mini Project: CLI Tool dengan Flag & Args
Buat CLI tool interaktif menggunakan package flag, os.Args, bufio, dan os β lengkap dengan subcommand dan help text.
Mini Project: CLI Tool Go
Go sangat cocok untuk membuat Command Line Interface (CLI) tool karena menghasilkan single binary tanpa dependency eksternal. Kita akan membuat tool manajemen catatan sederhana.
mynote add "Isi catatan"β Tambah catatan barumynote listβ Tampilkan semua catatanmynote delete 3β Hapus catatan ke-3mynote --helpβ Tampilkan bantuanmynote --versionβ Tampilkan versi
Package flag β Parsing Arguments
import "flag"
// Definisikan flags
version := flag.Bool("version", false, "Tampilkan versi aplikasi")
output := flag.String("output", "text", "Format output (text/json)")
limit := flag.Int("limit", 10, "Jumlah item yang ditampilkan")
// Parse semua flags
flag.Parse()
// Argumen yang tersisa setelah flags
args := flag.Args() // []string
subcommand := args[0] // "add", "list", "delete"
Menyimpan Data ke File JSON
import (
"encoding/json"
"os"
)
type Note struct {
ID int `json:"id"`
Content string `json:"content"`
CreatedAt time.Time `json:"created_at"`
}
func loadNotes(filename string) []Note {
data, err := os.ReadFile(filename)
if err != nil {
return []Note{} // File belum ada
}
var notes []Note
json.Unmarshal(data, Β¬es)
return notes
}
func saveNotes(filename string, notes []Note) error {
data, _ := json.MarshalIndent(notes, "", " ")
return os.WriteFile(filename, data, 0644)
}
Contoh program
package main
import (
"encoding/json"
"flag"
"fmt"
"os"
"strconv"
"time"
)
const (
Version = "1.0.0"
DataFile = "notes.json"
)
type Note struct {
ID int `json:"id"`
Content string `json:"content"`
CreatedAt time.Time `json:"created_at"`
}
func loadNotes() []Note {
data, err := os.ReadFile(DataFile)
if err != nil {
return []Note{}
}
var notes []Note
json.Unmarshal(data, ¬es)
return notes
}
func saveNotes(notes []Note) {
data, _ := json.MarshalIndent(notes, "", " ")
os.WriteFile(DataFile, data, 0644)
}
func addNote(content string) {
notes := loadNotes()
nextID := 1
if len(notes) > 0 {
nextID = notes[len(notes)-1].ID + 1
}
notes = append(notes, Note{
ID: nextID, Content: content, CreatedAt: time.Now(),
})
saveNotes(notes)
fmt.Printf("β
Catatan #%d ditambahkan!\n", nextID)
}
func listNotes() {
notes := loadNotes()
if len(notes) == 0 {
fmt.Println("π Belum ada catatan.")
return
}
fmt.Printf("π Total %d catatan:\n\n", len(notes))
for _, n := range notes {
fmt.Printf("[%d] %s\n π
%s\n\n",
n.ID, n.Content, n.CreatedAt.Format("2006-01-02 15:04"))
}
}
func deleteNote(id int) {
notes := loadNotes()
newNotes := make([]Note, 0)
deleted := false
for _, n := range notes {
if n.ID != id {
newNotes = append(newNotes, n)
} else {
deleted = true
}
}
if deleted {
saveNotes(newNotes)
fmt.Printf("ποΈ Catatan #%d dihapus!\n", id)
} else {
fmt.Printf("β Catatan #%d tidak ditemukan.\n", id)
}
}
func main() {
versionFlag := flag.Bool("version", false, "Tampilkan versi")
flag.Usage = func() {
fmt.Fprintf(os.Stderr, `MyNote CLI v%s - Manajemen Catatan Go
Usage: mynote <command> [arguments]
Commands:
add <catatan> Tambah catatan baru
list Tampilkan semua catatan
delete <id> Hapus catatan berdasarkan ID
Flags:
`, Version)
flag.PrintDefaults()
}
flag.Parse()
if *versionFlag {
fmt.Println("MyNote CLI v" + Version)
return
}
args := flag.Args()
if len(args) == 0 {
flag.Usage()
return
}
switch args[0] {
case "add":
if len(args) < 2 {
fmt.Println("β Gunakan: mynote add <isi catatan>")
return
}
addNote(args[1])
case "list":
listNotes()
case "delete":
if len(args) < 2 {
fmt.Println("β Gunakan: mynote delete <id>")
return
}
id, err := strconv.Atoi(args[1])
if err != nil {
fmt.Println("β ID harus berupa angka")
return
}
deleteNote(id)
default:
fmt.Printf("β Perintah tidak dikenal: %s\n", args[0])
flag.Usage()
}
}
267. Mini Project: Simple File-based Task Manager
Bangun task manager sederhana berbasis JSON file dengan operasi CRUD, status tracking, dan filter β menggunakan konsep OOP Go dengan struct dan methods.
Mini Project: Task Manager Go
Project ini menggabungkan berbagai konsep Go yang sudah dipelajari: struct, methods, interfaces, JSON serialization, error handling, dan file I/O.
- β Struct dengan methods (OOP style Go)
- β JSON encode/decode untuk persistensi data
- β Error handling idiomatik Go
- β Slice filtering dan sorting
- β Interface untuk extensibility
- β Goroutine untuk background auto-save
Arsitektur Task Manager
type Priority string
const (
Low Priority = "low"
Medium Priority = "medium"
High Priority = "high"
)
type Task struct {
ID int `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Priority Priority `json:"priority"`
Done bool `json:"done"`
DueDate time.Time `json:"due_date"`
CreatedAt time.Time `json:"created_at"`
}
// TaskManager sebagai type dengan methods
type TaskManager struct {
tasks []Task
filePath string
mu sync.Mutex
}
// Interface untuk filtering
type TaskFilter interface {
Filter(tasks []Task) []Task
}
// Filter implementations
type DoneFilter struct{}
type PriorityFilter struct { P Priority }
func (f DoneFilter) Filter(tasks []Task) []Task { ... }
func (f PriorityFilter) Filter(tasks []Task) []Task { ... }
Contoh program
package main
import (
"encoding/json"
"fmt"
"os"
"sort"
"time"
)
type Priority string
const (
Low Priority = "low"
Medium Priority = "medium"
High Priority = "high"
)
type Task struct {
ID int `json:"id"`
Title string `json:"title"`
Priority Priority `json:"priority"`
Done bool `json:"done"`
CreatedAt time.Time `json:"created_at"`
}
type TaskManager struct {
Tasks []Task `json:"tasks"`
NextID int `json:"next_id"`
filePath string
}
func NewTaskManager(filePath string) *TaskManager {
tm := &TaskManager{filePath: filePath, NextID: 1}
tm.load()
return tm
}
func (tm *TaskManager) load() {
data, err := os.ReadFile(tm.filePath)
if err != nil {
return
}
json.Unmarshal(data, tm)
}
func (tm *TaskManager) save() error {
data, err := json.MarshalIndent(tm, "", " ")
if err != nil {
return err
}
return os.WriteFile(tm.filePath, data, 0644)
}
func (tm *TaskManager) Add(title string, priority Priority) Task {
task := Task{
ID: tm.NextID, Title: title,
Priority: priority, CreatedAt: time.Now(),
}
tm.Tasks = append(tm.Tasks, task)
tm.NextID++
tm.save()
return task
}
func (tm *TaskManager) Complete(id int) bool {
for i, t := range tm.Tasks {
if t.ID == id {
tm.Tasks[i].Done = true
tm.save()
return true
}
}
return false
}
func (tm *TaskManager) FilterByPriority(p Priority) []Task {
var result []Task
for _, t := range tm.Tasks {
if t.Priority == p {
result = append(result, t)
}
}
return result
}
func (tm *TaskManager) SortedByPriority() []Task {
copy := make([]Task, len(tm.Tasks))
for i, t := range tm.Tasks {
copy[i] = t
}
priOrder := map[Priority]int{High: 0, Medium: 1, Low: 2}
sort.Slice(copy, func(i, j int) bool {
return priOrder[copy[i].Priority] < priOrder[copy[j].Priority]
})
return copy
}
func (tm *TaskManager) Stats() (total, done, pending int) {
total = len(tm.Tasks)
for _, t := range tm.Tasks {
if t.Done {
done++
} else {
pending++
}
}
return
}
func printTask(t Task) {
status := "β¬"
if t.Done {
status = "β
"
}
priEmoji := map[Priority]string{High: "π΄", Medium: "π‘", Low: "π’"}
fmt.Printf("%s [%d] %s %s\n", status, t.ID, priEmoji[t.Priority], t.Title)
}
func main() {
fmt.Println("=== Go Task Manager ===\n")
tm := NewTaskManager("tasks.json")
// Demo: Tambah tasks
t1 := tm.Add("Setup CI/CD Pipeline", High)
t2 := tm.Add("Tulis unit tests", Medium)
t3 := tm.Add("Update dokumentasi README", Low)
tm.Add("Deploy ke production server", High)
tm.Add("Review pull requests", Medium)
// Complete salah satu
tm.Complete(t1.ID)
tm.Complete(t2.ID)
_ = t3 // suppress unused
// Tampilkan sorted
fmt.Println("π Semua Task (sorted by priority):")
for _, t := range tm.SortedByPriority() {
printTask(t)
}
// Stats
total, done, pending := tm.Stats()
fmt.Printf("\nπ Stats: %d total | %d selesai | %d pending\n", total, done, pending)
// Filter high priority yang belum selesai
fmt.Println("\nπ΄ High Priority (pending):")
for _, t := range tm.FilterByPriority(High) {
if !t.Done {
printTask(t)
}
}
}