Introduction: Why Go is the Ideal Choice for REST API
Go (or Golang) is a compiled programming language from Google that combines the performance of C++ with the simplicity of Python. If you want to create a fast, reliable, and easily maintainable REST API, Go is one of the best options. In this article, we will write a full-fledged REST API in Go in 30 minutes that can create, read, update, and delete records (CRUD). No complex frameworks — just the standard library net/http and a bit of code.
By the end of the article, you will have a ready-made HTTP server that responds to requests in JSON format and works without external dependencies. Let's go!
Step 1: Project Setup and Structure
First, make sure you have Go installed (version 1.16+). Create a new directory for the project and initialize the module:
mkdir go-rest-apicd go-rest-apigo mod init go-rest-apiThe project structure will be simple — one file main.go. We will place all the code in it to keep the example uncomplicated. In real projects, you would, of course, split the code into packages, but for learning, this is ideal.
Step 2: Define the Data Structure and Global Storage
Our API will manage a list of books. Let's create a Book struct and a simple slice for storing data in memory (instead of a database).
package main
import ( "encoding/json" "log" "net/http" "strconv" "strings")
type Book struct { ID int `json:"id"` Title string `json:"title"` Author string `json:"author"` Year int `json:"year"`}
var books []Bookvar nextID int = 1Here, books is our temporary "data layer", and nextID is a counter for automatically assigning IDs to new books.
Step 3: Implement HTTP Request Handlers (CRUD)
Now for the interesting part — let's write handler functions for each HTTP method. We will use the standard router http.NewServeMux.
3.1. Get All Books (GET /books)
func getBooks(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(books)}3.2. Create a New Book (POST /books)
func createBook(w http.ResponseWriter, r *http.Request) { var newBook Book if err := json.NewDecoder(r.Body).Decode(&newBook); err != nil { http.Error(w, "Invalid JSON", http.StatusBadRequest) return } newBook.ID = nextID nextID++ books = append(books, newBook) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(newBook)}3.3. Get a Single Book by ID (GET /books/{id})
The standard ServeMux does not support dynamic paths, so we will extract the ID from the URL manually.
func getBook(w http.ResponseWriter, r *http.Request) { idStr := strings.TrimPrefix(r.URL.Path, "/books/") id, err := strconv.Atoi(idStr) if err != nil { http.Error(w, "Invalid ID", http.StatusBadRequest) return } for _, book := range books { if book.ID == id { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(book) return } } http.Error(w, "Book not found", http.StatusNotFound)}3.4. Update a Book (PUT /books/{id})
func updateBook(w http.ResponseWriter, r *http.Request) { idStr := strings.TrimPrefix(r.URL.Path, "/books/") id, err := strconv.Atoi(idStr) if err != nil { http.Error(w, "Invalid ID", http.StatusBadRequest) return } var updatedBook Book if err := json.NewDecoder(r.Body).Decode(&updatedBook); err != nil { h