Criando e desenvolvendo APIs em Go
Na lição anterior, você aprendeu a criar aplicações web usando a linguagem GoLang 😍
Mas você sabia que é possível criar aplicação web sem uma interface de usuário?
Sim, isso é totalmente possível, e é comumente chamada de APIs, que nada mais é do que uma abreviação de “Application Programming Interface” (Interface de Programação de Aplicações).
Portanto, em vez de entregar páginas HTML ao navegador, nós iremos aprender a entregar apenas dados (normalmente em formato JSON) que podem ser consumidos por outras aplicações, seja um frontend em JavaScript, um aplicativo móvel ou até outro serviço no backend.
Ao elaborar uma API em Go, você cria endpoints que respondem a requisições HTTP com payloads de dados, sem preocupações de renderizar templates ou lidar diretamente com a camada visual.
O que você precisa saber antes de aprender a construir APIs em GoLang?
Durante a nossa jornada, você deve ter passado por algumas lições que falavam sobre alguns assuntos correlacionados com o que iremos aprender agora, por exemplo:
Os ensinamentos existentes em cada uma dessas lições acima, são primordiais para aprendermos sobre APIs, portanto, se você ainda não passou por elas, recomendo dar uma olhada antes de continuar 😌
O que é uma API?

Você sabe o que é uma API? Faz ideia do que ela seja? Já criou uma API com GoLang alguma vez na sua vida?
Fique tranquilo se a sua resposta for NÃO para tudo, pois aqui nesta lição, o meu principal objetivo é te dar todas as respostas para as principais perguntas relacionadas a construção de APIs com GoLang 😉
Uma API (Application Programming Interface), cujo acrônico é Interface de Programação de Aplicações, nada mais é do que uma aplicação que conta com um conjunto de regras e definições que permitem que diferentes sistemas ou aplicativos se comuniquem entre si.
A ideia de uma API, é abstrair a complexidade interna de um software, permitindo que outras aplicações acessem suas funcionalidades sem precisar conhecer detalhes da sua implementação.
Um grande exemplo de APIs, são aplicativos de clima que retornam informações meteorológicas de um serviço remoto, ou um sistema de pagamento responsável por processar transações por meio de um gateway de pagamento.
Ainda não entendeu? Então vamos imaginar um exemplo mais prático 😋
Vamos imaginar que você tem um grande e-commerce de produtos eletrônicos, produtos estes que a maioria das pessoas não encontram em lugar algum, uma vez que é você quem os fabrica rs
Supondo que o seu e-commerce foi feito com GoLang, e que as informações dos seus produtos estão salvas em seu banco de dados relacional (MySQL).
É certo dizer que a sua aplicação é a única fonte que detém informações originais e atualizadas dos seus produtos, certo?
Vamos supor que a maioria dos seus produtos estourou no mercado de tecnologia, e com isso, você acabou recebendo diversas propostas de outros e-commerces e lojas virtuais, que se sentiram interessados em revender tais produtos.
Para que essa revenda seja possível, você vai precisar encontrar alguma forma de repassar as informações dos seus produtos para os outros e-commerces. E pensando em resolver essa questão de uma maneira rápida, você tem algumas opções:
- Você pode colocar os dados em uma planilha, e continuar enviando a mesma planilha com dados atualizados mensalmente aos revendedores.
- Ou você também pode criar um painel administrativo onde só os revendedores poderão ter acesso às informações dos produtos que você vende.
Ou.... como última alternativa, você também pode criar uma pequena aplicação que vai ficar responsável por distribuir as informações dos seus produtos que estão salvos em seu banco de dados, de modo a retornar essa resposta de volta ao cliente em um formato JSON
.
Ou seja, uma aplicação que não tem nenhuma interface de usuário (UI), onde foca exclusivamente no envio de informações que estão salvas em seu banco de dados.
Conseguiu imaginar esse sistema? Então... isso nada mais é do que uma API.
É claro que uma API vai muito além da consulta de dados, como também dá a possibilidade para que seus usuários possam de criar, atualizar ou até mesmo deletar registros.
Em termos práticos, as APIs são usadas para integrar sistemas, permitindo a troca de dados e a execução de operações entre diferentes softwares ou plataformas.
Pois dessa forma, um sistema feito em Python
pode se comunicar diretamente com um sistema feito em NodeJS
, ou quem sabe PHP
, C#
, Java
.... tudo por meio de APIs que se comunicam via requisições HTTP
por meio dos formatos JSON/XML
.
Conhecendo sobre os diferentes tipos de APIs
No mundo das APIs, nós podemos ter diferentes classificações com base nas funcionalidades e protocolos de cada uma delas.
Começando pela mais famosa de todas, nós temos as APIs baseadas em protocolos da web (HTTP), onde são amplamente usadas para conectar serviços online e aplicativos.
Dentre elas podemos destacar:
API REST (Representational State Transfer): é baseada em princípios arquitetônicos simples, onde usa métodos como GET
, POST
, PUT
, DELETE
para realizar suas operações, trabalhando com dados nos formatos JSON
ou XML
.
API SOAP (Simple Object Access Protocol): é um protocolo mais estruturado que utiliza XML
para troca de informações, e que pode operar sobre diferentes protocolos, como HTTP
ou SMTP
, sendo usada principalmente em serviços que exigem alta segurança e confiabilidade.
API GraphQL: permite que o cliente especifique exatamente quais dados deseja receber, o que evita o recebimento de dados desnecessários, sendo ideal para otimização de consultas em aplicações com grande volume de dados.
API de WebSocket: permite comunicação bidirecional e em tempo real entre cliente e servidor, sendo amplamente usada em aplicações que requerem atualizações dinâmicas, como sistemas de chat ou de negociação de ativos.
Em seguida, nós temos as APIs locais, que operam dentro de um sistema ou entre componentes locais de um aplicativo, como por exemplo:
- APIs de Bibliotecas ou Frameworks
- APIs de Sistema Operacional
- APIs de Hardware
- APIs de banco de dados
- APIs de pagamentos
É importante ressaltar que o foco desta lição será voltado para o entendimento das APIs do tipo REST, ok? 😉
Conhecendo um pouco mais sobre APIs do tipo Rest e RestFull
Como você já sabe, uma API REST segue um conjunto de princípios arquiteturais que definem como os recursos devem ser acessados e manipulados em uma aplicação web.
Ela se baseia no protocolo HTTP
e utiliza métodos padrão como GET
, POST
, PUT
, DELETE
, entre outros, sempre buscando responder em formatos JSON
ou XML
.
GET
: Para buscar ou recuperar dados.POST
: Para criar novos recursos.PUT
ouPATCH
: Para atualizar um recurso existente.DELETE
: Para excluir um recurso.
Lembrando que em uma API Rest, cada requisição funciona de forma independente (stateless = sem estado), ou seja, o servidor não armazena informações sobre o cliente entre as requisições.
Agora, para que que uma API seja considerada RestFull, ela precisa seguir todos os princípios de uma API Rest, e mais algumas outras coisas como:
Stateless: o servidor não deve armazenar o estado das requisições do cliente. Cada solicitação deve conter todas as informações necessárias para ser processada.
Cacheabilidade: as respostas das requisições podem ser armazenadas em cache (quando aplicável), para melhorar o desempenho e reduzir a carga no servidor.
Interface uniforme: a API deve fornecer uma interface uniforme e consistente, onde as mesmas operações (como GET, POST, etc.) são utilizadas da mesma forma em todos os recursos.
Desacoplamento cliente-servidor: o cliente e o servidor devem ser completamente independentes. O cliente deve ser capaz de evoluir sem depender de mudanças no servidor, e vice-versa.
Representações de recursos: um recurso pode ser representado de várias formas, como JSON
ou XML
, e o cliente pode solicitar diferentes representações de um recurso usando cabeçalhos HTTP
como Accept
.
Consegue perceber que uma API RestFull atua de forma um pouco mais completa que a uma API Rest? 🙂
Observação: a construção de uma API não se limita a nível da linguagem, uma vez que ele é um princípio de arquitetural.
Como (eu creio) que você já sabe e entende a aplicabilidade dos verbos HTTP e a serventia de cada um, vamos direto colocar a mão na massa!
Criando seu projeto de testes
Dentro da pasta JornadaGoLang, nós iremos criar uma nova pasta chamada de 25-apis-em-go
, mas dessa vez, não precisamos criar o nosso arquivo main.go
(ainda) 😉
Em seguida, não se esqueça de inicializar um novo projeto em Go, no meu caso eu fiz isso da seguinte forma:
go mod init apis-em-go
Criando sua primeira API em GoLang
A construção de uma API em GoLang segue a mesma estrutura de uma aplicação web que vimos na aula passada, a diferença é que não servimos conteúdos HTML
ao cliente, mas sim, conteúdos em JSON
ou XML
.
O nosso objetivo de hoje, será construir uma pequena API de gerenciamento de usuários, com os seguintes recursos:
- GET /users: listar todos os usuários cadastrados.
- GET /users/{id}: obtém um usuário específico pelo seu ID.
- POST /users: cria um novo usuário, enviando nome e email no corpo da requisição.
- PUT /users/{id}: atualiza os dados de um usuário existente
- DELETE /users/{id}: remove um usuário pelo ID.
O que é equivalente a uma espécie de CRUD no banco de dados, vamos lá? 😁
Definindo a Estrutura do Projeto
Para manter tudo organizado em suas respectivas pastas, vamos criar uma árvore de diretórios como vínhamos fazendo até então:
minha-api/
├── cmd/
│ └── api/
│ └── main.go
├── internal/
│ ├── domain/
│ │ └── user.go
│ ├── repositories/
│ │ └── user_inmemory.go
│ ├── services/
│ │ └── user_service.go
│ └── http/
│ ├── handlers/
│ │ └── user_handler.go
│ └── middlewares/
│ └── logging.go
├── go.mod
└── go.sum
cmd/api/main.go: será o nosso ponto de entrada da aplicação.
internal/domain/user.go: define a entidade User
e a interface de repositório.
internal/repositories/user_inmemory.go: implementação em memória de UserRepository
.
internal/services/user_service.go: contém regras de negócio (validação e orquestração).
internal/http/handlers/user_handler.go: expõe os endpoints da API, serializa/desserializa JSON
.
internal/http/middlewares/logging.go: registra logs de cada requisição.
Criou todas as pastas e arquivos necessários? Então vamos continuar...
Criando a nossa entidade e interface de Domínio
Agora, vamos começar primeiro definindo a nossa entidade User
e a interface que vai descrever como o serviço de persistência deverá funcionar.
Dentro do arquivo user.go
, existente na pasta internal/domain colocaremos a seguinte lógica:
// internal/domain/user.go
package domain
// User representa a entidade de usuário na API.
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
// UserRepository define os métodos que qualquer fonte de dados deve implementar
// para armazenar e recuperar usuários.
type UserRepository interface {
GetAll() ([]User, error)
GetByID(id int) (*User, error)
Create(u User) (*User, error)
Update(id int, u User) (*User, error)
Delete(id int) error
}
Na lógica acima, vemos que o campo ID
é do tipo int
e será gerado automaticamente (no nosso exemplo em memória).
Já as tags marcadas como json:campo
, elas irão garantir que ao serializar para JSON
, os nomes sejam exatamente "id", "name" e "email".
Criando o nosso repositório de memória
Para manter tudo funcionando de forma mais simples, criamos um repositório que guarda os usuários em um slice
.
Observação: em aplicações reais, você trocaria este arquivo por uma implementação que usa banco de dados (PostgreSQL, MySQL, MongoDB etc.).
Dentro do arquivo user_inmemory.go
, existente dentro da pasta internal/repositories colocaremos a seguinte lógica:
// internal/repositories/user_inmemory.go
package repositories
import (
"errors"
"sync"
"apis-em-go/internal/domain"
)
type userInMemory struct {
mu sync.RWMutex
storage []domain.User
nextID int
}
// NewUserInMemory retorna uma instância de UserRepository que armazena dados em memória.
func NewUserInMemory() domain.UserRepository {
return &userInMemory{
storage: make([]domain.User, 0),
nextID: 1,
}
}
func (r *userInMemory) GetAll() ([]domain.User, error) {
r.mu.RLock()
defer r.mu.RUnlock()
result := make([]domain.User, len(r.storage))
copy(result, r.storage)
return result, nil
}
func (r *userInMemory) GetByID(id int) (*domain.User, error) {
r.mu.RLock()
defer r.mu.RUnlock()
for _, u := range r.storage {
if u.ID == id {
// Retorna uma cópia para evitar vazamento do slice interno
usr := u
return &usr, nil
}
}
return nil, errors.New("usuário não encontrado")
}
func (r *userInMemory) Create(u domain.User) (*domain.User, error) {
r.mu.Lock()
defer r.mu.Unlock()
u.ID = r.nextID
r.nextID++
r.storage = append(r.storage, u)
created := u
return &created, nil
}
func (r *userInMemory) Update(id int, u domain.User) (*domain.User, error) {
r.mu.Lock()
defer r.mu.Unlock()
for i, existing := range r.storage {
if existing.ID == id {
u.ID = id
r.storage[i] = u
updated := u
return &updated, nil
}
}
return nil, errors.New("usuário não encontrado")
}
func (r *userInMemory) Delete(id int) error {
r.mu.Lock()
defer r.mu.Unlock()
for i, u := range r.storage {
if u.ID == id {
// Remove o elemento do slice
r.storage = append(r.storage[:i], r.storage[i+1:]...)
return nil
}
}
return errors.New("usuário não encontrado")
}
Observe que criamos um Mutex (sync.RWMutex
) que será usado para garantir mais segurança durante a leitura e escrita quando houver concorrência de requisições.
Note também que o nextID
incrementa para gerar um ID único a cada criação de um novo usuário dentro do nosso slice
.
Por fim, cada método implementa uma interface UserRepository
que foi definida dentro de domain/user.go 😉
Criando nosso serviço de usuário
Já o UserService
precisa conter a lógica de negócio, além de orquestrar todas chamadas feitas ao nosso repositório.
Nele podemos validar dados (por exemplo, verificar formato de e-mail) antes de delegar a criação/atualização ao repositório.
Sendo assim, dentro do arquivo user_service.go
, existente dentro da pasta internal/services, vamos inserir a seguinte lógica:
// internal/services/user_service.go
package services
import (
"errors"
"regexp"
"strings"
"apis-em-go/internal/domain"
)
// UserService é responsável por regras de negócio e usa um UserRepository.
type UserService struct {
Repo domain.UserRepository
}
// NewUserService cria um UserService com o repositório injetado.
func NewUserService(repo domain.UserRepository) *UserService {
return &UserService{Repo: repo}
}
// ListUsers retorna todos os usuários.
func (s *UserService) ListUsers() ([]domain.User, error) {
return s.Repo.GetAll()
}
// GetUser retorna um usuário pelo ID.
func (s *UserService) GetUser(id int) (*domain.User, error) {
return s.Repo.GetByID(id)
}
// CreateUser valida e então cria um novo usuário.
func (s *UserService) CreateUser(name, email string) (*domain.User, error) {
name = strings.TrimSpace(name)
email = strings.TrimSpace(email)
if name == "" {
return nil, errors.New("nome é obrigatório")
}
if email == "" {
return nil, errors.New("email é obrigatório")
}
// Validação simples de e-mail
re := regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,}$`)
if !re.MatchString(email) {
return nil, errors.New("formato de email inválido")
}
user := domain.User{
Name: name,
Email: email,
}
return s.Repo.Create(user)
}
// UpdateUser atualiza um usuário existente.
func (s *UserService) UpdateUser(id int, name, email string) (*domain.User, error) {
name = strings.TrimSpace(name)
email = strings.TrimSpace(email)
if name == "" {
return nil, errors.New("nome é obrigatório")
}
if email == "" {
return nil, errors.New("email é obrigatório")
}
re := regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,}$`)
if !re.MatchString(email) {
return nil, errors.New("formato de email inválido")
}
user := domain.User{
Name: name,
Email: email,
}
return s.Repo.Update(id, user)
}
// DeleteUser remove um usuário pelo ID.
func (s *UserService) DeleteUser(id int) error {
return s.Repo.Delete(id)
}
Repare que todas as decisões de negócio (verificar campos vazios, formatar strings, validar padrão de e-mail) acontecem aqui, além disso, o repositório continua responsável somente por persistir ou recuperar dados.
Criando o arquivo responsável pelos handlers HTTP
Agora, precisamos transformar requisições HTTP em chamadas a UserService
e serializa-las em respostas do tipo JSON
.
Neste caso especifico, nos iremos usar apenas a biblioteca padrão net/http
e encoding/json
.
Sendo assim, dentro do arquivo user_handler.go
, existente dentro da pasta internal/http/handlers, vamos inserir a seguinte lógica:
// internal/http/handlers/user_handler.go
package handlers
import (
"encoding/json"
"net/http"
"strconv"
"strings"
"apis-em-go/internal/services"
)
// UserHandler agrupa métodos que respondem às rotas da API de usuários.
type UserHandler struct {
Service *services.UserService
}
// NewUserHandler cria um UserHandler com o UserService injetado.
func NewUserHandler(svc *services.UserService) *UserHandler {
return &UserHandler{Service: svc}
}
// respondJSON define cabeçalhos e serializa resposta JSON.
func respondJSON(w http.ResponseWriter, status int, payload interface{}) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status)
if payload != nil {
json.NewEncoder(w).Encode(payload)
}
}
// parseID extrai o ID do final da URL (por exemplo, /users/123).
func parseID(path string) (int, error) {
// Path esperado: "/users/{id}"
parts := strings.Split(path, "/")
if len(parts) < 3 {
return 0, http.ErrNotSupported
}
return strconv.Atoi(parts[2])
}
// ListUsersHandler lida com GET /users
func (h *UserHandler) ListUsersHandler(w http.ResponseWriter, r *http.Request) {
users, err := h.Service.ListUsers()
if err != nil {
respondJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
respondJSON(w, http.StatusOK, users)
}
// GetUserHandler lida com GET /users/{id}
func (h *UserHandler) GetUserHandler(w http.ResponseWriter, r *http.Request) {
id, err := parseID(r.URL.Path)
if err != nil {
respondJSON(w, http.StatusBadRequest, map[string]string{"error": "ID inválido"})
return
}
user, err := h.Service.GetUser(id)
if err != nil {
respondJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()})
return
}
respondJSON(w, http.StatusOK, user)
}
// CreateUserHandler lida com POST /users
func (h *UserHandler) CreateUserHandler(w http.ResponseWriter, r *http.Request) {
var input struct {
Name string `json:"name"`
Email string `json:"email"`
}
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
respondJSON(w, http.StatusBadRequest, map[string]string{"error": "JSON inválido"})
return
}
created, err := h.Service.CreateUser(input.Name, input.Email)
if err != nil {
respondJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
respondJSON(w, http.StatusCreated, created)
}
// UpdateUserHandler lida com PUT /users/{id}
func (h *UserHandler) UpdateUserHandler(w http.ResponseWriter, r *http.Request) {
id, err := parseID(r.URL.Path)
if err != nil {
respondJSON(w, http.StatusBadRequest, map[string]string{"error": "ID inválido"})
return
}
var input struct {
Name string `json:"name"`
Email string `json:"email"`
}
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
respondJSON(w, http.StatusBadRequest, map[string]string{"error": "JSON inválido"})
return
}
updated, err := h.Service.UpdateUser(id, input.Name, input.Email)
if err != nil {
if err.Error() == "usuário não encontrado" {
respondJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()})
} else {
respondJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
}
return
}
respondJSON(w, http.StatusOK, updated)
}
// DeleteUserHandler lida com DELETE /users/{id}
func (h *UserHandler) DeleteUserHandler(w http.ResponseWriter, r *http.Request) {
id, err := parseID(r.URL.Path)
if err != nil {
respondJSON(w, http.StatusBadRequest, map[string]string{"error": "ID inválido"})
return
}
if err := h.Service.DeleteUser(id); err != nil {
respondJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()})
return
}
respondJSON(w, http.StatusNoContent, nil)
}
parseID
: vai extrair o ID a partir do caminho da URL (supondo que o handler esteja registrado em /users/).
Onde cada handler lê parâmetros de URL ou do corpo JSON
, chama o UserService
e usa respondJSON
para enviar a resposta adequada, incluindo os status codes
.
Criando nosso middleware de logs
É sempre bom registrarmos cada requisição que acontece dentro do nosso servidor, e para isso é vital que criemos um middleware simples que imprime informações relevantes no console.
Em aplicações reais e mais robustas, a ideia é que você salve esses logs em uma base de dados, ou em arquivos dentro de uma pasta do seu sistema.
Sendo assim, dentro do arquivo logging.go
, existente dentro da pasta internal/http/middlewares/, vamos inserir a seguinte lógica:
// internal/http/middlewares/logging.go
package middlewares
import (
"log"
"net/http"
"time"
)
// LoggingMiddleware imprime método, caminho e duração da requisição.
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
duration := time.Since(start)
log.Printf("%s %s → %v\n", r.Method, r.URL.Path, duration)
})
}
Criando o arquivo de ponto de entrada (main.go)
Por fim, no arquivo main.go
, montamos o roteador, registramos as rotas, aplicamos nossos middlewares e iniciamos o servidor.
Para isso, crie um novo arquivo chamado main.go
dentro da pasta cmd/api:
// cmd/api/main.go
package main
import (
"log"
"net/http"
"apis-em-go/internal/http/handlers"
"apis-em-go/internal/http/middlewares"
"apis-em-go/internal/repositories"
"apis-em-go/internal/services"
)
func main() {
// 1. Configurações iniciais (por ex., leitura de variáveis de ambiente) poderiam ser aqui
// 2. Instancia o repositório (in-memory)
userRepo := repositories.NewUserInMemory()
// 3. Cria o serviço de usuário
userSvc := services.NewUserService(userRepo)
// 4. Cria o handler, injetando o UserService
userHandler := handlers.NewUserHandler(userSvc)
// 5. Configura mux (roteador) e registra rotas com middleware de logging
mux := http.NewServeMux()
// "/users" => GET (listar) ou POST (criar)
mux.Handle("/users",
middlewares.LoggingMiddleware(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
userHandler.ListUsersHandler(w, r)
case http.MethodPost:
userHandler.CreateUserHandler(w, r)
default:
http.Error(w, "Método não permitido", http.StatusMethodNotAllowed)
}
}),
),
)
// "/users/" => GET, PUT ou DELETE em "/users/{id}"
mux.Handle("/users/",
middlewares.LoggingMiddleware(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
userHandler.GetUserHandler(w, r)
case http.MethodPut:
userHandler.UpdateUserHandler(w, r)
case http.MethodDelete:
userHandler.DeleteUserHandler(w, r)
default:
http.Error(w, "Método não permitido", http.StatusMethodNotAllowed)
}
}),
),
)
// 6. Inicia o servidor na porta 3000
addr := ":3000"
log.Println("API rodando em http://localhost" + addr)
if err := http.ListenAndServe(addr, mux); err != nil {
log.Fatal("Erro ao iniciar servidor:", err)
}
}
Como net/http
puro não faz roteamento “por método + caminho” automaticamente, registramos /users
duas vezes (para GET e POST) e /users/
para as rotas dinâmicas com {id}
.
Além disso, é possível usar um router
mais avançado (por exemplo, github.com/gorilla/mux ou github.com/julienschmidt/httprouter) para simplificar a definição de rotas e extração de parâmetros, mas neste exemplo mantemos padrão.
E pronto, já temos todas as lógicas necessárias para o rodar a nossa primeira API em GoLang 😄
Testando a sua primeira API

