Building a Simple Task API in Go

go dev.to

Previously, we learned how to send and receive JSON data in Go.

One of the next steps in building backend applications is creating APIs with multiple endpoints that can handle different types of requests.

In this tutorial, we will build a simple task API in Go using only the standard library.

By the end, you will understand:

  • how API endpoints work
  • how to handle multiple HTTP methods
  • how to store data in memory using slices
  • how to send and receive JSON data
  • how backend APIs manage resources

Prerequisites

To follow along, you should have:

  • Go installed
  • basic familiarity with Go syntax
  • understanding of the net/http package
  • basic understanding of JSON handling

You can confirm if Go is installed by running:

go version
Enter fullscreen mode Exit fullscreen mode

Step 1 — Create the Project

Create a new folder for the project:

mkdir go-task-api
cd go-task-api
Enter fullscreen mode Exit fullscreen mode

Now initialize a Go module:

go mod init go-task-api
Enter fullscreen mode Exit fullscreen mode

This creates a go.mod file for managing project dependencies.

Step 2 — Create the Server File

Create a file called main.go.

Your project structure should now look like this:

go-task-api/
├─ go.mod
└─ main.go
Enter fullscreen mode Exit fullscreen mode

Step 3 — Write the API

Open main.go and add the following code:

package main

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

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

var tasks []Task

func tasksHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")

    switch r.Method {

    case http.MethodGet:
        json.NewEncoder(w).Encode(tasks)

    case http.MethodPost:
        var task Task

        err := json.NewDecoder(r.Body).Decode(&task)
        if err != nil {
            http.Error(w, "Invalid JSON", http.StatusBadRequest)
            return
        }

        tasks = append(tasks, task)

        json.NewEncoder(w).Encode(task)

    default:
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
    }
}

func main() {
    http.HandleFunc("/tasks", tasksHandler)

    http.ListenAndServe(":8080", nil)
}
Enter fullscreen mode Exit fullscreen mode

Now let's unpack what is happening.

Understanding the Struct

This struct represents a task:

type Task struct {
    ID    int    `json:"id"`
    Title string `json:"title"`
}
Enter fullscreen mode Exit fullscreen mode

Each task contains:

  • an ID
  • a Title

This struct defines the JSON format our API will use.

For example:

{"id":1,"title":"Learn Go"}
Enter fullscreen mode Exit fullscreen mode

Understanding In-Memory Storage

This line:

var tasks []Task
Enter fullscreen mode Exit fullscreen mode

creates a slice that stores tasks in memory.

For now, our data only exists while the server is running.

Once the server stops, the data disappears.

This keeps the tutorial simple before introducing databases later.

Understanding HTTP Methods

This part checks the request method:

switch r.Method
Enter fullscreen mode Exit fullscreen mode

HTTP methods tell the server what action the client wants to perform.

Here, we handle:

Method Purpose
GET Retrieves tasks
POST Creates tasks

Later, we could also add:

  • PUT for updating tasks
  • DELETE for removing tasks

Because, you know, every backend journey eventually leads there.

Handling GET Requests

This section handles fetching tasks:

case http.MethodGet:
    json.NewEncoder(w).Encode(tasks)
Enter fullscreen mode Exit fullscreen mode

When the client sends a GET request:

  • the server retrieves all tasks
  • the tasks are converted into JSON
  • the JSON response is sent back to the client

Handling POST Requests

This section handles creating tasks:

case http.MethodPost:
Enter fullscreen mode Exit fullscreen mode

Inside this block:

var task Task
Enter fullscreen mode Exit fullscreen mode

we create a task variable to store the incoming JSON data.

Then we decode the request body:

json.NewDecoder(r.Body).Decode(&task)
Enter fullscreen mode Exit fullscreen mode

If decoding succeeds, we add the task to the slice:

tasks = append(tasks, task)
Enter fullscreen mode Exit fullscreen mode

Finally, we return the created task as JSON.

Understanding append

This line:

tasks = append(tasks, task)
Enter fullscreen mode Exit fullscreen mode

adds a new task to the slice.

Slices in Go are dynamic, that is, they can grow as we add more data.

This allows our API to store multiple tasks.

Understanding Error Handling

This section checks whether the JSON request is valid:

if err != nil {
    http.Error(w, "Invalid JSON", http.StatusBadRequest)
    return
}
Enter fullscreen mode Exit fullscreen mode

If decoding fails:

  • the server sends an error response
  • the request stops processing

This helps keep the API stable when invalid data is sent.

Step 4 — Run the Server

Start the application:

go run main.go
Enter fullscreen mode Exit fullscreen mode

The server should now be running on port 8080.

Step 5 — Create a Task

We can create a task using curl.

Run:

curl -X POST http://localhost:8080/tasks \
-H "Content-Type: application/json" \
-d '{"id":1,"title":"Learn Go"}'
Enter fullscreen mode Exit fullscreen mode

You should receive:

{"id":1,"title":"Learn Go"}
Enter fullscreen mode Exit fullscreen mode

Step 6 — Retrieve Tasks

Now retrieve all tasks using:

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

You should receive:

[{"id":1,"title":"Learn Go"}]
Enter fullscreen mode Exit fullscreen mode

What Happens When a Request Is Made?

Here's the flow:

  1. The client sends an HTTP request
  2. Go checks the request method
  3. The correct code block runs
  4. Tasks are created or retrieved
  5. JSON responses are returned to the client

This is the foundation of many backend APIs.

Where to Go Next

Now that we have a simple task API, we could extend it by adding:

  • PUT requests
  • DELETE requests
  • route parameters
  • databases
  • request validation
  • authentication

This is where backend applications start becoming much more powerful and realistic.

Final Thoughts

We started with simple HTTP servers and gradually built a working task API.

Along the way, we learned about:

  • HTTP methods
  • JSON encoding
  • JSON decoding
  • slices
  • request handling
  • API design

These are the foundational backend development concepts that appear in many real-world applications.

Thanks for reading!

Happy coding!

Source: dev.to

arrow_back Back to Tutorials