Neste quinto e último artigo da série “Golang para Iniciantes”, vamos aplicar tudo o que aprendemos para construir uma API REST completa do zero. Você criará um servidor web funcional com rotas, handlers, JSON, persistência em memória e testes.
Se você está chegando agora, recomendamos revisar os artigos anteriores:
📖 ← Série Completa: Golang para Iniciantes
O Que Vamos Construir
Vamos criar uma API de Gerenciamento de Tarefas (Todo API) com as seguintes funcionalidades:
- ✅ Criar tarefa (POST /tarefas)
- ✅ Listar tarefas (GET /tarefas)
- ✅ Buscar tarefa por ID (GET /tarefas/{id})
- ✅ Atualizar tarefa (PUT /tarefas/{id})
- ✅ Deletar tarefa (DELETE /tarefas/{id})
- ✅ Marcar como concluída (PATCH /tarefas/{id}/concluir)
Estrutura do Projeto
todo-api/
├── go.mod
├── main.go
├── handlers/
│ └── tarefas.go
├── models/
│ └── tarefa.go
├── storage/
│ └── memory.go
└── go.sum
Configuração Inicial
1. Criar o Módulo
mkdir todo-api
cd todo-api
go mod init todo-api
2. Instalar Dependências
Vamos usar o Chi Router — leve, idiomático e rápido:
go get -u github.com/go-chi/chi/v5
go get -u github.com/go-chi/chi/v5/middleware
Modelos (models/tarefa.go)
Definimos a estrutura de dados:
package models
import (
"encoding/json"
"time"
)
// Tarefa representa uma tarefa no sistema
type Tarefa struct {
ID int `json:"id"`
Titulo string `json:"titulo"`
Descricao string `json:"descricao"`
Concluida bool `json:"concluida"`
CriadaEm time.Time `json:"criada_em"`
AtualizadaEm time.Time `json:"atualizada_em"`
}
// CreateTarefaRequest representa o payload para criar tarefa
type CreateTarefaRequest struct {
Titulo string `json:"titulo"`
Descricao string `json:"descricao"`
}
// Validate valida os dados da requisição
func (r CreateTarefaRequest) Validate() error {
if r.Titulo == "" {
return fmt.Errorf("título é obrigatório")
}
if len(r.Titulo) > 100 {
return fmt.Errorf("título deve ter no máximo 100 caracteres")
}
return nil
}
// UpdateTarefaRequest representa o payload para atualizar tarefa
type UpdateTarefaRequest struct {
Titulo string `json:"titulo,omitempty"`
Descricao string `json:"descricao,omitempty"`
Concluida *bool `json:"concluida,omitempty"`
}
Adicione o import:
import "fmt"
Armazenamento em Memória (storage/memory.go)
package storage
import (
"errors"
"sync"
"time"
"todo-api/models"
)
var (
ErrTarefaNotFound = errors.New("tarefa não encontrada")
)
// MemoryStorage implementa armazenamento em memória
type MemoryStorage struct {
mu sync.RWMutex
tarefas map[int]models.Tarefa
nextID int
}
// NewMemoryStorage cria uma nova instância
func NewMemoryStorage() *MemoryStorage {
return &MemoryStorage{
tarefas: make(map[int]models.Tarefa),
nextID: 1,
}
}
// Create adiciona uma nova tarefa
func (s *MemoryStorage) Create(tarefa models.Tarefa) models.Tarefa {
s.mu.Lock()
defer s.mu.Unlock()
tarefa.ID = s.nextID
tarefa.CriadaEm = time.Now()
tarefa.AtualizadaEm = time.Now()
s.tarefas[tarefa.ID] = tarefa
s.nextID++
return tarefa
}
// GetByID busca uma tarefa pelo ID
func (s *MemoryStorage) GetByID(id int) (models.Tarefa, error) {
s.mu.RLock()
defer s.mu.RUnlock()
tarefa, ok := s.tarefas[id]
if !ok {
return models.Tarefa{}, ErrTarefaNotFound
}
return tarefa, nil
}
// GetAll retorna todas as tarefas
func (s *MemoryStorage) GetAll() []models.Tarefa {
s.mu.RLock()
defer s.mu.RUnlock()
tarefas := make([]models.Tarefa, 0, len(s.tarefas))
for _, t := range s.tarefas {
tarefas = append(tarefas, t)
}
return tarefas
}
// Update atualiza uma tarefa existente
func (s *MemoryStorage) Update(id int, updates models.UpdateTarefaRequest) (models.Tarefa, error) {
s.mu.Lock()
defer s.mu.Unlock()
tarefa, ok := s.tarefas[id]
if !ok {
return models.Tarefa{}, ErrTarefaNotFound
}
if updates.Titulo != "" {
tarefa.Titulo = updates.Titulo
}
if updates.Descricao != "" {
tarefa.Descricao = updates.Descricao
}
if updates.Concluida != nil {
tarefa.Concluida = *updates.Concluida
}
tarefa.AtualizadaEm = time.Now()
s.tarefas[id] = tarefa
return tarefa, nil
}
// Delete remove uma tarefa
func (s *MemoryStorage) Delete(id int) error {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.tarefas[id]; !ok {
return ErrTarefaNotFound
}
delete(s.tarefas, id)
return nil
}
Handlers (handlers/tarefas.go)
package handlers
import (
"encoding/json"
"net/http"
"strconv"
"todo-api/models"
"todo-api/storage"
"github.com/go-chi/chi/v5"
)
// TarefaHandler gerencia as requisições de tarefas
type TarefaHandler struct {
storage *storage.MemoryStorage
}
// NewTarefaHandler cria um novo handler
func NewTarefaHandler(s *storage.MemoryStorage) *TarefaHandler {
return &TarefaHandler{storage: s}
}
// Create cria uma nova tarefa
func (h *TarefaHandler) Create(w http.ResponseWriter, r *http.Request) {
var req models.CreateTarefaRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
respondWithError(w, http.StatusBadRequest, "JSON inválido")
return
}
if err := req.Validate(); err != nil {
respondWithError(w, http.StatusBadRequest, err.Error())
return
}
tarefa := models.Tarefa{
Titulo: req.Titulo,
Descricao: req.Descricao,
}
created := h.storage.Create(tarefa)
respondWithJSON(w, http.StatusCreated, created)
}
// GetAll lista todas as tarefas
func (h *TarefaHandler) GetAll(w http.ResponseWriter, r *http.Request) {
tarefas := h.storage.GetAll()
respondWithJSON(w, http.StatusOK, tarefas)
}
// GetByID busca uma tarefa específica
func (h *TarefaHandler) GetByID(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(chi.URLParam(r, "id"))
if err != nil {
respondWithError(w, http.StatusBadRequest, "ID inválido")
return
}
tarefa, err := h.storage.GetByID(id)
if err != nil {
respondWithError(w, http.StatusNotFound, "Tarefa não encontrada")
return
}
respondWithJSON(w, http.StatusOK, tarefa)
}
// Update atualiza uma tarefa
func (h *TarefaHandler) Update(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(chi.URLParam(r, "id"))
if err != nil {
respondWithError(w, http.StatusBadRequest, "ID inválido")
return
}
var req models.UpdateTarefaRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
respondWithError(w, http.StatusBadRequest, "JSON inválido")
return
}
tarefa, err := h.storage.Update(id, req)
if err != nil {
respondWithError(w, http.StatusNotFound, "Tarefa não encontrada")
return
}
respondWithJSON(w, http.StatusOK, tarefa)
}
// Delete remove uma tarefa
func (h *TarefaHandler) Delete(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(chi.URLParam(r, "id"))
if err != nil {
respondWithError(w, http.StatusBadRequest, "ID inválido")
return
}
if err := h.storage.Delete(id); err != nil {
respondWithError(w, http.StatusNotFound, "Tarefa não encontrada")
return
}
respondWithJSON(w, http.StatusOK, map[string]string{
"message": "Tarefa deletada com sucesso",
})
}
// Concluir marca uma tarefa como concluída
func (h *TarefaHandler) Concluir(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(chi.URLParam(r, "id"))
if err != nil {
respondWithError(w, http.StatusBadRequest, "ID inválido")
return
}
concluida := true
tarefa, err := h.storage.Update(id, models.UpdateTarefaRequest{
Concluida: &concluida,
})
if err != nil {
respondWithError(w, http.StatusNotFound, "Tarefa não encontrada")
return
}
respondWithJSON(w, http.StatusOK, tarefa)
}
// Helpers
func respondWithJSON(w http.ResponseWriter, status int, payload interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(payload)
}
func respondWithError(w http.ResponseWriter, status int, message string) {
respondWithJSON(w, status, map[string]string{"error": message})
}
Ponto de Entrada (main.go)
package main
import (
"fmt"
"net/http"
"todo-api/handlers"
"todo-api/storage"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func main() {
// Inicializa armazenamento
db := storage.NewMemoryStorage()
// Cria handler
tarefaHandler := handlers.NewTarefaHandler(db)
// Configura router
r := chi.NewRouter()
// Middlewares
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
// Rotas
r.Route("/tarefas", func(r chi.Router) {
r.Get("/", tarefaHandler.GetAll)
r.Post("/", tarefaHandler.Create)
r.Get("/{id}", tarefaHandler.GetByID)
r.Put("/{id}", tarefaHandler.Update)
r.Delete("/{id}", tarefaHandler.Delete)
r.Patch("/{id}/concluir", tarefaHandler.Concluir)
})
// Health check
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{"status":"ok"}`))
})
// Inicia servidor
port := ":8080"
fmt.Printf("Servidor rodando em http://localhost%s\n", port)
if err := http.ListenAndServe(port, r); err != nil {
fmt.Printf("Erro ao iniciar servidor: %v\n", err)
}
}
Testando a API
1. Iniciar o Servidor
go run main.go
2. Testar com curl
# Criar tarefa
curl -X POST http://localhost:8080/tarefas \
-H "Content-Type: application/json" \
-d '{
"titulo": "Aprender Go",
"descricao": "Completar o tutorial de Go"
}'
# Listar tarefas
curl http://localhost:8080/tarefas
# Buscar tarefa específica
curl http://localhost:8080/tarefas/1
# Atualizar tarefa
curl -X PUT http://localhost:8080/tarefas/1 \
-H "Content-Type: application/json" \
-d '{
"titulo": "Aprender Go Avançado",
"descricao": "Dominar concorrência em Go"
}'
# Marcar como concluída
curl -X PATCH http://localhost:8080/tarefas/1/concluir
# Deletar tarefa
curl -X DELETE http://localhost:8080/tarefas/1
# Health check
curl http://localhost:8080/health
Melhorias e Próximos Passos
1. Adicionar Banco de Dados
Substitua o MemoryStorage por PostgreSQL ou MongoDB:
// storage/postgres.go
package storage
import (
"database/sql"
_ "github.com/lib/pq"
)
type PostgresStorage struct {
db *sql.DB
}
func NewPostgresStorage(connStr string) (*PostgresStorage, error) {
db, err := sql.Open("postgres", connStr)
if err != nil {
return nil, err
}
return &PostgresStorage{db: db}, nil
}
2. Adicionar Testes
// handlers/tarefas_test.go
package handlers
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"todo-api/models"
"todo-api/storage"
)
func TestCreateTarefa(t *testing.T) {
db := storage.NewMemoryStorage()
handler := NewTarefaHandler(db)
reqBody := `{"titulo":"Teste","descricao":"Descrição"}`
req := httptest.NewRequest(http.MethodPost, "/tarefas", bytes.NewBufferString(reqBody))
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
handler.Create(rr, req)
if rr.Code != http.StatusCreated {
t.Errorf("Esperado %d, obtido %d", http.StatusCreated, rr.Code)
}
var response models.Tarefa
json.Unmarshal(rr.Body.Bytes(), &response)
if response.Titulo != "Teste" {
t.Errorf("Título incorreto: %s", response.Titulo)
}
}
3. Dockerizar
# Dockerfile
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o todo-api .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/todo-api .
EXPOSE 8080
CMD ["./todo-api"]
4. Adicionar Autenticação
// middleware/auth.go
package middleware
import (
"net/http"
"strings"
)
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" || !strings.HasPrefix(token, "Bearer ") {
http.Error(w, "Não autorizado", http.StatusUnauthorized)
return
}
// Validar token...
next.ServeHTTP(w, r)
})
}
Resumo do Que Você Aprendeu
Nesta série completa, você aprendeu:
- Artigo 1: Instalação, configuração e primeiro programa
- Artigo 2: Variáveis, tipos, funções, structs e métodos
- Artigo 3: Controle de fluxo (if, switch, loops)
- Artigo 4: Concorrência com goroutines e channels
- Artigo 5: API REST completa
Você agora tem uma base sólida em Go e pode construir aplicações web reais!
Próximos Passos
Continue Seu Aprendizado
- 📚 Effective Go — Guia de estilo oficial
- 🎓 Go by Example — Mais exemplos práticos
- 🔧 Gin Framework — Framework web popular
- 🗄️ GORM — ORM para Go
- ☁️ AWS SDK para Go — Cloud computing
Projetos Sugeridos
- API de Blog — CRUD completo com autenticação JWT
- WebSocket Chat — Chat em tempo real
- CLI Tool — Ferramenta de linha de comando
- Microserviço — Com gRPC ou REST
- Pipeline de Dados — Processamento concorrente
Comunidade
Parabéns por completar a série “Golang para Iniciantes”! 🎉
Você está pronto para construir aplicações Go profissionais. Boa codificação!
Veja a série completa: Golang para Iniciantes
Última atualização: 09 de fevereiro de 2026
Versão do Go: 1.23