---
title: "Go para APIs REST: Guia Completo de Desenvolvimento Web"
url: "https://golang.com.br/tutoriais/go-api-rest/"
markdown_url: "https://golang.com.br/tutoriais/go-api-rest.MD"
description: "Aprenda a criar APIs REST robustas em Go. Tutorial completo cobrindo rotas, JSON, middleware, autenticação, banco de dados e deploy. Exemplos práticos incluídos."
date: "2026-02-10"
author: ""
---

# Go para APIs REST: Guia Completo de Desenvolvimento Web

Aprenda a criar APIs REST robustas em Go. Tutorial completo cobrindo rotas, JSON, middleware, autenticação, banco de dados e deploy. Exemplos práticos incluídos.


Go é uma das melhores linguagens para desenvolvimento de APIs REST. Com sua performance nativa, concorrência eficiente via goroutines e biblioteca padrão robusta, Go permite criar serviços web escaláveis e de alta performance. Neste guia completo, vamos construir uma API REST do zero, cobrindo desde conceitos básicos até padrões avançados usados em produção.

## Por Que Go para APIs REST?

### Vantagens de Go para Backend

```
┌─────────────────────────────────────────────────────────┐
│  PERFORMANCE          │  Goroutines leves (2KB stack)   │
├─────────────────────────────────────────────────────────┤
│  CONFIABILIDADE       │  Tipagem forte, erro explícito  │
├─────────────────────────────────────────────────────────┤
│  PRODUTIVIDADE        │  Compilação rápida, stdlib rica │
├─────────────────────────────────────────────────────────┤
│  DEPLOY               │  Binário único, cross-compile   │
├─────────────────────────────────────────────────────────┤
│  ECOSISTEMA           │  Gin, Echo, Chi, Fiber          │
└─────────────────────────────────────────────────────────┘
```

**Empresas usando Go em produção:**
- Google (Kubernetes, Docker)
- Uber (microservices)
- Netflix (backend services)
- Dropbox (storage systems)
- Mercado Libre (pagamentos)

## Servidor HTTP Básico

### Hello World Web

Vamos começar com o servidor HTTP mais simples em Go:

```go
package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	// Handler para rota /
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Olá, Mundo!")
	})

	// Iniciar servidor na porta 8080
	log.Println("Servidor rodando em http://localhost:8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}
```

**Testando:**
```bash
go run main.go
curl http://localhost:8080
# Output: Olá, Mundo!
```

### Múltiplas Rotas com Standard Library

```go
package main

import (
	"encoding/json"
	"log"
	"net/http"
	"time"
)

// Resposta padrão da API
type Response struct {
	Message string    `json:"message"`
	Time    time.Time `json:"time"`
}

func main() {
	// Configurar rotas
	mux := http.NewServeMux()
	
	mux.HandleFunc("GET /", homeHandler)
	mux.HandleFunc("GET /health", healthHandler)
	mux.HandleFunc("GET /api/status", statusHandler)

	// Configurar servidor com timeouts
	server := &http.Server{
		Addr:         ":8080",
		Handler:      mux,
		ReadTimeout:  15 * time.Second,
		WriteTimeout: 15 * time.Second,
		IdleTimeout:  60 * time.Second,
	}

	log.Println("API em http://localhost:8080")
	log.Fatal(server.ListenAndServe())
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(Response{
		Message: "Bem-vindo à API Go!",
		Time:    time.Now(),
	})
}

func healthHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(map[string]string{
		"status": "healthy",
	})
}

func statusHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]interface{}{
		"service":   "api-go",
		"version":   "1.0.0",
		"timestamp": time.Now().Unix(),
	})
}
```

## Roteamento com Gorilla Mux

### Por Que Usar um Router?

A biblioteca padrão é boa, mas routers como Gorilla Mux oferecem:
- **Path parameters**: `/users/{id}`
- **Method matching**: `GET`, `POST`, etc.
- **Middleware chains**
- **Subrouters**

