---
title: "Criando sua Primeira API REST em Go: Tutorial Completo Passo a Passo"
url: "https://golang.com.br/tutoriais/golang-para-iniciantes/golang-api-rest-primeira-aplicacao/"
markdown_url: "https://golang.com.br/tutoriais/golang-para-iniciantes/golang-api-rest-primeira-aplicacao.MD"
description: "Construa uma API REST completa em Go. Tutorial prático com HTTP handlers, JSON, routing, middleware e banco de dados. Do zero à produção."
date: "2026-02-09"
author: ""
---

# Criando sua Primeira API REST em Go: Tutorial Completo Passo a Passo

Construa uma API REST completa em Go. Tutorial prático com HTTP handlers, JSON, routing, middleware e banco de dados. Do zero à produção.


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

```bash
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:

```bash
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:

```go
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:

```go
import "fmt"
```

## Armazenamento em Memória (storage/memory.go)

```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)

```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)

```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

```bash
go run main.go
```

### 2. Testar com curl

```bash
# 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:

```go
// 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

```go
// 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
# 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

```go
// 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:

1. **Artigo 1**: Instalação, configuração e primeiro programa
2. **Artigo 2**: Variáveis, tipos, funções, structs e métodos
3. **Artigo 3**: Controle de fluxo (if, switch, loops)
4. **Artigo 4**: Concorrência com goroutines e channels
5. **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](https://go.dev/doc/effective_go) — Guia de estilo oficial
- 🎓 [Go by Example](https://gobyexample.com/) — Mais exemplos práticos
- 🔧 [Gin Framework](https://gin-gonic.com/) — Framework web popular
- 🗄️ [GORM](https://gorm.io/) — ORM para Go
- ☁️ [AWS SDK para Go](https://aws.github.io/aws-sdk-go-v2/) — Cloud computing

### Projetos Sugeridos

1. **API de Blog** — CRUD completo com autenticação JWT
2. **WebSocket Chat** — Chat em tempo real
3. **CLI Tool** — Ferramenta de linha de comando
4. **Microserviço** — Com gRPC ou REST
5. **Pipeline de Dados** — Processamento concorrente

### Comunidade

- 💬 [Golang Brasil no Discord](/comunidade)
- 🐦 [Twitter: @golangbrasil](https://twitter.com/golangbrasil)
- 📺 [YouTube: Golang Brasil](https://youtube.com/golangbrasil)

---

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*