Agora que já temos nossa primeira API em mãos, que tal testarmos o funcionamento dela?
Observação: Não se esqueça de inicializar seu servidor (go run cmd/api/main.go
) antes de prosseguir.
Como você já sabe, uma API não possuí interface visual, e como consequência disso, você vai precisar criar um sistema capaz de realizar requisições HTTP para a sua API, ou quem sabe fazer o uso de uma ferramenta capaz de realizar requisições HTTP.
No meu caso eu vou usar o próprio CURL do sistema operacional, mas você pode usar o POSTMAN para isso, como também fazer o uso de qualquer outra ferramenta que desejar 😋
1) listando usuários (inicialmente virá vazio):
curl -i http://localhost:3000/users
A resposta esperada será:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
[]
2) Criando um novo usuário:
curl -i -X POST http://localhost:3000/users \
-H "Content-Type: application/json" \
-d '{"name": "João Silva", "email": "joao@exemplo.com"}'
A resposta esperada será:
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
{"id":1,"name":"João Silva","email":"joao@exemplo.com"}
3) Verificando se o usuário foi inserido com sucesso (listando novamente):
curl -i http://localhost:3000/users
A resposta esperada será:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
[{"id":1,"name":"João Silva","email":"joao@exemplo.com"}]
4) Obtendo um usuário por um ID específico:
curl -i http://localhost:3000/users/1
A resposta esperada será:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":1,"name":"João Silva","email":"joao@exemplo.com"}
5) Atualizando as informações de um determinado usuário:
curl -i -X PUT http://localhost:3000/users/1 \
-H "Content-Type: application/json" \
-d '{"name": "João S.", "email": "jsilva@exemplo.com"}'
A resposta esperada será:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":1,"name":"João S.","email":"jsilva@exemplo.com"}
6) Removendo um usuário da lista:
curl -i -X DELETE http://localhost:3000/users/1
A resposta esperada será:
HTTP/1.1 204 No Content
Content-Type: application/json; charset=utf-8
Por fim, você pode verificar se o usuário foi removido com sucesso, executando novamente a rota de listagem:
curl -i http://localhost:3000/users
E como resposta esperada:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
[]
Se todos os passos acima deram certo, parabéns! A sua API está funcionando perfeitamente! 🥳
Boas práticas que você pode adotar ao construir suas próprias APIs em GoLang
Abaixo deixou um conjunto de boas práticas que você pode implementar em suas APIs afim de deixá-las cada vez mais robustas.
Que tal usar um Router mais avançado?
Quando sua API está crescendo, é bom você considerar a migração para outras bibliotecas de roteamento, como gorilla/mux
, chi
ou httprouter
.
Pois elas facilitam a extração de parâmetros de rota, vejamos um exemplo com o uso de mux
:
r := mux.NewRouter()
r.HandleFunc("/users", userHandler.ListUsersHandler).Methods("GET")
r.HandleFunc("/users", userHandler.CreateUserHandler).Methods("POST")
r.HandleFunc("/users/{id}", userHandler.GetUserHandler).Methods("GET")
r.HandleFunc("/users/{id}", userHandler.UpdateUserHandler).Methods("PUT")
r.HandleFunc("/users/{id}", userHandler.DeleteUserHandler).Methods("DELETE")
log.Fatal(http.ListenAndServe(":3000", r))
Note o quão fácil a biblioteca é em esconder grande parte da lógica do roteamento, o que faz com que o seu código fique mais limpo.
Persistência real: Sempre crie uma implementação de UserRepository
que use um banco de dados relacional (via database/sql ou um ORM), ou um banco NoSQL
. Basta implementar a interface domain.UserRepository
.
Validações avançadas: Utilize pacotes como go-playground/validator
para validar estruturas de forma automatizada, reduzindo código manual de checagem de campos.
Estrutura de erros padronizada: Defina um modelo de erro JSON
único (por exemplo: {"error": "mensagem", "code": 123}) e crie um middleware para capturar panic ou erros não tratados, retornando sempre no mesmo formato.
Vai precisar de autenticação e autorização de acesso?
Sem problemas, a biblioteca github.com/golang-jwt/jwt/v5
resolve isso para você. Com ela seu sistema será capaz de implementar um middleware capaz de validar tokens JWT
.
No topo do mux
ou do router
escolhido, registre esse middleware para todas as rotas que exigem login 🙂↕️
Não sabe o que são Tokens JWT? Então dê uma olhada neste artigo.
Sempre faça o uso de documentações automáticas!
Ferramentas como swaggo/swag
permitem gerar documentação Swagger a partir de comentários no código, facilitando o consumo da API por terceiros.
E por fim, não se esqueça de implementar testes!
Neste caso, é uma boa prática escrever testes unitários para seus serviços (UserService), mocando domain.UserRepository
.
Para seus handlers, use httptest.NewRecorder()
e http.NewRequest
para simular requisições HTTP.
Mas não se preocupe, futuramente teremos uma lição que vai tratar exclusivamente de testes automatizados em GoLang! 🤓
Repositório da lição
Todos os arquivos relacionados com esta lição, podem ser encontrados nos seguintes repositórios abaixo:
Conclusão
Nesta lição, você aprendeu a criar APIs com GoLang, sejam elas APIs Rest ou RestFull.
Além disso, aprendeu a seguir boas práticas de arquitetura, mantendo o código limpo e testável.
Na próxima lição, vamos entender em como criar e gerenciar datas e horas com GoLang.
Até já 🤗