Previously, we built a simple task API that could create and retrieve tasks.
If you have been following this series, we've gradually progressed from building a simple HTTP server to creating a JSON-based task API. In this article, we will complete that progression by adding update and delete functionality, giving our API the four operations commonly known as CRUD.
CRUD stands for:
- Create
- Read
- Update
- Delete
These operations form the foundation of many backend applications and APIs.
In this tutorial, we will extend our task API using only Go's standard library. To keep things simple, we'll continue storing tasks in memory using a slice rather than a database.
By the end, you'll understand:
- how CRUD APIs work
- how to handle multiple HTTP methods
- how to update existing data
- how to remove data from slices
- 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-crud-api
cd go-crud-api
Now initialize a Go module:
go mod init go-crud-api
Step 2 — Create the Server File
Create a file called main.go.
Your project structure should now look like this.
go-crud-api/
├─ go.mod
└─ main.go
Step 3 — Write the CRUD 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)
case http.MethodPut:
var updatedTask Task
err := json.NewDecoder(r.Body).Decode(&updatedTask)
if err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
for i, task := range tasks {
if task.ID == updatedTask.ID {
tasks[i].Title = updatedTask.Title
json.NewEncoder(w).Encode(tasks[i])
return
}
}
http.Error(w, "Task not found", http.StatusNotFound)
case http.MethodDelete:
var taskToDelete Task
err := json.NewDecoder(r.Body).Decode(&taskToDelete)
if err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
for i, task := range tasks {
if task.ID == taskToDelete.ID {
tasks = append(tasks[:i], tasks[i+1:]...)
w.Write([]byte("Task deleted"))
return
}
}
http.Error(w, "Task not found", http.StatusNotFound)
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 CRUD Operations
CRUD represents the four most common actions performed on data.
| Operation | HTTP Method |
|---|---|
| Create | POST |
| Read | GET |
| Update | PUT |
| Delete | DELETE |
Most backend APIs are built around these operations.
Create and Read
The POST and GET handlers work exactly as they did in the previous tutorial.
-
POSTcreates tasks -
GETretrieves tasks
These operations allow us to add and view data.
Understanding Updates
This section handles updates:
case http.MethodPut:
The server expects updated task information in the request body.
For example:
{
"id": 1,
"title": "Learn Advanced Go"
}
After decoding the request, we search for a matching task:
for i, task := range tasks {
if task.ID == updatedTask.ID {
If a match is found, we update the title:
tasks[i].Title = updatedTask.Title
Then we return the updated task as JSON.
Understanding Deletes
This section handles deleting tasks:
case http.MethodDelete:
The client sends the ID of the task to remove:
{
"id": 1
}
We search for the task with matching ID and remove it from the slice.
The deletion happens here:
tasks = append(tasks[:i], tasks[i+1:]...)
This combines:
- everything before the task
- everything after the task
into a new slice that no longer contains the deleted item.
Understanding Slice Deletion
This line often looks strange at first:
tasks = append(tasks[:i], tasks[i+1:]...)
Let's break it down.
Suppose we have:
tasks = [1 2 3 4]
and want to remove:
2
Then:
tasks[:i]
gives:
[1]
and:
tasks[i+1:]
gives:
[3 4]
Appending them together creates:
[1 3 4]
The item has effectively been removed.
Go Tip: The
append(tasks[:i], tasks[i+1:]...)pattern is one of the most common ways to remove an element from a slice in Go. You'll see it in many Go projects, so it's worth understanding how it works.
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
curl -X POST http://localhost:8080/tasks \
-H "Content-Type: application/json" \
-d '{"id":1,"title":"Learn Go"}'
Response:
{
"id": 1,
"title": "Learn Go"
}
Step 6 — Retrieve Tasks
curl http://localhost:8080/tasks
Response:
[
{
"id": 1,
"title": "Learn Go"
}
]
Step 7 — Update a Task
curl -X PUT http://localhost:8080/tasks \
-H "Content-Type: application/json" \
-d '{"id":1,"title":"Learn Advanced Go"}'
Response:
{
"id": 1,
"title": "Learn Advanced Go"
}
Step 8 — Delete a Task
curl -X DELETE http://localhost:8080/tasks \
-H "Content-Type: application/json" \
-d '{"id":1}'
Response:
Task deleted
What Happens When a Request Is Made?
Here's the flow:
- The client sends an HTTP request.
- Go checks the request method.
- The appropriate handler creates, retrieves, updates, or deletes a task.
- The server prepares the response.
- A response is sent back to the client
This is the foundation of many real-world APIs.
Where to Go Next
Now that we have a simple CRUD API, we could extend it by adding:
- route parameters
- persistent databases
- request validation
- middleware
- authentication
This is where backend applications begin moving closer to production-ready systems.
Final Thoughts
We started with a simple task API and extended it into a complete CRUD API.
Along the way, we learned about:
- HTTP methods
- updating data
- deleting data
- slice manipulation
- API design
These concepts appear in countless backend applications and services.
Thanks for reading!
Happy coding!
Top comments (0)