Created
June 20, 2025 07:30
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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