Janeiro 2026 · ~13 min

Go API e Golang API: Como Criar uma API REST em 2026

Como criar uma Go API (Golang API / API em Go) com net/http: REST, CRUD, JSON, testes e checklist de produção sem framework.

Go API e Golang API: Como Criar uma API REST

Resposta rápida: para criar uma Go API (também buscada como Golang API, API em Go, Golang REST API ou API Go), use http.NewServeMux, registre rotas como GET /tasks e POST /tasks, leia e escreva JSON com encoding/json e execute um http.Server com timeouts. Desde o Go 1.22, a standard library (net/http) também reconhece parâmetros de caminho como GET /tasks/{id}. Você consegue construir uma API REST de produção — CRUD, validação, middleware, testes e graceful shutdown — sem framework. O primeiro exemplo abaixo compila e roda em um único arquivo. E se o seu objetivo for o outro lado — consumir (chamar) uma API externa em Go — a seção 10 mostra o padrão correto com http.Client.

Go é uma das melhores linguagens para APIs REST: binário único e compilado, baixo consumo de memória, concorrência barata via goroutines e uma biblioteca padrão cobrindo HTTP, JSON e criptografia. Se você quer entender por que equipes escolhem Go para back-end, leia Go para back-end em 2026; aqui o foco é colocar uma API no ar.

Golang API, API em Go ou Go API: é a mesma coisa?

Sim. Golang API, API em Go, Go API, API Go e API Golang são nomes de busca diferentes para uma API cujo servidor foi escrito na linguagem Go. O projeto oficial chama a linguagem de Go; “Golang” continua popular porque ajuda a diferenciar a linguagem da palavra inglesa go. O código e as ferramentas são os mesmos em todos os casos.

Para uma API HTTP nova, o ponto de partida recomendado é a biblioteca padrão:

  • net/http recebe requisições, define rotas e envia respostas;
  • encoding/json decodifica o corpo e serializa a resposta;
  • context propaga cancelamento e deadlines;
  • log/slog registra logs estruturados;
  • testing e net/http/httptest testam handlers sem abrir uma porta real.

Esse conjunto já cobre uma Go REST API pequena ou média. Frameworks como Gin, Echo e Chi entram quando o time quer binding, validação, grupos de rotas ou middlewares prontos. Independentemente da escolha, os requisitos de produção continuam iguais: timeouts, validação, autenticação, limites de corpo, CORS configurado com precisão, observabilidade e encerramento gracioso.

Este é o caminho “sem framework”. Para a versão com o framework Gin, siga a série completa API REST com Golang e Gin — a lógica de domínio é a mesma, o que muda é a camada de roteamento.

O que vamos construir

Uma API de tarefas (TODO) com CRUD completo, persistência em memória, validação de entrada, middleware de log, timeouts e desligamento elegante. Ao final você saberá estruturar handlers, ler/escrever JSON com segurança e preparar a API para produção.

API em Go em 5 minutos: exemplo completo

Se você pesquisou por “golang api”, “api go” ou “api golang” e quer primeiro ver algo funcionando, copie este arquivo. Ele expõe GET /tasks e POST /tasks, protege os dados concorrentes com mutex, limita o corpo da requisição e configura timeouts no servidor.

package main

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

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

var store = struct {
	sync.RWMutex
	tasks  []Task
	nextID int
}{nextID: 1}

func main() {
	mux := http.NewServeMux()

	mux.HandleFunc("GET /tasks", func(w http.ResponseWriter, r *http.Request) {
		store.RLock()
		defer store.RUnlock()
		writeJSON(w, http.StatusOK, store.tasks)
	})

	mux.HandleFunc("POST /tasks", func(w http.ResponseWriter, r *http.Request) {
		r.Body = http.MaxBytesReader(w, r.Body, 1<<20)

		var input struct {
			Title string `json:"title"`
		}
		dec := json.NewDecoder(r.Body)
		dec.DisallowUnknownFields()
		if err := dec.Decode(&input); err != nil || input.Title == "" {
			writeJSON(w, http.StatusBadRequest, map[string]string{
				"error": "envie um JSON com title preenchido",
			})
			return
		}

		store.Lock()
		task := Task{ID: store.nextID, Title: input.Title}
		store.nextID++
		store.tasks = append(store.tasks, task)
		store.Unlock()

		writeJSON(w, http.StatusCreated, task)
	})

	srv := &http.Server{
		Addr:              ":8080",
		Handler:           mux,
		ReadHeaderTimeout: 5 * time.Second,
		ReadTimeout:       10 * time.Second,
		WriteTimeout:      15 * time.Second,
		IdleTimeout:       60 * time.Second,
	}

	log.Println("API disponível em http://localhost:8080")
	log.Fatal(srv.ListenAndServe())
}

