How to Create a Simple HTTP Server in Go
Build a basic Go HTTP server with CRUD routes, handlers, and safe concurrency using sync.RWMutex.
Go makes it incredibly easy to build fast, concurrent, and scalable web servers with minimal code. In this tutorial, we'll walk through how to create a basic HTTP server that handles CRUD operations for users including adding, retrieving, and deleting them.
Step 1: Setting Up the Project
Start by creating a new Go file, for example, main.go, and add the following package declaration and imports:
package main
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"sync"
)Here:
net/httpprovides tools for building web servers.encoding/jsonallows you to work with JSON data.strconvhelps convert strings to numbers.syncis used for safe concurrent access to shared data.
Step 2: Define a User Type and Cache
We'll create a simple User struct and use a map to store users in memory.
type User struct {
Name string `json:"name"`
}
var userCache = make(map[int]User)
var cacheMutex sync.RWMutexThe cacheMutex ensures safe read/write access to userCache since multiple requests could come in at once.
Step 3: Create the Server and Routes
We'll use http.NewServeMux() to create a request multiplexer that directs incoming requests to their corresponding handlers.
func main() {
mux := http.NewServeMux()
fmt.Println("Server is running on port 8080")1. Root Endpoint
A simple handler to test if your server is running:
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello World"))
})2. POST /users: Create a New User
mux.HandleFunc("POST /users", func(w http.ResponseWriter, r *http.Request) {
var user User
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if user.Name == "" {
http.Error(w, "Name is required", http.StatusBadRequest)
return
}
cacheMutex.Lock()
userCache[len(userCache)+1] = user
cacheMutex.Unlock()
w.Write([]byte("User Created"))
})This handler reads the JSON body, validates it, and stores the user in the cache.
3. GET /users/{id}: Retrieve a User by ID
mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(r.PathValue("id"))
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
cacheMutex.RLock()
user, ok := userCache[id]
cacheMutex.RUnlock()
if !ok {
http.Error(w, "User not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
j, err := json.Marshal(user)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write(j)
})This route fetches a user by ID, converts it to JSON, and returns it.
4. DELETE /users/{id}: Delete a User
mux.HandleFunc("DELETE /users/{id}", func(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(r.PathValue("id"))
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if _, ok := userCache[id]; !ok {
http.Error(w, "User not found", http.StatusNotFound)
return
}
cacheMutex.Lock()
delete(userCache, id)
cacheMutex.Unlock()
w.WriteHeader(http.StatusNoContent)
w.Write([]byte("User Deleted"))
})This handler deletes a user from the cache safely.
Step 4: Start the Server
Finally, start the server by listening on port 8080:
http.ListenAndServe(":8080", mux)
}Now, run the program:
go run main.goVisit http://localhost:8080 — you should see Hello World!
Conclusion
With just a few lines of Go, you've built:
- A basic HTTP server
- CRUD endpoints for managing users
- Safe concurrent access with
sync.RWMutex
This example is a great starting point for learning how to build REST APIs in Go. From here, you can expand to using databases, authentication, and middleware for a full production setup.
The source code is available at: github.com/satyawaniaman/http-server-go