### Instalação e Uso Básico

```bash
go get -u github.com/gorilla/mux
```

```go
package main

import (
	"encoding/json"
	"log"
	"net/http"
	"strconv"
	"time"

	"github.com/gorilla/mux"
)

// Modelo de Usuário
type User struct {
	ID        uint      `json:"id"`
	Name      string    `json:"name"`
	Email     string    `json:"email"`
	CreatedAt time.Time `json:"created_at"`
}

// "Banco de dados" em memória
var users = []User{
	{ID: 1, Name: "Alice", Email: "alice@example.com", CreatedAt: time.Now()},
	{ID: 2, Name: "Bob", Email: "bob@example.com", CreatedAt: time.Now()},
}
var nextID uint = 3

func main() {
	r := mux.NewRouter()

	// Rotas da API
	api := r.PathPrefix("/api/v1").Subrouter()
	
	// Users
	api.HandleFunc("/users", getUsers).Methods("GET")
	api.HandleFunc("/users", createUser).Methods("POST")
	api.HandleFunc("/users/{id}", getUser).Methods("GET")
	api.HandleFunc("/users/{id}", updateUser).Methods("PUT")
	api.HandleFunc("/users/{id}", deleteUser).Methods("DELETE")

	// Middleware
	r.Use(loggingMiddleware)
	r.Use(contentTypeMiddleware)

	server := &http.Server{
		Addr:         ":8080",
		Handler:      r,
		ReadTimeout:  15 * time.Second,
		WriteTimeout: 15 * time.Second,
	}

	log.Println("API em http://localhost:8080")
	log.Fatal(server.ListenAndServe())
}

// 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))
	})
}

// Middleware de content-type
func contentTypeMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		next.ServeHTTP(w, r)
	})
}

// Handlers
func getUsers(w http.ResponseWriter, r *http.Request) {
	json.NewEncoder(w).Encode(users)
}

func getUser(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id, err := strconv.ParseUint(vars["id"], 10, 32)
	if err != nil {
		http.Error(w, `{"error": "ID inválido"}`, http.StatusBadRequest)
		return
	}

	for _, user := range users {
		if user.ID == uint(id) {
			json.NewEncoder(w).Encode(user)
			return
		}
	}

	http.Error(w, `{"error": "Usuário não encontrado"}`, http.StatusNotFound)
}

func createUser(w http.ResponseWriter, r *http.Request) {
	var user User
	if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
		http.Error(w, `{"error": "JSON inválido"}`, http.StatusBadRequest)
		return
	}

	// Validação simples
	if user.Name == "" || user.Email == "" {
		http.Error(w, `{"error": "Nome e email são obrigatórios"}`, http.StatusBadRequest)
		return
	}

	user.ID = nextID
	nextID++
	user.CreatedAt = time.Now()
	users = append(users, user)

	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(user)
}

func updateUser(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id, _ := strconv.ParseUint(vars["id"], 10, 32)

	var updated User
	if err := json.NewDecoder(r.Body).Decode(&updated); err != nil {
		http.Error(w, `{"error": "JSON inválido"}`, http.StatusBadRequest)
		return
	}

	for i, user := range users {
		if user.ID == uint(id) {
			users[i].Name = updated.Name
			users[i].Email = updated.Email
			json.NewEncoder(w).Encode(users[i])
			return
		}
	}

	http.Error(w, `{"error": "Usuário não encontrado"}`, http.StatusNotFound)
}

func deleteUser(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id, _ := strconv.ParseUint(vars["id"], 10, 32)

	for i, user := range users {
		if user.ID == uint(id) {
			users = append(users[:i], users[i+1:]...)
			w.WriteHeader(http.StatusNoContent)
			return
		}
	}

	http.Error(w, `{"error": "Usuário não encontrado"}`, http.StatusNotFound)
}
```