func writeJSON(w http.ResponseWriter, status int, value any) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(value)
}

Rode e teste em outro terminal:

go mod init exemplo-api
go run .

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

Esse exemplo é propositalmente pequeno. Nas próximas seções, vamos separar armazenamento e handlers, adicionar rotas por ID, validação, testes, banco de dados, observabilidade e deploy.

net/http ou framework: qual escolher para sua API?

OpçãoEscolha quandoPrincipal custo
net/httpAPI pequena ou média, poucas dependências e controle explícitoMais código manual para binding e validação
ChiVocê quer roteador leve e compatibilidade total com http.HandlerBinding e validação continuam manuais
GinO time quer produtividade, binding por tags e muitos exemplosCódigo fica acoplado ao gin.Context
EchoVocê prefere handlers que retornam erro e tratamento centralizadoCódigo fica acoplado ao echo.Context
FiberCompatibilidade com estilo Express é prioridade e o trade-off foi medidoNão usa net/http; middlewares exigem alternativas

Para comparar APIs e exemplos lado a lado antes de escolher, veja Gin vs Echo vs Fiber vs Chi. Neste tutorial, seguimos com net/http porque ele ensina a base comum do ecossistema.

1. Setup do projeto

Crie o módulo e o primeiro arquivo:

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

Você precisa do Go 1.22 ou superior (a versão estável acompanhada pelo site é Go 1.27.1). Verifique com go version. Se ainda não domina módulos, leia Go Modules na prática.

2. Modelo e “banco de dados” em memória

Comece definindo o modelo de domínio e um armazenamento simples, protegido por um sync.Mutex (em produção você troca isso por PostgreSQL — veja o passo 7).

// main.go
package main

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

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

type TaskStore struct {
	mu     sync.Mutex
	tasks  map[int]Task
	nextID int
}

func NewTaskStore() *TaskStore {
	return &TaskStore{tasks: make(map[int]Task), nextID: 1}
}

Por que o Mutex? Várias requisições concorrentes podem escrever no mapa ao mesmo tempo, e mapas em Go não são seguros para escrita concorrente. O Mutex serializa o acesso — um conceito que vale revisitar em concorrência em Go.

3. Rotas com Go 1.22+ (net/http)

A novidade do Go 1.22 é o ServeMux com padrões de método e variáveis de caminho. Em vez de fazer switch r.Method e cortar a URL à mão, você declara a rota já com método e {id}:

func main() {
	store := NewTaskStore()
	h := &TaskHandler{store: store}

	mux := http.NewServeMux()
	mux.HandleFunc("GET /tasks", h.list)
	mux.HandleFunc("POST /tasks", h.create)
	mux.HandleFunc("GET /tasks/{id}", h.get)
	mux.HandleFunc("PUT /tasks/{id}", h.update)
	mux.HandleFunc("DELETE /tasks/{id}", h.delete)

	log.Println("servidor em http://localhost:8080")
	log.Fatal(http.ListenAndServe(":8080", mux))
}

Para ler o id dentro de um handler, use r.PathValue("id"). Se vier vazio ou inválido, retorne 400. Esse padrão elimina dezenas de linhas de parsing manual que tutors mais antigos ainda mostram.

4. JSON: entrada com validação, saída consistente

O erro mais comum em APIs Go é confiar cegamente no json.Decode. Em produção você quer: limitar o tamanho do corpo, rejeitar campos desconhecidos e validar o domínio. Veja o create:

func (h *TaskHandler) create(w http.ResponseWriter, r *http.Request) {
	// Limite de 1 MB no corpo da requisição
	r.Body = http.MaxBytesReader(w, r.Body, 1<<20)

	var in TaskInput
	dec := json.NewDecoder(r.Body)
	dec.DisallowUnknownFields() // rejeita campos não esperados
	if err := dec.Decode(&in); err != nil {
		writeError(w, http.StatusBadRequest, "JSON inválido: "+err.Error())
		return
	}
	if err := in.Validate(); err != nil {
		writeError(w, http.StatusBadRequest, err.Error())
		return
	}

	task := h.store.Add(in.Title)
	writeJSON(w, http.StatusCreated, task)
}

