Como Criar uma API REST com Go

Go é perfeito para APIs: rápido, simples, e a biblioteca padrão já inclui tudo que você precisa.

O que Vamos Construir

Uma API de tarefas (TODO) com:

  • Listar todas as tarefas
  • Criar nova tarefa
  • Buscar tarefa por ID
  • Atualizar tarefa
  • Deletar tarefa

Setup do Projeto

mkdir todo-api
cd todo-api
go mod init todo-api

API Básica com net/http

Crie main.go:

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "strconv"
    "sync"
)

// Modelo
type Task struct {
    ID        int    `json:"id"`
    Title     string `json:"title"`
    Completed bool   `json:"completed"`
}

// "Banco de dados" em memória
var (
    tasks  = make(map[int]Task)
    nextID = 1
    mu     sync.Mutex
)

func main() {
    http.HandleFunc("/tasks", handleTasks)
    http.HandleFunc("/tasks/", handleTaskByID)
    
    fmt.Println("Servidor rodando em http://localhost:8080")
    http.ListenAndServe(":8080", nil)
}

Handlers

Listar e Criar Tarefas

func handleTasks(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case "GET":
        listTasks(w, r)
    case "POST":
        createTask(w, r)
    default:
        http.Error(w, "Método não permitido", http.StatusMethodNotAllowed)
    }
}

func listTasks(w http.ResponseWriter, r *http.Request) {
    mu.Lock()
    defer mu.Unlock()
    
    taskList := make([]Task, 0, len(tasks))
    for _, task := range tasks {
        taskList = append(taskList, task)
    }
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(taskList)
}

func createTask(w http.ResponseWriter, r *http.Request) {
    var task Task
    if err := json.NewDecoder(r.Body).Decode(&task); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    
    mu.Lock()
    task.ID = nextID
    nextID++
    tasks[task.ID] = task
    mu.Unlock()
    
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(task)
}

Buscar, Atualizar e Deletar

func handleTaskByID(w http.ResponseWriter, r *http.Request) {
    // Extrai ID da URL: /tasks/123
    idStr := r.URL.Path[len("/tasks/"):]
    id, err := strconv.Atoi(idStr)
    if err != nil {
        http.Error(w, "ID inválido", http.StatusBadRequest)
        return
    }
    
    switch r.Method {
    case "GET":
        getTask(w, r, id)
    case "PUT":
        updateTask(w, r, id)
    case "DELETE":
        deleteTask(w, r, id)
    default:
        http.Error(w, "Método não permitido", http.StatusMethodNotAllowed)
    }
}

func getTask(w http.ResponseWriter, r *http.Request, id int) {
    mu.Lock()
    task, ok := tasks[id]
    mu.Unlock()
    
    if !ok {
        http.Error(w, "Tarefa não encontrada", http.StatusNotFound)
        return
    }
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(task)
}

func updateTask(w http.ResponseWriter, r *http.Request, id int) {
    mu.Lock()
    defer mu.Unlock()
    
    if _, ok := tasks[id]; !ok {
        http.Error(w, "Tarefa não encontrada", http.StatusNotFound)
        return
    }
    
    var task Task
    if err := json.NewDecoder(r.Body).Decode(&task); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    
    task.ID = id
    tasks[id] = task
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(task)
}

func deleteTask(w http.ResponseWriter, r *http.Request, id int) {
    mu.Lock()
    defer mu.Unlock()
    
    if _, ok := tasks[id]; !ok {
        http.Error(w, "Tarefa não encontrada", http.StatusNotFound)
        return
    }
    
    delete(tasks, id)
    w.WriteHeader(http.StatusNoContent)
}

Testando a API

Execute o servidor:

go run main.go

Com cURL

# Criar tarefa
curl -X POST http://localhost:8080/tasks \
  -H "Content-Type: application/json" \
  -d '{"title": "Aprender Go", "completed": false}'

# Listar tarefas
curl http://localhost:8080/tasks

# Buscar tarefa
curl http://localhost:8080/tasks/1

# Atualizar tarefa
curl -X PUT http://localhost:8080/tasks/1 \
  -H "Content-Type: application/json" \
  -d '{"title": "Aprender Go", "completed": true}'

# Deletar tarefa
curl -X DELETE http://localhost:8080/tasks/1

Para projetos maiores, frameworks como Gin simplificam o código:

go get -u github.com/gin-gonic/gin
package main

import (
    "net/http"
    "github.com/gin-gonic/gin"
)

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

var tasks = []Task{}
var nextID = 1

func main() {
    r := gin.Default()
    
    r.GET("/tasks", listTasks)
    r.POST("/tasks", createTask)
    r.GET("/tasks/:id", getTask)
    r.PUT("/tasks/:id", updateTask)
    r.DELETE("/tasks/:id", deleteTask)
    
    r.Run(":8080")
}

func listTasks(c *gin.Context) {
    c.JSON(http.StatusOK, tasks)
}

func createTask(c *gin.Context) {
    var task Task
    if err := c.ShouldBindJSON(&task); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    task.ID = nextID
    nextID++
    tasks = append(tasks, task)
    c.JSON(http.StatusCreated, task)
}

// ... outros handlers similares

Boas Práticas

1. Estrutura de Projeto

todo-api/
├── main.go
├── handlers/
│   └── tasks.go
├── models/
│   └── task.go
├── middleware/
│   └── logging.go
└── go.mod

2. Middleware de Logging

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    })
}

3. Tratamento de Erros

type APIError struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
}

func respondWithError(w http.ResponseWriter, code int, message string) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(code)
    json.NewEncoder(w).Encode(APIError{Code: code, Message: message})
}

Deploy

Docker

FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o main .

FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/main .
EXPOSE 8080
CMD ["./main"]
docker build -t todo-api .
docker run -p 8080:8080 todo-api

Próximos Passos

  • Adicionar banco de dados (PostgreSQL, MySQL)
  • Implementar autenticação (JWT)
  • Adicionar testes automatizados
  • Documentar com Swagger

Veja Também


Última atualização: Janeiro 2026


Continue aprendendo