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/httppackage - basic understanding of JSON handling
You can confirm if Go is installed by running:
go version
Step 1 — Create the Project
Create a new folder for the project:
mkdir go-task-api
cd go-task-api
Now initialize a Go module:
go mod init go-task-api
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
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)
}
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"`
}
Each task contains:
- an
ID - a
Title
This struct defines the JSON format our API will use.
For example:
{"id":1,"title":"Learn Go"}
Understanding In-Memory Storage
This line:
var tasks []Task
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
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:
-
PUTfor updating tasks -
DELETEfor 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)
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:
Inside this block:
var task Task
we create a task variable to store the incoming JSON data.
Then we decode the request body:
json.NewDecoder(r.Body).Decode(&task)
If decoding succeeds, we add the task to the slice:
tasks = append(tasks, task)
Finally, we return the created task as JSON.
Understanding append
This line:
tasks = append(tasks, task)
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
}
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
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"}'
You should receive:
{"id":1,"title":"Learn Go"}
Step 6 — Retrieve Tasks
Now retrieve all tasks using:
curl http://localhost:8080/tasks
You should receive:
[{"id":1,"title":"Learn Go"}]
What Happens When a Request Is Made?
Here's the flow:
- The client sends an HTTP request
- Go checks the request method
- The correct code block runs
- Tasks are created or retrieved
- 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!