O TaskInput separa o DTO de entrada do modelo de domínio — uma prática que evita que campos como id sejam sobrescritos pelo cliente. Para um aprofundamento em tags, precisão numérica, streaming e encoding/json/v2, leia o guia JSON em Go.

As respostas sempre passam por um helper que centraliza cabeçalho e serialização:

func writeJSON(w http.ResponseWriter, status int, v any) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(v)
}

func writeError(w http.ResponseWriter, status int, msg string) {
	writeJSON(w, status, map[string]string{"error": msg})
}

Erros em formato {"error": "..."} são um contrato simples e previsível; se sua API crescer, padronize com problemas detalhados (RFC 7807) e um contrato OpenAPI.

5. CRUD completo (list, get, update, delete)

func (h *TaskHandler) list(w http.ResponseWriter, r *http.Request) {
	writeJSON(w, http.StatusOK, h.store.All())
}

func (h *TaskHandler) get(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(r)
	if !ok {
		writeError(w, http.StatusBadRequest, "ID inválido")
		return
	}
	task, found := h.store.Get(id)
	if !found {
		writeError(w, http.StatusNotFound, "tarefa não encontrada")
		return
	}
	writeJSON(w, http.StatusOK, task)
}

func (h *TaskHandler) update(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(r)
	if !ok {
		writeError(w, http.StatusBadRequest, "ID inválido")
		return
	}
	r.Body = http.MaxBytesReader(w, r.Body, 1<<20)
	var in TaskInput
	dec := json.NewDecoder(r.Body)
	dec.DisallowUnknownFields()
	if err := dec.Decode(&in); err != nil {
		writeError(w, http.StatusBadRequest, "JSON inválido")
		return
	}
	task, found := h.store.Update(id, in.Title, in.Completed)
	if !found {
		writeError(w, http.StatusNotFound, "tarefa não encontrada")
		return
	}
	writeJSON(w, http.StatusOK, task)
}

func (h *TaskHandler) delete(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(r)
	if !ok {
		writeError(w, http.StatusBadRequest, "ID inválido")
		return
	}
	if !h.store.Delete(id) {
		writeError(w, http.StatusNotFound, "tarefa não encontrada")
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

func parseID(r *http.Request) (int, bool) {
	id, err := strconv.Atoi(r.PathValue("id"))
	if err != nil || id < 1 {
		return 0, false
	}
	return id, true
}
OperaçãoMétodo + rotaSucessoFalha comum
ListarGET /tasks200—
CriarPOST /tasks201400 (JSON inválido)
BuscarGET /tasks/{id}200404
AtualizarPUT /tasks/{id}200404
RemoverDELETE /tasks/{id}204404

6. Middleware, timeouts e graceful shutdown

A versão acima usa http.ListenAndServe, que não impõe timeouts — em produção isso vira vazamento de goroutines e DoS acidental. O correto é construir um http.Server com limites e um desligamento elegante que termina as requisições em andamento:

func main() {
	store := NewTaskStore()
	h := &TaskHandler{store: store}

	mux := http.NewServeMux()
	mux.HandleFunc("GET /tasks", h.list)
	mux.HandleFunc("POST /tasks", h.create)
	mux.HandleFunc("GET /tasks/{id}", h.get)
	mux.HandleFunc("PUT /tasks/{id}", h.update)
	mux.HandleFunc("DELETE /tasks/{id}", h.delete)

	handler := logging(mux) // middleware de log por fora

	srv := &http.Server{
		Addr:         ":8080",
		Handler:      handler,
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 15 * time.Second,
		IdleTimeout:  120 * time.Second,
	}

	// Roda em segundo plano e aguarda SIGINT/SIGTERM para desligar
	ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer stop()

	go func() {
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("listen: %v", err)
		}
	}()
	<-ctx.Done()
	log.Println("desligando...")

	shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	_ = srv.Shutdown(shutdownCtx)
	log.Println("servidor parado")
}

Um middleware de log simples envolve o handler:

func logging(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()
		rec := &statusRecorder{ResponseWriter: w, status: 200}
		next.ServeHTTP(rec, r)
		log.Printf("%s %s %d %s", r.Method, r.URL.Path, rec.status, time.Since(start))
	})
}

