Build Your First REST API with Go: A Complete Guide for Beginners
Are you ready to build your first production-ready REST API with Go? In this hands-on tutorial, we’ll create a task management API from scratch, learning essential Go programming concepts along the way. Whether you’re new to Go or looking to enhance your backend development skills, this guide will walk you through every step of building a robust API.
This is Part 2 of our “Mastering Go Programming” series. If you haven’t already, check out Part 1: Getting Started with Go in 2024 first.
What You’ll Learn in This Tutorial
By following this tutorial, you’ll learn how to:
- Structure a Go project using best practices
- Create RESTful endpoints with Go’s net/http package
- Handle JSON requests and responses
- Implement basic CRUD operations
- Manage in-memory data storage
- Handle errors effectively in Go
- Test your API endpoints
Prerequisites:
- Basic understanding of Go syntax
- Go installed on your system (version 1.21 or later)
- A code editor (VS Code recommended)
- Basic knowledge of REST APIs
Creating a Production-Ready Project Structure
First, let’s set up our project with a scalable directory structure that follows Go best practices:
mkdir task-manager
cd task-manager
go mod init github.com/yourusername/task-manager
Create this directory structure for optimal organization:
task-manager/
├── cmd/
│ └── api/
│ └── main.go
├── internal/
│ ├── handlers/
│ │ └── task_handlers.go
│ └── models/
│ └── task.go
├── pkg/
│ └── middleware/
│ └── logging.go
└── go.mod
Setting Up Our Task Model
Let’s create our task model with proper JSON tags and validation. Create internal/models/task.go
:
package models
import (
"time"
"errors"
)
type Task struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Status string `json:"status"`
DueDate time.Time `json:"due_date"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// TaskStatus represents the valid states of a task
type TaskStatus string
const (
StatusPending TaskStatus = "pending"
StatusInProgress TaskStatus = "in_progress"
StatusCompleted TaskStatus = "completed"
)
// Validate checks if the task data is valid
func (t *Task) Validate() error {
if t.Title == "" {
return errors.New("title is required")
}
if len(t.Title) < 3 {
return errors.New("title must be at least 3 characters long")
}
return nil
}
// Store tasks in memory (we'll replace this with a database in Part 3)
var Tasks = make(map[string]Task)
Implementing RESTful Handlers
Create internal/handlers/task_handlers.go
to handle our API endpoints:
package handlers
import (
"encoding/json"
"net/http"
"time"
"github.com/google/uuid"
"../models"
)
// CreateTaskHandler handles POST requests to create new tasks
func CreateTaskHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var task models.Task
if err := json.NewDecoder(r.Body).Decode(&task); err != nil {
http.Error(w, "Invalid request body", http.StatusBadRequest)
return
}
// Validate task data
if err := task.Validate(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Generate UUID and set timestamps
task.ID = uuid.New().String()
task.CreatedAt = time.Now()
task.UpdatedAt = time.Now()
task.Status = string(models.StatusPending)
// Store the task
models.Tasks[task.ID] = task
// Return the created task
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(task)
}
// GetTasksHandler returns all tasks
func GetTasksHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
tasks := make([]models.Task, 0, len(models.Tasks))
for _, task := range models.Tasks {
tasks = append(tasks, task)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(tasks)
}
Setting Up the Main Application
Create cmd/api/main.go
to tie everything together:
package main
import (
"log"
"net/http"
"github.com/yourusername/task-manager/internal/handlers"
"github.com/yourusername/task-manager/pkg/middleware"
)
func main() {
// Initialize router
mux := http.NewServeMux()
// Register routes with logging middleware
mux.HandleFunc("/api/tasks", middleware.LogRequest(handlers.GetTasksHandler))
mux.HandleFunc("/api/task", middleware.LogRequest(handlers.CreateTaskHandler))
// Configure server
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
// Start server
log.Printf("Server starting on http://localhost:8080")
if err := server.ListenAndServe(); err != nil {
log.Fatal("Server failed to start:", err)
}
}
Testing Your API
Let’s test our API endpoints using cURL:
# Create a new task
curl -X POST http://localhost:8080/api/task \
-H "Content-Type: application/json" \
-d '{
"title": "Learn Go Testing",
"description": "Learn how to write unit tests in Go",
"due_date": "2024-02-01T15:04:05Z"
}'
# Get all tasks
curl http://localhost:8080/api/tasks
Best Practices and Tips
Error Handling
- Always return appropriate HTTP status codes
- Provide meaningful error messages
- Log errors for debugging
- Never expose sensitive information in error responses
API Design
- Use plural nouns for resource endpoints
- Keep URLs consistent and intuitive
- Version your API (/api/v1/tasks)
- Use appropriate HTTP methods
Code Organization
- Separate concerns (handlers, models, middleware)
- Use interfaces for flexibility
- Keep functions small and focused
- Comment complex logic
Next Steps: Extending Your API
Try these challenges to enhance your API:
Add endpoints for:
- Updating tasks (
PUT /api/task/{id}
) - Deleting tasks (
DELETE /api/task/{id}
) - Getting a single task (
GET /api/task/{id}
)
- Updating tasks (
Implement advanced features:
- Task filtering and sorting
- Pagination
- Task categories or tags
- Due date notifications
Coming Up in Part 3
In the next tutorial, we’ll enhance our API with:
- PostgreSQL database integration
- JWT authentication
- Request validation middleware
- Unit and integration tests
- API documentation with Swagger
- Deployment best practices
Share Your Progress!
How did you customize your task manager API? Share your implementations and questions in the comments below. Don’t forget to follow our blog for weekly Go tutorials and tips!
Additional Resources
Last updated: January 5, 2025