Pular para o conteúdo

API Real: Go e PostgreSQL (CRUD Prático)

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/sql puro (sem ORM) + github.com/lib/pq como 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:

  1. Go — instruções oficiais em go.dev/doc/install
  2. PostgreSQL — instruções oficiais em postgresql.org/download
  3. 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ê.