APIs RESTful são a base da comunicação entre sistemas modernos. Este guia mostra como construir uma API funcional usando Go no backend e PostgreSQL como banco, cobrindo as quatro operações de CRUD (Create, Read, Update, Delete) e, principalmente, como conectá-las de fato a rotas HTTP — a parte que a maioria dos tutoriais promete e não entrega.
Resumo rápido
- Usamos
database/sqlpuro (sem ORM) +github.com/lib/pqcomo driver PostgreSQL.- O roteamento HTTP fica por conta do
gorilla/mux.- Se você quer uma abordagem mais leve com framework web (Gin), tem outro guia aqui focado nisso — este post aqui é o caminho “sem framework”, mais explícito sobre o que está acontecendo por baixo.
Configurando o Ambiente
Antes de codar, instale:
- Go — instruções oficiais em go.dev/doc/install
- PostgreSQL — instruções oficiais em postgresql.org/download
- As bibliotecas Go necessárias:
go get github.com/lib/pq
go get github.com/gorilla/mux
Conectando ao PostgreSQL
import (
"database/sql"
"fmt"
_ "github.com/lib/pq"
)
func connectDB(host, port, user, password, dbname string) (*sql.DB, error) {
psqlInfo := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
host, port, user, password, dbname)
db, err := sql.Open("postgres", psqlInfo)
if err != nil {
return nil, err
}
if err = db.Ping(); err != nil {
return nil, err
}
fmt.Println("Conectado ao banco de dados!")
return db, nil
}
Troque host, port, user, password e dbname pelas suas credenciais reais. sql.Open só valida a string de conexão — é o db.Ping() que de fato testa se o banco está acessível.
As 4 Operações CRUD
Vamos usar uma tabela products com id, name e price.
func createProduct(db *sql.DB, name string, price float64) (int, error) {
var id int
err := db.QueryRow(`INSERT INTO products (name, price) VALUES ($1, $2) RETURNING id`, name, price).Scan(&id)
return id, err
}
func getProduct(db *sql.DB, id int) (string, float64, error) {
var name string
var price float64
err := db.QueryRow(`SELECT name, price FROM products WHERE id=$1`, id).Scan(&name, &price)
return name, price, err
}
func updateProduct(db *sql.DB, id int, name string, price float64) error {
_, err := db.Exec(`UPDATE products SET name = $2, price = $3 WHERE id = $1`, id, name, price)
return err
}
func deleteProduct(db *sql.DB, id int) error {
_, err := db.Exec(`DELETE FROM products WHERE id = $1`, id)
return err
}
Repare que toda query usa $1, $2 como parâmetro (placeholder), nunca concatenação de string — isso evita SQL injection por construção, não por disciplina.
O que a maioria dos tutoriais pula: ligar isso a rotas HTTP de verdade
Ter as funções de banco prontas não é ter uma API — falta o roteador que transforma requisição HTTP em chamada dessas funções. É aqui que o gorilla/mux entra:
import (
"encoding/json"
"net/http"
"strconv"
"github.com/gorilla/mux"
)
func getProductHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(mux.Vars(r)["id"])
if err != nil {
http.Error(w, "id inválido", http.StatusBadRequest)
return
}
name, price, err := getProduct(db, id)
if err != nil {
http.Error(w, "produto não encontrado", http.StatusNotFound)
return
}
json.NewEncoder(w).Encode(map[string]interface{}{"id": id, "name": name, "price": price})
}
}
func main() {
db, err := connectDB("localhost", "5432", "postgres", "senha", "loja")
if err != nil {
panic(err)
}
defer db.Close()
r := mux.NewRouter()
r.HandleFunc("/products/{id}", getProductHandler(db)).Methods("GET")
// repita o padrão pra POST, PUT e DELETE chamando createProduct/updateProduct/deleteProduct
http.ListenAndServe(":8080", r)
}
Agora GET /products/1 de verdade dispara getProduct e devolve JSON — o pedaço que faltava entre “ter funções de banco” e “ter uma API”.
Pra onde ir daqui
O próximo passo natural, antes de colocar isso em produção, é adicionar autenticação e validação de entrada — sem isso, qualquer um que souber a URL edita seus produtos. Se seu banco crescer e a dúvida for “Postgres é mesmo a escolha certa aqui”, vale ler por que PostgreSQL costuma ser a opção mais segura por padrão.
Perguntas frequentes
Por que não usar um ORM (tipo GORM)? database/sql puro dá controle total sobre a query e evita abstração desnecessária em APIs pequenas — mas pra projetos grandes, um ORM economiza bastante código repetitivo.
Isso é seguro contra SQL injection? Sim, desde que você sempre use parâmetros ($1, $2, …) em vez de concatenar string diretamente na query, como no exemplo acima.
Preciso do gorilla/mux mesmo, ou dá pra usar só net/http? Dá pra usar só net/http puro, mas você perde recursos como variáveis de rota ({id}) prontas — o mux só evita reimplementar isso na mão.
Recomendação relacionada
The Go Programming Language
Escrito por Alan Donovan e Brian Kernighan, é a referência mais citada por quem quer entender Go a fundo, além do básico.
Ver na Amazon →Como Associado Amazon, Visão Binária pode ganhar uma comissão sobre compras qualificadas feitas através deste link, sem custo adicional pra você.