Go

 

Building RESTful APIs with Go and the Chi Framework

In the world of web development, building robust and efficient RESTful APIs is a common requirement. Go, also known as Golang, has gained popularity for its performance and simplicity, making it an excellent choice for creating RESTful APIs. To streamline the development process and make it even more efficient, we have the Chi framework at our disposal. In this blog, we’ll explore how to build RESTful APIs with Go and the Chi framework, step by step.

Go for Financial Applications: Handling Currency and Transactions

1. Prerequisites

Before we dive into building RESTful APIs with Go and the Chi framework, make sure you have the following prerequisites in place:

  • Go Installed: Ensure you have Go installed on your system. You can download and install it from the official Go website.
  • Text Editor or IDE: Choose a text editor or integrated development environment (IDE) for writing and running your Go code. Popular choices include Visual Studio Code, GoLand, and Sublime Text.
  • Basic Knowledge of Go: Familiarize yourself with the basics of the Go programming language. If you’re new to Go, the official Go Tour is a great place to start.

2. Setting Up Your Project

Let’s start by setting up a new Go project for building our RESTful API with the Chi framework. Follow these steps:

2.1. Create a Project Directory

Create a new directory for your project. You can do this using the mkdir command in your terminal.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bash
mkdir my-api-project
cd my-api-project
bash mkdir my-api-project cd my-api-project
bash
mkdir my-api-project
cd my-api-project

2.2. Initialize a Go Module

Initialize a Go module in your project directory. This will help manage your project’s dependencies.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bash
go mod init my-api-project
bash go mod init my-api-project
bash
go mod init my-api-project

2.3. Install Chi

Install the Chi framework by running the following command:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bash
go get github.com/go-chi/chi
bash go get github.com/go-chi/chi
bash
go get github.com/go-chi/chi

3. Building a Simple RESTful API

Now that our project is set up, let’s create a simple RESTful API using Chi. We’ll build an API that allows us to manage a list of tasks. Our API will support basic CRUD (Create, Read, Update, Delete) operations.

3.1. Define Your Data Model