type statusRecorder struct {
	http.ResponseWriter
	status int
}

func (r *statusRecorder) WriteHeader(code int) {
	r.status = code
	r.ResponseWriter.WriteHeader(code)
}

Para logs estruturados com níveis e campos, troque o log padrão pelo pacote slog — veja slog: logging estruturado para produção. O padrão de desligamento acima também aparece, com mais detalhes de produção, em graceful shutdown em Go.

7. Trocando memória por PostgreSQL

O TaskStore em memória existe só para o tutorial. Para dados reais, conecte ao PostgreSQL com pgx ou sqlc:

Com banco de dados você também precisa de migrations — leia migrations em Go para produção.

8. Testando a API

Teste os handlers em isolamento com httptest, sem subir servidor. O padrão table-driven cobre vários casos com pouco código:

func TestCreateTask(t *testing.T) {
	cases := []struct {
		name    string
		body    string
		want    int
	}{
		{"valido", `{"title":"Aprender Go","completed":false}`, http.StatusCreated},
		{"sem titulo", `{"completed":false}`, http.StatusBadRequest},
		{"campo desconhecido", `{"title":"x","extra":1}`, http.StatusBadRequest},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			req := httptest.NewRequest("POST", "/tasks", strings.NewReader(tc.body))
			rec := httptest.NewRecorder()
			newHandler().ServeHTTP(rec, req) // newHandler monta mux+store de teste
			if rec.Code != tc.want {
				t.Fatalf("status %d, esperado %d", rec.Code, tc.want)
			}
		})
	}
}

Aprofunde-se em testes em Go e no guia de table-driven tests.

9. Testando com cURL

Suba com go run . e exercite os endpoints:

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

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

# Buscar por ID
curl http://localhost:8080/tasks/1

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

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

10. Consumindo uma API externa com http.Client

Muitas buscas por Go API também querem o outro lado: consumir (chamar) uma API REST em Go — do IBGE, do ViaCEP, de um microsserviço interno ou de qualquer provedor. O padrão correto usa http.Client com timeout explícito, contexto, checagem de status e limite de leitura:

func fetchTask(ctx context.Context, url string) (Task, error) {
	client := &http.Client{Timeout: 10 * time.Second} // nunca use o client padrão sem timeout

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return Task{}, fmt.Errorf("criar requisição: %w", err)
	}
	req.Header.Set("Accept", "application/json")

	resp, err := client.Do(req)
	if err != nil {
		return Task{}, fmt.Errorf("chamar %s: %w", url, err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return Task{}, fmt.Errorf("%s retornou %d", url, resp.StatusCode)
	}

	var task Task
	if err := json.NewDecoder(io.LimitReader(resp.Body, 1<<20)).Decode(&task); err != nil {
		return Task{}, fmt.Errorf("decodificar resposta: %w", err)
	}
	return task, nil
}

Erros de rede e de decodificação voltam envolvidos com %w, então o chamador decide entre retry, circuit breaker ou falha rápida. Os quatro erros mais comuns ao consumir APIs em Go:

ErroCorreção
Usar http.Get (client padrão) sem timeoutCrie &http.Client{Timeout: ...} — o padrão nunca expira
Ignorar resp.StatusCode4xx/5xx não são erro do Do; confira antes de decodificar
Esquecer defer resp.Body.Close()Vaza conexões e esgota o pool
Decodificar corpo ilimitadoio.LimitReader (aqui, 1 MB) protege contra respostas gigantes

Para ir além: rastreie cada fase da chamada (DNS, TLS, conexão) com httptrace, proteja dependências instáveis com circuit breaker e teste o consumidor simulando o servidor com httptest e mocks.

11. Checklist de produção

Antes de expor a API, use o checklist de produção de API Go e cubra estes pontos — cada um tem conteúdo aprofundado no site:

12. Quando usar Gin (e quando não)

A standard library resolve a maioria das APIs pequenas e médias. Frameworks como Gin valem a pena quando você quer binding por tags, validação declarativa (binding:"required"), middlewares maduros e uma sintaxe ainda mais concisa. O custo é uma dependência externa e mais convenções para aprender. Para decidir e ver o caminho com framework, siga a série API REST com Golang e Gin (parte 1 a 4).

