Skip to content

Instantly share code, notes, and snippets.

@ayoubzulfiqar
Created June 20, 2025 07:30
Show Gist options
  • Save ayoubzulfiqar/4f917965a96edf0e4beb248a75cee6a6 to your computer and use it in GitHub Desktop.
Save ayoubzulfiqar/4f917965a96edf0e4beb248a75cee6a6 to your computer and use it in GitHub Desktop.
This is a simple script which host/serve the remote as well as LOCAL json file
package main
import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"time"
)
// 🔁 Hardcoded source: local file or remote URL
const SOURCE = "LOCAL_PATH" // 👈 Change this to your local file or remote URL
// 🧱 Your custom struct — replace this with what quickjson.io generates for your JSON
type JSONMODEL struct {
Difficulty string `json:"difficulty"`
PaidOnly bool `json:"paidOnly"`
Title string `json:"title"`
Description string `json:"description"`
Category string `json:"category"`
Hints []string `json:"hints"`
ID int64 `json:"id"`
}
var jsonData []JSONMODEL // 👈 Adjust this type to match your data structure
// Rate limiting config
const (
rateLimitRequests = 5
rateLimitWindow = time.Second
)
var (
visits = make(map[string]time.Time)
mu sync.Mutex
)
// loadData loads JSON from local file or remote URL into typed struct
func loadData(source string) ([]JSONMODEL, error) {
var reader io.Reader
if strings.HasPrefix(source, "http") {
resp, err := http.Get(source)
if err != nil {
return nil, err
}
defer resp.Body.Close()
reader = resp.Body
} else {
file, err := os.Open(filepath.Clean(source))
if err != nil {
return nil, err
}
defer file.Close()
reader = file
}
var data []JSONMODEL
if err := json.NewDecoder(reader).Decode(&data); err != nil {
return nil, err
}
return data, nil
}
// getNestedValue simulates dynamic access (for array index like /api/0)
func getNestedValue(data []JSONMODEL, parts []string) interface{} {
if len(parts) == 0 {
return data
}
index := parseInt(parts[0])
if index >= 0 && index < len(data) {
return data[index]
}
return nil
}
// setNestedValue allows updating an item by index
func setNestedValue(data []JSONMODEL, parts []string, value interface{}) ([]JSONMODEL, error) {
if len(parts) == 0 {
return data, fmt.Errorf("no index provided")
}
index := parseInt(parts[0])
if index < 0 || index >= len(data) {
return data, fmt.Errorf("index out of bounds")
}
if v, ok := value.(JSONMODEL); ok {
data[index] = v
} else {
return data, fmt.Errorf("invalid type for update")
}
return data, nil
}
// deleteNestedValue removes an item by index
func deleteNestedValue(data []JSONMODEL, parts []string) ([]JSONMODEL, error) {
if len(parts) == 0 {
return data, fmt.Errorf("no index provided")
}
index := parseInt(parts[0])
if index < 0 || index >= len(data) {
return data, fmt.Errorf("index out of bounds")
}
return append(data[:index], data[index+1:]...), nil
}
// parseInt safely parses int from string
func parseInt(s string) int {
n := 0
for _, r := range s {
n = n*10 + int(r-'0')
}
return n
}
// Standard lib-based rate limiter middleware
func rateLimit(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ip, _, _ := net.SplitHostPort(r.RemoteAddr)
mu.Lock()
defer mu.Unlock()
lastSeen := visits[ip]
now := time.Now()
if !lastSeen.IsZero() && now.Sub(lastSeen) < rateLimitWindow {
http.Error(w, `{"error": "rate limit exceeded"}`, http.StatusTooManyRequests)
return
}
visits[ip] = now
next(w, r)
}
}
// CORS middleware
func enableCORS(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next(w, r)
}
}
// handler for dynamic routes
func jsonHandler(data *[]JSONMODEL) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
path := strings.Trim(r.URL.Path, "/")
parts := strings.Split(path, "/")
switch r.Method {
case http.MethodGet:
value := getNestedValue(*data, parts)
if value == nil {
http.Error(w, `{"error": "not found"}`, http.StatusNotFound)
return
}
json.NewEncoder(w).Encode(value)
case http.MethodPost:
fallthrough
case http.MethodPut:
var payload JSONMODEL
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
http.Error(w, `{"error": "invalid request body"}`, http.StatusBadRequest)
return
}
if len(parts) == 0 {
http.Error(w, `{"error": "no index provided"}`, http.StatusBadRequest)
return
}
updatedData, err := setNestedValue(*data, parts, payload)
if err != nil {
http.Error(w, `{"error": "update failed"}`, http.StatusInternalServerError)
return
}
*data = updatedData
json.NewEncoder(w).Encode(payload)
case http.MethodDelete:
if len(parts) == 0 {
http.Error(w, `{"error": "nothing to delete"}`, http.StatusBadRequest)
return
}
updatedData, err := deleteNestedValue(*data, parts)
if err != nil {
http.Error(w, `{"error": "delete failed"}`, http.StatusInternalServerError)
return
}
*data = updatedData
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "deleted"})
default:
http.Error(w, `{"error": "method not allowed"}`, http.StatusMethodNotAllowed)
}
}
}
func main() {
const port = "3000"
server := &http.Server{
Addr: ":" + port,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}
// Load JSON at startup
log.Printf("Loading JSON from: %s", SOURCE)
tempData, err := loadData(SOURCE)
if err != nil {
log.Fatalf("Failed to load JSON: %v", err)
}
jsonData = tempData
// Root route
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(jsonData)
})
// API endpoints
http.HandleFunc("/api/", enableCORS(rateLimit(func(w http.ResponseWriter, r *http.Request) {
jsonHandler(&jsonData)(w, r)
})))
log.Printf("✅ JSON Server running at http://localhost:%s", port)
// Start server in goroutine to allow graceful shutdown
go func() {
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Error starting server: %v", err)
}
}()
// Wait for interrupt signal to gracefully shut down
quit := make(chan os.Signal, 1)
<-quit
log.Println("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
log.Println("Server exited")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment