Building a Simple REST API in Go

Featured on Hashnode
Building a Simple REST API in Go

Photo by Andrew Neel on Unsplash

If you're new to Go (often referred to as Golang) and want to learn how to build a RESTful API, you're in the right place! This guide will walk you through the process step-by-step, providing code examples to help you understand how everything fits together.

What is a REST API?

A REST (Representational State Transfer) API is a way for applications to communicate over HTTP using standard methods like GET, POST, PUT, and DELETE. RESTful APIs are stateless and can be used across various platforms and languages, making them a popular choice for web services.

Why Use Go for REST APIs?

Go is a statically typed, compiled language known for its simplicity and performance. It's excellent for building web services due to its efficient concurrency model and robust standard library, which includes powerful packages for handling HTTP requests.

Prerequisites

Before we begin, make sure you have:

  • Go installed on your system. You can download it from the official website.simp

  • A basic understanding of Go syntax and programming concepts.

  • A code editor or IDE you're comfortable with (e.g., VSCode, GoLand).

Setting Up Your Go Project

First, create a new directory for your project and navigate into it:

mkdir simple-rest-api
cd simple-rest-api

Initialize a new Go module:

go mod init github.com/yourusername/simple-rest-api

Replace github.com/yourusername/simple-rest-api with your module path.

Building the REST API

We'll build a simple API to manage a list of books. The API will support the following operations:

  • Get a list of all books (GET)

  • Get a single book by ID (GET)

  • Add a new book (POST)

  • Update an existing book (PUT)

  • Delete a book (DELETE)

Creating a Simple Server

Let's start by creating a basic HTTP server.

Create a file named main.go and add the following code:

package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", homePage)
    fmt.Println("Server starting on port 8080...")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

func homePage(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Welcome to the Simple REST API in Go!")
}

Run the server:

go run main.go

Visit http://localhost:8080 in your browser, and you should see "Welcome to the Simple REST API in Go!"

Defining a Data Model

Let's define a Book struct to represent our data model.

Add the following code to main.go:

type Book struct {
    ID     string `json:"id"`
    Title  string `json:"title"`
    Author string `json:"author"`
}

var books []Book

Initialize some sample data:

func init() {
    books = []Book{
        {ID: "1", Title: "1984", Author: "George Orwell"},
        {ID: "2", Title: "To Kill a Mockingbird", Author: "Harper Lee"},
        {ID: "3", Title: "The Great Gatsby", Author: "F. Scott Fitzgerald"},
    }
}

Implementing API Endpoints

Get All Books

Add the following handler function:

func getBooks(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(books)
}

Don't forget to import the encoding/json package at the top:

import (
    "encoding/json"
    // other imports
)

Update the main function to handle this route:

func main() {
    http.HandleFunc("/", homePage)
    http.HandleFunc("/books", getBooks)
    // rest of the code
}

Now, when you navigate to http://localhost:8080/books, you should see the list of books in JSON format.

Get a Single Book

Add the handler function:

func getBook(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    id := r.URL.Query().Get("id")
    for _, book := range books {
        if book.ID == id {
            json.NewEncoder(w).Encode(book)
            return
        }
    }
    http.Error(w, "Book not found", http.StatusNotFound)
}

Update main to handle this route:

func main() {
    http.HandleFunc("/", homePage)
    http.HandleFunc("/books", getBooks)
    http.HandleFunc("/book", getBook)
    // rest of the code
}

Now you can get a single book by ID: http://localhost:8080/book?id=1

Add a New Book

Add the handler function:

func createBook(w http.ResponseWriter, r *http.Request) {
    if r.Method != "POST" {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }
    w.Header().Set("Content-Type", "application/json")
    var newBook Book
    err := json.NewDecoder(r.Body).Decode(&newBook)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    books = append(books, newBook)
    json.NewEncoder(w).Encode(newBook)
}

Update main:

func main() {
    http.HandleFunc("/", homePage)
    http.HandleFunc("/books", getBooks)
    http.HandleFunc("/book", getBook)
    http.HandleFunc("/book/create", createBook)
    // rest of the code
}

Now you can add a new book by sending a POST request to http://localhost:8080/book/create with a JSON body.

Update a Book

Add the handler function:

func updateBook(w http.ResponseWriter, r *http.Request) {
    if r.Method != "PUT" {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }
    w.Header().Set("Content-Type", "application/json")
    id := r.URL.Query().Get("id")
    var updatedBook Book
    err := json.NewDecoder(r.Body).Decode(&updatedBook)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    for i, book := range books {
        if book.ID == id {
            books[i] = updatedBook
            json.NewEncoder(w).Encode(updatedBook)
            return
        }
    }
    http.Error(w, "Book not found", http.StatusNotFound)
}

Update main:

func main() {
    http.HandleFunc("/", homePage)
    http.HandleFunc("/books", getBooks)
    http.HandleFunc("/book", getBook)
    http.HandleFunc("/book/create", createBook)
    http.HandleFunc("/book/update", updateBook)
    // rest of the code
}

You can update a book by sending a PUT request to http://localhost:8080/book/update?id=1.

Delete a Book

Add the handler function:

func deleteBook(w http.ResponseWriter, r *http.Request) {
    if r.Method != "DELETE" {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }
    w.Header().Set("Content-Type", "application/json")
    id := r.URL.Query().Get("id")
    for i, book := range books {
        if book.ID == id {
            books = append(books[:i], books[i+1:]...)
            fmt.Fprintf(w, "Book with ID %s deleted", id)
            return
        }
    }
    http.Error(w, "Book not found", http.StatusNotFound)
}

Update main:

func main() {
    http.HandleFunc("/", homePage)
    http.HandleFunc("/books", getBooks)
    http.HandleFunc("/book", getBook)
    http.HandleFunc("/book/create", createBook)
    http.HandleFunc("/book/update", updateBook)
    http.HandleFunc("/book/delete", deleteBook)
    // rest of the code
}

Now you can delete a book by sending a DELETE request to http://localhost:8080/book/delete?id=1.

Testing the API

You can test the API endpoints using a tool like Postman or curl.

Example with curl

  • Get all books

      curl http://localhost:8080/books
    
  • Get a single book

      curl http://localhost:8080/book?id=1
    
  • Create a new book

      curl -X POST -H "Content-Type: application/json" -d '{"id":"4","title":"Brave New World","author":"Aldous Huxley"}' http://localhost:8080/book/create
    
  • Update a book

      curl -X PUT -H "Content-Type: application/json" -d '{"id":"4","title":"Brave New World Revisited","author":"Aldous Huxley"}' http://localhost:8080/book/update?id=4
    
  • Delete a book

      curl -X DELETE http://localhost:8080/book/delete?id=4
    

Conclusion

Congratulations! You've built a simple REST API in Go. This basic API can be expanded and improved in many ways, such as adding a database, using a router package for cleaner route handling, and implementing proper error handling and logging.

Next Steps

  • Use a Router Package: Packages like gorilla/mux or chi can make route handling cleaner and more efficient.

  • Connect to a Database: Instead of using an in-memory slice, connect your API to a database like PostgreSQL or MongoDB.

  • Implement Middleware: Add middleware for logging, authentication, and request validation.


By understanding these basics, you're well on your way to building robust and efficient web services in Go. Happy coding!