DEV Community

Cover image for Building RESTful APIs with Go
Neel Patel
Neel Patel

Posted on

Building RESTful APIs with Go

Welcome to Part 2 of our Backend Engineering for Full Stack Devs series! 🚀 Today, we’re diving into one of the most fundamental topics: Building RESTful APIs with Go. Whether you’re building an internal tool or a public API, understanding how to structure and implement RESTful endpoints is key.

In this post, we’ll cover:

  • What makes an API RESTful (and why it matters).
  • How to design endpoints and handle HTTP methods.
  • Building a simple CRUD API with Go’s net/http package and Gorilla Mux.
  • Handling JSON requests and responses.

By the end, you’ll have a solid grasp of how to design and implement scalable RESTful APIs in Go. Let’s get started! 🎉


What Makes an API RESTful? 🤔

Before we dive into the code, let’s quickly go over what makes an API RESTful. REST (Representational State Transfer) is an architectural style for building APIs that follow these principles:

  1. Stateless: Each request from the client must contain all the necessary information to process it. The server doesn’t store session information.
  2. Resource-Oriented: URLs represent resources, like /users, /products, or /orders, and HTTP methods define the actions.
  3. HTTP Methods: RESTful APIs use HTTP methods to specify actions:
    • GET: Retrieve data.
    • POST: Create new resources.
    • PUT: Update existing resources.
    • DELETE: Remove resources.
  4. Use of HTTP Status Codes: RESTful APIs make good use of HTTP status codes (200 for success, 404 for not found, etc.).

Setting Up Your Go Project

Let’s build a basic CRUD API for managing users using Go and the Gorilla Mux router. Our API will have the following endpoints:

HTTP Method Endpoint Action
GET /users Retrieve all users
GET /users/{id} Retrieve a specific user
POST /users Create a new user
PUT /users/{id} Update an existing user
DELETE /users/{id} Delete a user

Step 1: Install Dependencies

First, install the Gorilla Mux router for routing:

go get -u github.com/gorilla/mux
Enter fullscreen mode Exit fullscreen mode

Create a simple main.go file to get started:

package main

import (
    "log"
    "net/http"
    "github.com/gorilla/mux"
)

func main() {
    r := mux.NewRouter()

    // Define routes here
    http.Handle("/", r)

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

Step 2: Define the Handlers

Now, let’s define our API handlers. These will correspond to our CRUD actions. We’ll use a simple in-memory map to store user data for this example.

In-Memory Data Store

type User struct {
    ID   string `json:"id"`
    Name string `json:"name"`
    Age  int    `json:"age"`
}

var users = make(map[string]User)
Enter fullscreen mode Exit fullscreen mode

Step 3: Implement CRUD Handlers

We’ll implement each CRUD operation: Create, Read, Update, and Delete users.

GET /users – Retrieve All Users
func getUsers(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    var userList []User
    for _, user := range users {
        userList = append(userList, user)
    }
    json.NewEncoder(w).Encode(userList)
}
Enter fullscreen mode Exit fullscreen mode
GET /users/{id} – Retrieve a Specific User
func getUser(w http.ResponseWriter, r *http.Request) {
    params := mux.Vars(r)
    user, found := users[params["id"]]
    if !found {
        http.Error(w, "User not found", http.StatusNotFound)
        return
    }
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(user)
}
Enter fullscreen mode Exit fullscreen mode
POST /users – Create a New User
func createUser(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    var user User
    _ = json.NewDecoder(r.Body).Decode(&user)
    users[user.ID] = user
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(user)
}
Enter fullscreen mode Exit fullscreen mode
PUT /users/{id} – Update an Existing User
func updateUser(w http.ResponseWriter, r *http.Request) {
    params := mux.Vars(r)
    user, found := users[params["id"]]
    if !found {
        http.Error(w, "User not found", http.StatusNotFound)
        return
    }
    _ = json.NewDecoder(r.Body).Decode(&user)
    user.ID = params["id"]  // Ensure the ID stays the same
    users[params["id"]] = user
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(user)
}
Enter fullscreen mode Exit fullscreen mode
DELETE /users/{id} – Delete a User
func deleteUser(w http.ResponseWriter, r *http.Request) {
    params := mux.Vars(r)
    _, found := users[params["id"]]
    if !found {
        http.Error(w, "User not found", http.StatusNotFound)
        return
    }
    delete(users, params["id"])
    w.WriteHeader(http.StatusNoContent)
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Register Routes

Now that we’ve defined our handlers, let’s add the routes to our router in the main function:

func main() {
    r := mux.NewRouter()

    r.HandleFunc("/users", getUsers).Methods("GET")
    r.HandleFunc("/users/{id}", getUser).Methods("GET")
    r.HandleFunc("/users", createUser).Methods("POST")
    r.HandleFunc("/users/{id}", updateUser).Methods("PUT")
    r.HandleFunc("/users/{id}", deleteUser).Methods("DELETE")

    http.Handle("/", r)

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

Step 5: Testing the API

You can test your API using Postman or curl commands. Here’s how you can create a new user and retrieve users:

  1. Create a New User:
   curl -X POST http://localhost:8080/users \
   -H "Content-Type: application/json" \
   -d '{"id":"1", "name":"John Doe", "age":30}'
Enter fullscreen mode Exit fullscreen mode
  1. Get All Users:
   curl -X GET http://localhost:8080/users
Enter fullscreen mode Exit fullscreen mode
  1. Get a Specific User:
   curl -X GET http://localhost:8080/users/1
Enter fullscreen mode Exit fullscreen mode
  1. Update a User:
   curl -X PUT http://localhost:8080/users/1 \
   -H "Content-Type: application/json" \
   -d '{"name":"John Smith", "age":31}'
Enter fullscreen mode Exit fullscreen mode
  1. Delete a User:
   curl -X DELETE http://localhost:8080/users/1
Enter fullscreen mode Exit fullscreen mode

Best Practices for Building RESTful APIs

  1. Use Proper HTTP Methods: Follow RESTful principles by using GET for reads, POST for creates, PUT for updates, and DELETE for deletes.
  2. Return Appropriate Status Codes: Always use correct HTTP status codes (e.g., 201 Created for successful resource creation, 404 Not Found for missing resources).
  3. Handle Errors Gracefully: Don’t expose internal errors to users. Use generic messages like "User not found" or "Invalid request."
  4. Use Pagination for Large Data Sets: When returning large lists (like /users), implement pagination to prevent excessive data loading.
  5. Secure Your API: Use authentication methods like JWT or OAuth2 to secure sensitive endpoints.

What’s Next?

Now that we’ve built a basic RESTful API, it’s time to integrate database support so we can persist our data. In the next post, we’ll dive into using an ORM to connect our Go API to a database. Stay tuned for Part 3! 📦

Top comments (0)