## Middleware Avançado

### Autenticação JWT

```go
package middleware

import (
	"context"
	"net/http"
	"strings"
	"time"

	"github.com/golang-jwt/jwt/v5"
)

var jwtSecret = []byte("sua-chave-secreta-aqui") // Use variável de ambiente!

// Claims personalizadas
type Claims struct {
	UserID uint   `json:"user_id"`
	Email  string `json:"email"`
	jwt.RegisteredClaims
}

// Gerar token
func GenerateToken(userID uint, email string) (string, error) {
	claims := Claims{
		UserID: userID,
		Email:  email,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(jwtSecret)
}

// Middleware de autenticação
func AuthMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		authHeader := r.Header.Get("Authorization")
		if authHeader == "" {
			http.Error(w, `{"error": "Token não fornecido"}`, http.StatusUnauthorized)
			return
		}

		// Extrair token (Bearer token)
		parts := strings.Split(authHeader, " ")
		if len(parts) != 2 || parts[0] != "Bearer" {
			http.Error(w, `{"error": "Formato de token inválido"}`, http.StatusUnauthorized)
			return
		}

		tokenString := parts[1]

		// Validar token
		claims := &Claims{}
		token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
			return jwtSecret, nil
		})

		if err != nil || !token.Valid {
			http.Error(w, `{"error": "Token inválido"}`, http.StatusUnauthorized)
			return
		}

		// Adicionar claims ao contexto
		ctx := context.WithValue(r.Context(), "claims", claims)
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

// Recuperar usuário do contexto
func GetUserFromContext(r *http.Request) *Claims {
	claims, ok := r.Context().Value("claims").(*Claims)
	if !ok {
		return nil
	}
	return claims
}
```

### Rate Limiting

```go
package middleware

import (
	"net/http"
	"sync"
	"time"
)

// RateLimiter implementa token bucket
type RateLimiter struct {
	requests map[string][]time.Time
	limit    int
	window   time.Duration
	mu       sync.RWMutex
}

func NewRateLimiter(limit int, window time.Duration) *RateLimiter {
	rl := &RateLimiter{
		requests: make(map[string][]time.Time),
		limit:    limit,
		window:   window,
	}
	
	// Limpar entradas antigas periodicamente
	go rl.cleanup()
	
	return rl
}

func (rl *RateLimiter) cleanup() {
	ticker := time.NewTicker(rl.window)
	for range ticker.C {
		rl.mu.Lock()
		now := time.Now()
		for ip, times := range rl.requests {
			var valid []time.Time
			for _, t := range times {
				if now.Sub(t) < rl.window {
					valid = append(valid, t)
				}
			}
			if len(valid) == 0 {
				delete(rl.requests, ip)
			} else {
				rl.requests[ip] = valid
			}
		}
		rl.mu.Unlock()
	}
}

func (rl *RateLimiter) Allow(ip string) bool {
	rl.mu.Lock()
	defer rl.mu.Unlock()

	now := time.Now()
	times := rl.requests[ip]

	// Remover requisições antigas
	var valid []time.Time
	for _, t := range times {
		if now.Sub(t) < rl.window {
			valid = append(valid, t)
		}
	}

	if len(valid) >= rl.limit {
		return false
	}

	rl.requests[ip] = append(valid, now)
	return true
}

func RateLimitMiddleware(limiter *RateLimiter) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			ip := r.RemoteAddr
			if !limiter.Allow(ip) {
				w.WriteHeader(http.StatusTooManyRequests)
				json.NewEncoder(w).Encode(map[string]string{
					"error": "Rate limit exceeded",
				})
				return
			}
			next.ServeHTTP(w, r)
		})
	}
}
```

## Integração com Banco de Dados

### Usando SQL nativo