13. Deploy com Docker

Multi-stage build mantém a imagem final pequena (só o binário):

# build
FROM golang:1.27.1-alpine AS builder
WORKDIR /app
COPY go.mod ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o server .

# runtime
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
EXPOSE 8080
USER nonroot:nonroot
ENTRYPOINT ["/server"]
docker build -t todo-api .
docker run -p 8080:8080 todo-api

Para além do container local — Kubernetes, deploy em nuvem e CI/CD — veja Go com Docker, Go no Kubernetes e Go com TDD e CI/CD.

Próximos passos

A API funciona, mas é o começo. Uma progressão natural para dominar back-end com Go:

  1. Banco de dados real: conecte ao PostgreSQL com pgxpool e adicione migrations.
  2. Testes sólidos: cubra os handlers com table-driven tests.
  3. Segurança: adicione JWT e rate limiting.
  4. Arquitetura: evolua para clean architecture quando o domínio crescer.
  5. Carreira: pratique com perguntas de entrevista de Go e acompanhe vagas de back-end Go.

Veja Também


Última atualização: 2 de outubro de 2026 — cobertura das buscas Go API / Golang API / API em Go / Golang REST API, exemplos alinhados ao Go 1.27.1, nova seção de consumo de APIs com http.Client, CORS, timeouts, graceful shutdown e checklist de produção.

Perguntas frequentes

Como criar uma Go API, Golang API ou API em Go do zero?

Crie um módulo com go mod init, monte um http.NewServeMux, registre rotas como GET /tasks e POST /tasks, serialize respostas com encoding/json e execute um http.Server com timeouts. O exemplo completo desta página funciona com Go 1.22 ou superior e não exige framework.

Go API e Golang REST API são a mesma coisa?

Sim. Go API, Golang API, Golang REST API, API em Go e API Go descrevem o mesmo tipo de serviço HTTP escrito em Go. O nome oficial da linguagem é Go; Golang é o apelido de busca. Nesta página a implementação usa net/http da standard library, sem framework.

Go precisa de framework para criar API REST?

Não. A partir do Go 1.22, a standard library (net/http) já faz roteamento por método e por variável de caminho (GET /tasks/{id}) sem nenhuma biblioteca externa. Frameworks como Gin e Echo continuam úteis para conveniências e ecossistema, mas não são mais necessários para montar uma API REST organizada.

Como rotear por método e por ID na standard library do Go?

Use http.NewServeMux() com padrões como mux.HandleFunc("GET /tasks/{id}", handler) e leia o parâmetro com r.PathValue("id"). Esse recurso entrou no Go 1.22 e elimina o parsing manual de URL.

Go é bom para criar APIs?

Excelente. Go é uma das melhores linguagens para APIs: compila rápido, consome pouca memória, suporta alta concorrência nativa com goroutines e a standard library inclui um servidor HTTP completo. Empresas como Uber, Twitch e Mercado Livre usam Go para suas APIs.

Qual a diferença entre criar API REST com Go e com Gin?

Com a standard library você não adiciona dependências e usa os recursos nativos do Go 1.22+ (roteamento por método, PathValue, http.Server com timeouts). Com Gin você ganha binding de JSON, validação por tags, middlewares prontos e sintaxe mais concisa, ao custo de uma dependência externa. Para APIs pequenas e medianas, a standard library basta.

Qual pacote usar para criar uma Go API?

Comece com net/http, encoding/json e context, todos da biblioteca padrão. O http.NewServeMux do Go 1.22 ou superior aceita método e parâmetros na rota. Adicione Gin, Echo ou Chi apenas quando as conveniências do framework justificarem uma dependência externa.

Como consumir ou chamar uma API em Go?

Use um http.Client com Timeout definido (o client padrão não tem timeout), crie a requisição com http.NewRequestWithContext, execute com client.Do, feche o corpo com defer resp.Body.Close(), confira o StatusCode antes de decodificar e leia o JSON com json.NewDecoder. A seção sobre http.Client desta página mostra o exemplo completo, incluindo limite de tamanho e contexto.

O que falta para colocar uma API Go em produção?

Além do CRUD, configure timeouts no http.Server, graceful shutdown, autenticação, rate limiting, logs estruturados, testes com httptest e um contrato OpenAPI. Use o checklist de produção de API Go deste site e escolha um alvo de deploy compatível com binário único.