DEV Community

Allan Roberto
Allan Roberto

Posted on

Building a Simple REST API in Go (Without Frameworks)

Modern backend development often starts with a framework. But sometimes the best way to understand a language is to remove the abstractions.

In this project, I built a minimal REST API in Go using only the standard library, without any external frameworks.

The goal is simple: demonstrate how HTTP APIs work internally in Go.

👉 Project repository: go-native-api-sample


Why Build an API Without Frameworks?

Frameworks are powerful, but they can hide important concepts.

When you build an API using only Go's native packages, you understand:

  • how HTTP servers actually work
  • how routing is handled
  • how JSON serialization works
  • how concurrency and shared state must be managed

Go’s standard library is already powerful enough for many services.


Project Structure

The project is intentionally simple.

go-native-api-sample
 ┣ main.go
 â”— README.md
Enter fullscreen mode Exit fullscreen mode

All the logic is inside main.go, making it easy to explore how everything works.


Starting the Server

The server is created using Go’s net/http package.

func main() {
    http.HandleFunc("/health", healthHandler)
    http.HandleFunc("/users", usersHandler)

    log.Println("Server running on :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}
Enter fullscreen mode Exit fullscreen mode

This registers two endpoints:

  • /health
  • /users

And starts the HTTP server on port 8080.


Health Check Endpoint

A basic health endpoint is useful in production systems for monitoring and load balancers.

func healthHandler(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("OK"))
}
Enter fullscreen mode Exit fullscreen mode

Test it with:

curl http://localhost:8080/health
Enter fullscreen mode Exit fullscreen mode

Response:

OK
Enter fullscreen mode Exit fullscreen mode

User Model

The API stores users in memory.

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}
Enter fullscreen mode Exit fullscreen mode

Since multiple requests can happen simultaneously, we also use a mutex to protect shared data.

var (
    users []User
    mu    sync.Mutex
)
Enter fullscreen mode Exit fullscreen mode

Creating Users (POST)

To create users, the API accepts JSON.

Example request:

{
  "name": "Allan"
}
Enter fullscreen mode Exit fullscreen mode

Handler example:

func createUser(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
    }

    mu.Lock()
    user.ID = len(users) + 1
    users = append(users, user)
    mu.Unlock()

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(user)
}
Enter fullscreen mode Exit fullscreen mode

Test with curl:

curl -X POST http://localhost:8080/users \
-H "Content-Type: application/json" \
-d '{"name":"Allan"}'
Enter fullscreen mode Exit fullscreen mode

Listing Users (GET)

To retrieve users:

func listUsers(w http.ResponseWriter, r *http.Request) {
    mu.Lock()
    defer mu.Unlock()

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(users)
}
Enter fullscreen mode Exit fullscreen mode

Request:

curl http://localhost:8080/users
Enter fullscreen mode Exit fullscreen mode

Response example:

[
  {
    "id": 1,
    "name": "Allan"
  }
]
Enter fullscreen mode Exit fullscreen mode

Request Flow

Below is a simplified diagram of how the API works.

diagram of how the API works

This shows the flow:

  1. Client sends HTTP request
  2. Go HTTP server receives it
  3. Router maps the endpoint
  4. Handler processes request
  5. Data is stored in memory safely using a mutex

Running the Project

Clone the repository:

git clone https://github.com/allanroberto18/go-native-api-sample
Enter fullscreen mode Exit fullscreen mode

Run the API:

go run main.go
Enter fullscreen mode Exit fullscreen mode

Server will start on: http://localhost:8080


Why This Project Matters

Small projects like this are incredibly valuable for learning.

Before introducing routers, ORMs, dependency injection, and frameworks, it’s important to understand:

  • how a request enters your application
  • how it's processed
  • how responses are generated

Once you understand the fundamentals, frameworks become tools rather than magic.


Possible Improvements

This project could evolve into a more realistic API by adding:

  • persistent storage (PostgreSQL)
  • a router like Chi or Gin
  • middleware (logging, auth)
  • environment configuration
  • Docker support
  • structured logging

Final Thoughts

Go’s standard library is surprisingly capable.

You can build fully functional APIs without any framework, which is one of the reasons Go is so popular for backend services and microservices.

If you're learning Go, building something like this from scratch is one of the best exercises you can do.

Top comments (0)