```go
package database

import (
	"database/sql"
	"fmt"
	"log"
	"time"

	_ "github.com/mattn/go-sqlite3" // ou "github.com/lib/pq" para PostgreSQL
)

// DB encapsula a conexão
type DB struct {
	*sql.DB
}

func New(connectionString string) (*DB, error) {
	db, err := sql.Open("sqlite3", connectionString)
	if err != nil {
		return nil, err
	}

	// Configurar pool de conexões
	db.SetMaxOpenConns(25)
	db.SetMaxIdleConns(25)
	db.SetConnMaxLifetime(5 * time.Minute)

	// Verificar conexão
	if err := db.Ping(); err != nil {
		return nil, err
	}

	return &DB{db}, nil
}

// Criar tabelas
func (db *DB) Migrate() error {
	query := `
	CREATE TABLE IF NOT EXISTS users (
		id INTEGER PRIMARY KEY AUTOINCREMENT,
		name TEXT NOT NULL,
		email TEXT UNIQUE NOT NULL,
		password_hash TEXT NOT NULL,
		created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
		updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
	);

	CREATE TABLE IF NOT EXISTS posts (
		id INTEGER PRIMARY KEY AUTOINCREMENT,
		user_id INTEGER NOT NULL,
		title TEXT NOT NULL,
		content TEXT,
		created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
		FOREIGN KEY (user_id) REFERENCES users(id)
	);

	CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
	`

	_, err := db.Exec(query)
	return err
}

// Repository Pattern
type UserRepository struct {
	db *DB
}

func NewUserRepository(db *DB) *UserRepository {
	return &UserRepository{db: db}
}

func (r *UserRepository) Create(user *User) error {
	query := `
		INSERT INTO users (name, email, password_hash)
		VALUES (?, ?, ?)
		RETURNING id, created_at
	`
	return r.db.QueryRow(query, user.Name, user.Email, user.PasswordHash).
		Scan(&user.ID, &user.CreatedAt)
}

func (r *UserRepository) GetByID(id int64) (*User, error) {
	user := &User{}
	query := `SELECT id, name, email, created_at, updated_at FROM users WHERE id = ?`
	
	err := r.db.QueryRow(query, id).Scan(
		&user.ID, &user.Name, &user.Email,
		&user.CreatedAt, &user.UpdatedAt,
	)
	
	if err == sql.ErrNoRows {
		return nil, fmt.Errorf("usuário não encontrado")
	}
	
	return user, err
}

func (r *UserRepository) GetAll() ([]User, error) {
	query := `SELECT id, name, email, created_at, updated_at FROM users ORDER BY created_at DESC`
	
	rows, err := r.db.Query(query)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var users []User
	for rows.Next() {
		var u User
		err := rows.Scan(
			&u.ID, &u.Name, &u.Email,
			&u.CreatedAt, &u.UpdatedAt,
		)
		if err != nil {
			return nil, err
		}
		users = append(users, u)
	}
	
	return users, rows.Err()
}

func (r *UserRepository) Update(user *User) error {
	query := `
		UPDATE users 
		SET name = ?, email = ?, updated_at = CURRENT_TIMESTAMP
		WHERE id = ?
	`
	result, err := r.db.Exec(query, user.Name, user.Email, user.ID)
	if err != nil {
		return err
	}

	rows, _ := result.RowsAffected()
	if rows == 0 {
		return fmt.Errorf("usuário não encontrado")
	}
	
	return nil
}

func (r *UserRepository) Delete(id int64) error {
	result, err := r.db.Exec("DELETE FROM users WHERE id = ?", id)
	if err != nil {
		return err
	}

	rows, _ := result.RowsAffected()
	if rows == 0 {
		return fmt.Errorf("usuário não encontrado")
	}
	
	return nil
}
```

### Usando GORM (ORM)