In this example, we’ll work with a simple task model that has a title and a description. Create a task.go file in your project directory to define the data model:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
go
package main
type Task struct {
ID int `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
}
go package main type Task struct { ID int `json:"id"` Title string `json:"title"` Description string `json:"description"` }
go
package main

type Task struct {
    ID          int    `json:"id"`
    Title       string `json:"title"`
    Description string `json:"description"`
}

3.2. Create the API Routes

Now, let’s create the API routes for our tasks. We’ll define routes for creating, reading, updating, and deleting tasks. Create a main.go file and add the following code:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
go
package main
import (
"encoding/json"
"net/http"
"strconv"
"github.com/go-chi/chi"
)
var tasks = make(map[int]Task)
var lastID = 0
func main() {
r := chi.NewRouter()
r.Post("/tasks", createTask)
r.Get("/tasks/{id}", getTask)
r.Get("/tasks", listTasks)
r.Put("/tasks/{id}", updateTask)
r.Delete("/tasks/{id}", deleteTask)
http.ListenAndServe(":8080", r)
}
func createTask(w http.ResponseWriter, r *http.Request) {
var task Task
err := json.NewDecoder(r.Body).Decode(&task)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
lastID++
task.ID = lastID
tasks[lastID] = task
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(task)
}
func getTask(w http.ResponseWriter, r *http.Request) {
idStr := chi.URLParam(r, "id")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid task ID", http.StatusBadRequest)
return
}
task, found := tasks[id]
if !found {
http.Error(w, "Task not found", http.StatusNotFound)
return
}
json.NewEncoder(w).Encode(task)
}
func listTasks(w http.ResponseWriter, r *http.Request) {
taskList := make([]Task, 0, len(tasks))
for _, task := range tasks {
taskList = append(taskList, task)
}
json.NewEncoder(w).Encode(taskList)
}
func updateTask(w http.ResponseWriter, r *http.Request) {
idStr := chi.URLParam(r, "id")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid task ID", http.StatusBadRequest)
return
}
var updatedTask Task
err = json.NewDecoder(r.Body).Decode(&updatedTask)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
_, found := tasks[id]
if !found {
http.Error(w, "Task not found", http.StatusNotFound)
return
}
updatedTask.ID = id
tasks[id] = updatedTask
json.NewEncoder(w).Encode(updatedTask)
}
func deleteTask(w http.ResponseWriter, r *http.Request) {
idStr := chi.URLParam(r, "id")
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "Invalid task ID", http.StatusBadRequest)
return
}
_, found := tasks[id]
if !found {
http.Error(w, "Task not found", http.StatusNotFound)
return
}
delete(tasks, id)
w.WriteHeader(http.StatusNoContent)
}
go package main import ( "encoding/json" "net/http" "strconv" "github.com/go-chi/chi" ) var tasks = make(map[int]Task) var lastID = 0 func main() { r := chi.NewRouter() r.Post("/tasks", createTask) r.Get("/tasks/{id}", getTask) r.Get("/tasks", listTasks) r.Put("/tasks/{id}", updateTask) r.Delete("/tasks/{id}", deleteTask) http.ListenAndServe(":8080", r) } func createTask(w http.ResponseWriter, r *http.Request) { var task Task err := json.NewDecoder(r.Body).Decode(&task) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } lastID++ task.ID = lastID tasks[lastID] = task w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(task) } func getTask(w http.ResponseWriter, r *http.Request) { idStr := chi.URLParam(r, "id") id, err := strconv.Atoi(idStr) if err != nil { http.Error(w, "Invalid task ID", http.StatusBadRequest) return } task, found := tasks[id] if !found { http.Error(w, "Task not found", http.StatusNotFound) return } json.NewEncoder(w).Encode(task) } func listTasks(w http.ResponseWriter, r *http.Request) { taskList := make([]Task, 0, len(tasks)) for _, task := range tasks { taskList = append(taskList, task) } json.NewEncoder(w).Encode(taskList) } func updateTask(w http.ResponseWriter, r *http.Request) { idStr := chi.URLParam(r, "id") id, err := strconv.Atoi(idStr) if err != nil { http.Error(w, "Invalid task ID", http.StatusBadRequest) return } var updatedTask Task err = json.NewDecoder(r.Body).Decode(&updatedTask) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } _, found := tasks[id] if !found { http.Error(w, "Task not found", http.StatusNotFound) return } updatedTask.ID = id tasks[id] = updatedTask json.NewEncoder(w).Encode(updatedTask) } func deleteTask(w http.ResponseWriter, r *http.Request) { idStr := chi.URLParam(r, "id") id, err := strconv.Atoi(idStr) if err != nil { http.Error(w, "Invalid task ID", http.StatusBadRequest) return } _, found := tasks[id] if !found { http.Error(w, "Task not found", http.StatusNotFound) return } delete(tasks, id) w.WriteHeader(http.StatusNoContent) }
go
package main

import (
    "encoding/json"
    "net/http"
    "strconv"

    "github.com/go-chi/chi"
)

var tasks = make(map[int]Task)
var lastID = 0

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

    r.Post("/tasks", createTask)
    r.Get("/tasks/{id}", getTask)
    r.Get("/tasks", listTasks)
    r.Put("/tasks/{id}", updateTask)
    r.Delete("/tasks/{id}", deleteTask)

    http.ListenAndServe(":8080", r)
}

func createTask(w http.ResponseWriter, r *http.Request) {
    var task Task
    err := json.NewDecoder(r.Body).Decode(&task)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    lastID++
    task.ID = lastID
    tasks[lastID] = task

    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(task)
}

func getTask(w http.ResponseWriter, r *http.Request) {
    idStr := chi.URLParam(r, "id")
    id, err := strconv.Atoi(idStr)
    if err != nil {
        http.Error(w, "Invalid task ID", http.StatusBadRequest)
        return
    }

    task, found := tasks[id]
    if !found {
        http.Error(w, "Task not found", http.StatusNotFound)
        return
    }

    json.NewEncoder(w).Encode(task)
}

func listTasks(w http.ResponseWriter, r *http.Request) {
    taskList := make([]Task, 0, len(tasks))
    for _, task := range tasks {
        taskList = append(taskList, task)
    }

    json.NewEncoder(w).Encode(taskList)
}

func updateTask(w http.ResponseWriter, r *http.Request) {
    idStr := chi.URLParam(r, "id")
    id, err := strconv.Atoi(idStr)
    if err != nil {
        http.Error(w, "Invalid task ID", http.StatusBadRequest)
        return
    }

    var updatedTask Task
    err = json.NewDecoder(r.Body).Decode(&updatedTask)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    _, found := tasks[id]
    if !found {
        http.Error(w, "Task not found", http.StatusNotFound)
        return
    }

    updatedTask.ID = id
    tasks[id] = updatedTask

    json.NewEncoder(w).Encode(updatedTask)
}

func deleteTask(w http.ResponseWriter, r *http.Request) {
    idStr := chi.URLParam(r, "id")
    id, err := strconv.Atoi(idStr)
    if err != nil {
        http.Error(w, "Invalid task ID", http.StatusBadRequest)
        return
    }

    _, found := tasks[id]
    if !found {
        http.Error(w, "Task not found", http.StatusNotFound)
        return
    }

    delete(tasks, id)
    w.WriteHeader(http.StatusNoContent)
}

This code defines a simple RESTful API with routes for creating, reading, updating, and deleting tasks. It uses the Chi router to handle HTTP requests.

3.3. Run the API

To run the API, use the following command:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bash
go run main.go
bash go run main.go
bash
go run main.go

Your API should now be running on http://localhost:8080.

4. Testing Your RESTful API

Now that you have built your RESTful API, it’s essential to test it to ensure it works as expected. You can use tools like curl, Postman, or write test cases in Go using the testing package.

4.1. Using curl

Here are some curl commands to test your API:

Create a Task:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bash
curl -X POST -H "Content-Type: application/json" -d '{"title": "Task 1", "description": "Description 1"}' http://localhost:8080/tasks
bash curl -X POST -H "Content-Type: application/json" -d '{"title": "Task 1", "description": "Description 1"}' http://localhost:8080/tasks
bash
curl -X POST -H "Content-Type: application/json" -d '{"title": "Task 1", "description": "Description 1"}' http://localhost:8080/tasks

Get a Task:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bash
curl http://localhost:8080/tasks/1
bash curl http://localhost:8080/tasks/1
bash
curl http://localhost:8080/tasks/1

List Tasks:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bash
curl http://localhost:8080/tasks
bash curl http://localhost:8080/tasks
bash
curl http://localhost:8080/tasks

Update a Task:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bash
curl -X PUT -H "Content-Type: application/json" -d '{"title": "Updated Task 1", "description": "Updated Description 1"}' http://localhost:8080/tasks/1
bash curl -X PUT -H "Content-Type: application/json" -d '{"title": "Updated Task 1", "description": "Updated Description 1"}' http://localhost:8080/tasks/1
bash
curl -X PUT -H "Content-Type: application/json" -d '{"title": "Updated Task 1", "description": "Updated Description 1"}' http://localhost:8080/tasks/1

Delete a Task:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bash
curl -X DELETE http://localhost:8080/tasks/1
bash curl -X DELETE http://localhost:8080/tasks/1
bash
curl -X DELETE http://localhost:8080/tasks/1

5. Writing Go Test Cases

You can also write test cases in Go to ensure the correctness of your API. Create a main_test.go file in your project directory with the following code:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
go
package main
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/go-chi/chi"
)
func TestAPI(t *testing.T) {
r := chi.NewRouter()
r.Post("/tasks", createTask)
r.Get("/tasks/{id}", getTask)
r.Get("/tasks", listTasks)
r.Put("/tasks/{id}", updateTask)
r.Delete("/tasks/{id}", deleteTask)
srv := httptest.NewServer(r)
defer srv.Close()
t.Run("CreateTask", func(t *testing.T) {
payload := `{"title": "Task 1", "description": "Description 1"}`
resp, err := http.Post(srv.URL+"/tasks", "application/json", strings.NewReader(payload))
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusCreated {
t.Errorf("Expected status code %d, got %d", http.StatusCreated, resp.StatusCode)
}
var task Task
if err := json.NewDecoder(resp.Body).Decode(&task); err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if task.ID == 0 {
t.Error("Expected non-zero task ID")
}
})
// Add more test cases for GET, PUT, and DELETE operations
}
go package main import ( "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "github.com/go-chi/chi" ) func TestAPI(t *testing.T) { r := chi.NewRouter() r.Post("/tasks", createTask) r.Get("/tasks/{id}", getTask) r.Get("/tasks", listTasks) r.Put("/tasks/{id}", updateTask) r.Delete("/tasks/{id}", deleteTask) srv := httptest.NewServer(r) defer srv.Close() t.Run("CreateTask", func(t *testing.T) { payload := `{"title": "Task 1", "description": "Description 1"}` resp, err := http.Post(srv.URL+"/tasks", "application/json", strings.NewReader(payload)) if err != nil { t.Fatalf("Expected no error, got %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusCreated { t.Errorf("Expected status code %d, got %d", http.StatusCreated, resp.StatusCode) } var task Task if err := json.NewDecoder(resp.Body).Decode(&task); err != nil { t.Fatalf("Expected no error, got %v", err) } if task.ID == 0 { t.Error("Expected non-zero task ID") } }) // Add more test cases for GET, PUT, and DELETE operations }
go
package main

import (
    "encoding/json"
    "net/http"
    "net/http/httptest"
    "strings"
    "testing"

    "github.com/go-chi/chi"
)

func TestAPI(t *testing.T) {
    r := chi.NewRouter()
    r.Post("/tasks", createTask)
    r.Get("/tasks/{id}", getTask)
    r.Get("/tasks", listTasks)
    r.Put("/tasks/{id}", updateTask)
    r.Delete("/tasks/{id}", deleteTask)

    srv := httptest.NewServer(r)
    defer srv.Close()

    t.Run("CreateTask", func(t *testing.T) {
        payload := `{"title": "Task 1", "description": "Description 1"}`
        resp, err := http.Post(srv.URL+"/tasks", "application/json", strings.NewReader(payload))
        if err != nil {
            t.Fatalf("Expected no error, got %v", err)
        }
        defer resp.Body.Close()

        if resp.StatusCode != http.StatusCreated {
            t.Errorf("Expected status code %d, got %d", http.StatusCreated, resp.StatusCode)
        }

        var task Task
        if err := json.NewDecoder(resp.Body).Decode(&task); err != nil {
            t.Fatalf("Expected no error, got %v", err)
        }

        if task.ID == 0 {
            t.Error("Expected non-zero task ID")
        }
    })

    // Add more test cases for GET, PUT, and DELETE operations
}

In this example, we use the httptest package to create an HTTP server for testing our API. You can expand this test suite to cover all the endpoints and scenarios of your API.

Conclusion

Building RESTful APIs with Go and the Chi framework is a powerful and efficient way to create robust web services. In this blog, we covered the basics of setting up a Go project, defining API routes, and testing your API. You can now use this knowledge to develop RESTful APIs for various applications and services. Remember to follow best practices for error handling, input validation, and security when building production-ready APIs. Happy coding!

Previously at
Flag Argentina
Mexico
time icon
GMT-6
Over 5 years of experience in Golang. Led the design and implementation of a distributed system and platform for building conversational chatbots.