Introduction: Why Go is the Ideal Choice for REST API
Go (Golang) is a compiled programming language created at Google for high-load systems. It combines the speed of C, the simplicity of Python, and built-in concurrency support. For creating REST APIs, Go offers a powerful standard library net/http and popular frameworks such as gorilla/mux and gin.
In this guide, we will create a full-fledged REST API for managing a Todo List in pure Go without third-party frameworks in 30 minutes. You will learn:
- How to set up routing and handlers
- How to work with JSON (serialization/deserialization)
- How to implement CRUD operations (Create, Read, Update, Delete)
- How to add middleware for logging and CORS
All the code will be written in a single file main.go for simplicity. Let's get started!
Step 1: Basic Structure and Starting the HTTP Server
Create a file main.go and add the following code. We will declare a data structure for a task and start a simple server.
package main
import ( "encoding/json" "log" "net/http" "strconv" "strings" "sync" "time")
// Task — task structuretype Task struct { ID int `json:"id"` Title string `json:"title"` Completed bool `json:"completed"` CreatedAt time.Time `json:"created_at"`}
// Task storage (in-memory)var ( tasks = make(map[int]Task) nextID = 1 mu sync.Mutex)
func main() { // Routes http.HandleFunc("/api/tasks", tasksHandler) http.HandleFunc("/api/tasks/", taskByIDHandler)
log.Println("Server started on :8080") log.Fatal(http.ListenAndServe(":8080", nil))}Explanation:
Task— a structure with JSON tags for correct serialization.tasks— a map for storing tasks in memory (no database).mu— a mutex for safe access from goroutines.- Two routes:
/api/tasks(list and create) and/api/tasks/(work with a single task by ID).
Step 2: Handlers for the Task List (GET and POST)
Let's implement the tasksHandler function, which handles GET (retrieving all tasks) and POST (creating a new task).
func tasksHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json")
switch r.Method { case http.MethodGet: mu.Lock() taskList := make([]Task, 0, len(tasks)) for _, task := range tasks { taskList = append(taskList, task) } mu.Unlock() json.NewEncoder(w).Encode(taskList)
case http.MethodPost: var newTask Task if err := json.NewDecoder(r.Body).Decode(&newTask); err != nil { http.Error(w, `{"error":"Invalid JSON"}`, http.StatusBadRequest) return } mu.Lock() newTask.ID = nextID nextID++ newTask.CreatedAt = time.Now() tasks[newTask.ID] = newTask mu.Unlock()
w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(newTask)
default: http.Error(w, `{"error":"Method not supported"}`, http.StatusMethodNotAllowed) }}Key points:
- We use
json.NewEncoderandjson.NewDecoderfor working with JSON. - On POST, we check the JSON validity, assign an ID, and return 201 Created.
- The mutex protects data during concurrent access.
Step 3: Handlers for a Single Task (GET, PUT, DELETE)
Now let's implement taskByIDHandler. We extract the ID from the URL using strings.TrimPrefix
Blogs
Book Recommendations
Read also
CI/CD for TypeScript Projects: GitHub Actions from A to Z
Rust Hackathon: Tips, Project Examples, and Code Analysis
Zero-downtime deployment of Kotlin services: 3 proven strategies
How to Convert a Zero-Terminated Byte Array to a String in Go
Optimizing Rust Application Performance: Tips and Examples