```go
package main

import (
	"log"
	"time"

	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

type User struct {
	ID        uint           `gorm:"primarykey" json:"id"`
	CreatedAt time.Time      `json:"created_at"`
	UpdatedAt time.Time      `json:"updated_at"`
	DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
	Name      string         `gorm:"size:100;not null" json:"name"`
	Email     string         `gorm:"size:100;uniqueIndex;not null" json:"email"`
	Password  string         `gorm:"-" json:"password,omitempty"` // Ignorar no DB
	Posts     []Post         `json:"posts,omitempty"`
}

type Post struct {
	ID      uint   `gorm:"primarykey" json:"id"`
	Title   string `gorm:"size:200;not null" json:"title"`
	Content string `json:"content"`
	UserID  uint   `json:"user_id"`
}

func main() {
	db, err := gorm.Open(sqlite.Open("app.db"), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Info),
	})
	if err != nil {
		log.Fatal(err)
	}

	// Auto migrate
	db.AutoMigrate(&User{}, &Post{})

	// CRUD com GORM
	// Create
	user := User{Name: "Alice", Email: "alice@example.com"}
	result := db.Create(&user)
	log.Printf("Usuário criado: ID=%d, erro=%v", user.ID, result.Error)

	// Read
	var found User
	db.First(&found, user.ID)
	
	// Update
	db.Model(&found).Update("name", "Alice Silva")
	
	// Delete
	db.Delete(&found)
}
```

## Testando APIs

### Testes Unitários

```go
package main

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gorilla/mux"
)

func TestGetUsers(t *testing.T) {
	req, err := http.NewRequest("GET", "/api/v1/users", nil)
	if err != nil {
		t.Fatal(err)
	}

	rr := httptest.NewRecorder()
	handler := http.HandlerFunc(getUsers)

	handler.ServeHTTP(rr, req)

	// Verificar status
	if status := rr.Code; status != http.StatusOK {
		t.Errorf("handler retornou status errado: got %v want %v", status, http.StatusOK)
	}

	// Verificar content-type
	if ctype := rr.Header().Get("Content-Type"); ctype != "application/json" {
		t.Errorf("content-type errado: got %v want application/json", ctype)
	}

	// Verificar body
	var users []User
	if err := json.Unmarshal(rr.Body.Bytes(), &users); err != nil {
		t.Fatal(err)
	}

	if len(users) != 2 {
		t.Errorf("número de usuários errado: got %d want 2", len(users))
	}
}

func TestCreateUser(t *testing.T) {
	// Setup
	users = []User{} // Reset
	nextID = 1

	// Criar request
	newUser := User{Name: "Carol", Email: "carol@example.com"}
	body, _ := json.Marshal(newUser)
	
	req, err := http.NewRequest("POST", "/api/v1/users", bytes.NewBuffer(body))
	if err != nil {
		t.Fatal(err)
	}
	req.Header.Set("Content-Type", "application/json")

	rr := httptest.NewRecorder()
	handler := http.HandlerFunc(createUser)

	handler.ServeHTTP(rr, req)

	// Verificar
	if status := rr.Code; status != http.StatusCreated {
		t.Errorf("status errado: got %v want %v", status, http.StatusCreated)
	}

	var created User
	json.Unmarshal(rr.Body.Bytes(), &created)
	
	if created.Name != "Carol" {
		t.Errorf("nome errado: got %v want Carol", created.Name)
	}
}

func TestGetUserNotFound(t *testing.T) {
	req, err := http.NewRequest("GET", "/api/v1/users/999", nil)
	if err != nil {
		t.Fatal(err)
	}

	// Gorilla Mux precisa das vars
	vars := map[string]string{"id": "999"}
	req = mux.SetURLVars(req, vars)

	rr := httptest.NewRecorder()
	handler := http.HandlerFunc(getUser)

	handler.ServeHTTP(rr, req)

	if status := rr.Code; status != http.StatusNotFound {
		t.Errorf("status errado: got %v want %v", status, http.StatusNotFound)
	}
}

// Teste de integração
func TestAPIIntegration(t *testing.T) {
	// Setup router
	r := mux.NewRouter()
	api := r.PathPrefix("/api/v1").Subrouter()
	api.HandleFunc("/users", getUsers).Methods("GET")
	
	// Criar servidor de teste
	server := httptest.NewServer(r)
	defer server.Close()

	// Fazer request real
	resp, err := http.Get(server.URL + "/api/v1/users")
	if err != nil {
		t.Fatal(err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		t.Errorf("status errado: %d", resp.StatusCode)
	}
}
```

