Building a Simple REST API in Go with Gin

If you're new to Go and interested in building RESTful APIs, the Gin web framework is a fantastic tool to have in your toolkit. In this guide, we'll walk through creating a simple REST API using Gin, complete with code examples to help you get started.

What is Gin?

Gin is a high-performance web framework written in Go. It provides a fast and easy way to build web applications and microservices. Gin is similar to other web frameworks like Martini but is up to 40 times faster, making it a popular choice for developers who need performance and simplicity.

Why Use Gin for REST APIs?

  • Speed: Gin is built for speed, making it ideal for high-performance APIs.

  • Simplicity: It has a straightforward and clean syntax, perfect for beginners.

  • Middleware Support: Gin supports middleware, allowing you to add functionalities like logging, authentication, and more.

  • Community and Documentation: Gin has a strong community and excellent documentation.

Prerequisites

Before we start, ensure you have the following:

  • Go installed on your system (Download Go).

  • Basic knowledge of Go programming.

  • A code editor (e.g., VSCode, GoLand).

Setting Up the Project

First, create a new directory for your project:

mkdir gin-rest-api
cd gin-rest-api

Initialize a new Go module:

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

Replace github.com/yourusername/gin-rest-api with your actual GitHub username or module path.

Building the REST API

We'll create an API to manage a list of books with the following operations:

  • Get all books (GET)

  • Get a single book by ID (GET)

  • Add a new book (POST)

  • Update an existing book (PUT)

  • Delete a book (DELETE)

Installing Gin

To use Gin, you need to install it first:

go get -u github.com/gin-gonic/gin

Creating a Simple Server

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

package main

import (
    "github.com/gin-gonic/gin"
)

func main() {
    router := gin.Default()
    router.GET("/", homePage)
    router.Run(":8080")
}

func homePage(c *gin.Context) {
    c.JSON(200, gin.H{
        "message": "Welcome to the Gin REST API!",
    })
}

Run the server:

go run main.go

Open http://localhost:8080 in your browser, and you should see:

{"message":"Welcome to the Gin REST API!"}

Defining the Data Model

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

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

var 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"},
}

Add this code above the main function in main.go.

Implementing API Endpoints

Get All Books

Add the following handler function:

func getBooks(c *gin.Context) {
    c.IndentedJSON(200, books)
}

Update your main function to include the new route:

func main() {
    router := gin.Default()
    router.GET("/", homePage)
    router.GET("/books", getBooks)
    router.Run(":8080")
}

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

Get a Single Book by ID

Add the handler function:

func getBookByID(c *gin.Context) {
    id := c.Param("id")

    for _, book := range books {
        if book.ID == id {
            c.IndentedJSON(200, book)
            return
        }
    }
    c.JSON(404, gin.H{"message": "Book not found"})
}

Update your routes:

router.GET("/books/:id", getBookByID)

Now you can get a book by ID:

  • http://localhost:8080/books/1

Add a New Book

Add the handler function:

func createBook(c *gin.Context) {
    var newBook Book

    // Bind the received JSON to newBook
    if err := c.BindJSON(&newBook); err != nil {
        return
    }

    books = append(books, newBook)
    c.IndentedJSON(201, newBook)
}

Update your routes:

router.POST("/books", createBook)

You can now add a new book by sending a POST request to http://localhost:8080/books with a JSON body.

Update an Existing Book

Add the handler function:

func updateBook(c *gin.Context) {
    id := c.Param("id")
    var updatedBook Book

    if err := c.BindJSON(&updatedBook); err != nil {
        return
    }

    for i, book := range books {
        if book.ID == id {
            books[i] = updatedBook
            c.IndentedJSON(200, updatedBook)
            return
        }
    }
    c.JSON(404, gin.H{"message": "Book not found"})
}

Update your routes:

router.PUT("/books/:id", updateBook)

Now you can update a book by sending a PUT request to http://localhost:8080/books/1.

Delete a Book

Add the handler function:

func deleteBook(c *gin.Context) {
    id := c.Param("id")

    for i, book := range books {
        if book.ID == id {
            books = append(books[:i], books[i+1:]...)
            c.JSON(200, gin.H{"message": "Book deleted"})
            return
        }
    }
    c.JSON(404, gin.H{"message": "Book not found"})
}

Update your routes:

router.DELETE("/books/:id", deleteBook)

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

Your main.go file should now look like this:

package main

import (
    "github.com/gin-gonic/gin"
)

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

var 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"},
}

func main() {
    router := gin.Default()
    router.GET("/", homePage)
    router.GET("/books", getBooks)
    router.GET("/books/:id", getBookByID)
    router.POST("/books", createBook)
    router.PUT("/books/:id", updateBook)
    router.DELETE("/books/:id", deleteBook)
    router.Run(":8080")
}

func homePage(c *gin.Context) {
    c.JSON(200, gin.H{
        "message": "Welcome to the Gin REST API!",
    })
}

func getBooks(c *gin.Context) {
    c.IndentedJSON(200, books)
}

func getBookByID(c *gin.Context) {
    id := c.Param("id")

    for _, book := range books {
        if book.ID == id {
            c.IndentedJSON(200, book)
            return
        }
    }
    c.JSON(404, gin.H{"message": "Book not found"})
}

func createBook(c *gin.Context) {
    var newBook Book

    if err := c.BindJSON(&newBook); err != nil {
        return
    }

    books = append(books, newBook)
    c.IndentedJSON(201, newBook)
}

func updateBook(c *gin.Context) {
    id := c.Param("id")
    var updatedBook Book

    if err := c.BindJSON(&updatedBook); err != nil {
        return
    }

    for i, book := range books {
        if book.ID == id {
            books[i] = updatedBook
            c.IndentedJSON(200, updatedBook)
            return
        }
    }
    c.JSON(404, gin.H{"message": "Book not found"})
}

func deleteBook(c *gin.Context) {
    id := c.Param("id")

    for i, book := range books {
        if book.ID == id {
            books = append(books[:i], books[i+1:]...)
            c.JSON(200, gin.H{"message": "Book deleted"})
            return
        }
    }
    c.JSON(404, gin.H{"message": "Book not found"})
}

Testing the API

You can test the API using tools like Postman, Insomnia, or curl.

Example with curl

  • Get all books

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

      curl http://localhost:8080/books/1
    
  • Add a new book

      curl -X POST -H "Content-Type: application/json" -d '{"id":"4","title":"Brave New World","author":"Aldous Huxley"}' http://localhost:8080/books
    
  • 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/books/4
    
  • Delete a book

      curl -X DELETE http://localhost:8080/books/4
    

Conclusion

Congratulations! You've built a simple REST API using the Gin framework in Go. This foundational knowledge sets you up to build more complex and feature-rich APIs.

Next Steps

  • Implement Middleware: Use Gin's middleware for logging, authentication, and error handling.

  • Connect to a Database: Replace the in-memory slice with a database like PostgreSQL or MongoDB.

  • Environment Variables: Use environment variables for configuration settings.

  • Struct Validation: Implement validation for your data models using packages like go-playground/validator.


By mastering these basics, you're well on your way to becoming proficient in building web services with Go and Gin. Happy coding!