## Deploy

### Dockerfile

```dockerfile
# Build stage
FROM golang:1.21-alpine AS builder

WORKDIR /app

# Cache dependencies
COPY go.mod go.sum ./
RUN go mod download

# Copy source
COPY . .

# Build
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

# Final stage
FROM alpine:latest

RUN apk --no-cache add ca-certificates

WORKDIR /root/

# Copy binary
COPY --from=builder /app/main .

# Copy config if needed
# COPY --from=builder /app/config.yaml .

EXPOSE 8080

CMD ["./main"]
```

```bash
# Build e run
docker build -t go-api .
docker run -p 8080:8080 go-api
```

### Docker Compose

```yaml
version: '3.8'

services:
  api:
    build: .
    ports:
      - "8080:8080"
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/mydb?sslmode=disable
      - JWT_SECRET=supersecret
    depends_on:
      - db
    restart: unless-stopped

  db:
    image: postgres:15-alpine
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass
      - POSTGRES_DB=mydb
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

volumes:
  postgres_data:
```

### Deploy na Railway/Render/Fly.io

```go
// main.go - Obter porta da variável de ambiente
package main

import (
	"log"
	"net/http"
	"os"
)

func main() {
	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}

	router := setupRouter() // sua função de configuração

	log.Printf("Servidor iniciando na porta %s", port)
	log.Fatal(http.ListenAndServe(":"+port, router))
}
```

## Documentação da API

### Swagger/OpenAPI

```go
// Instalação
// go get -u github.com/swaggo/http-swagger
// go get -u github.com/swaggo/swag/cmd/swag

// Documentação nos handlers
// @title API de Exemplo
// @version 1.0
// @description API REST em Go
// @host localhost:8080
// @BasePath /api/v1

// @Summary Listar usuários
// @Description Retorna todos os usuários cadastrados
// @Tags users
// @Accept json
// @Produce json
// @Success 200 {array} User
// @Router /users [get]
func getUsers(w http.ResponseWriter, r *http.Request) {
	// implementação
}
```

## Checklist de Produção

Antes de deployar sua API:

- [ ] **Segurança**: HTTPS, CORS configurado, headers de segurança
- [ ] **Autenticação**: JWT ou OAuth2 implementado
- [ ] **Validação**: Input validation em todos os endpoints
- [ ] **Logging**: Structured logging (JSON)
- [ ] **Métricas**: Prometheus/StatsD para monitoramento
- [ ] **Health checks**: Endpoints /health e /ready
- [ ] **Graceful shutdown**: Tratamento de sinais SIGTERM
- [ ] **Rate limiting**: Proteção contra abuse
- [ ] **Database**: Connection pooling, prepared statements
- [ ] **Timeouts**: Configurados em todas as camadas
- [ ] **Testes**: Cobertura > 80%
- [ ] **Documentação**: Swagger/OpenAPI atualizado

## Próximos Passos

Continue seu aprendizado:

1. **[Golang para Iniciantes](/tutoriais/golang-para-iniciantes/)** — Fundamentos da linguagem
2. **[Go Concurrency Patterns](/tutoriais/go-concurrency-patterns/)** — Goroutines e channels avançados
3. **[Go Testing](/tutoriais/go-testing/)** — Testes avançados e mocking
4. **[Go e gRPC](/tutoriais/go-grpc/)** — APIs com Protocol Buffers

---

*Go é excelente para APIs REST. Compartilhe o que você construiu!*
