Go

Como Usar Construindo APIs REST em Go sem Framework: Roteamento e Middlewares em Produção

20 min de leitura

Como Usar Construindo APIs REST em Go sem Framework: Roteamento e Middlewares em Produção

Fundamentos de APIs REST em Go Puro Go é uma linguagem extremamente eficiente para construir APIs REST de alta performance. Diferentemente de linguagens que dependem fortemente de frameworks, Go oferece uma biblioteca padrão robusta ( ) que fornece tudo o que você precisa para criar endpoints profissionais. Muitos desenvolvedores assumem erroneamente que precisam de um framework como Gin ou Echo para fazer isso, mas a realidade é que o pacote é suficientemente poderoso e maduro para aplicações em produção. O principal conceito que você precisa compreender é como o trabalha. Quando você inicia um servidor HTTP em Go, você está basicamente registrando handlers — funções que recebem uma solicitação HTTP e escrevem uma resposta. Esses handlers seguem a interface , que exige apenas um método: . Essa simplicidade é a força de Go: você não está preso a convenções complexas ou configurações mágicas. Vamos começar com o exemplo mais fundamental — um servidor HTTP básico: Quando você executa este código

<h2>Fundamentos de APIs REST em Go Puro</h2>

<p>Go é uma linguagem extremamente eficiente para construir APIs REST de alta performance. Diferentemente de linguagens que dependem fortemente de frameworks, Go oferece uma biblioteca padrão robusta (<code>net/http</code>) que fornece tudo o que você precisa para criar endpoints profissionais. Muitos desenvolvedores assumem erroneamente que precisam de um framework como Gin ou Echo para fazer isso, mas a realidade é que o pacote <code>net/http</code> é suficientemente poderoso e maduro para aplicações em produção.</p>

<p>O principal conceito que você precisa compreender é como o <code>net/http</code> trabalha. Quando você inicia um servidor HTTP em Go, você está basicamente registrando <strong>handlers</strong> — funções que recebem uma solicitação HTTP e escrevem uma resposta. Esses handlers seguem a interface <code>http.Handler</code>, que exige apenas um método: <code>ServeHTTP(ResponseWriter, *Request)</code>. Essa simplicidade é a força de Go: você não está preso a convenções complexas ou configurações mágicas.</p>

<p>Vamos começar com o exemplo mais fundamental — um servidor HTTP básico:</p>

<pre><code class="language-go">package main

import (

&quot;encoding/json&quot;

&quot;net/http&quot;

)

func main() {

// Registrar um handler simples

http.HandleFunc(&quot;/&quot;, func(w http.ResponseWriter, r *http.Request) {

w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)

json.NewEncoder(w).Encode(map[string]string{

&quot;mensagem&quot;: &quot;Olá, mundo!&quot;,

})

})

// Iniciar servidor na porta 8080

http.ListenAndServe(&quot;:8080&quot;, nil)

}</code></pre>

<p>Quando você executa este código e acessa <code>http://localhost:8080</code>, o handler é invocado. O <code>ResponseWriter</code> permite que você escreva headers e o corpo da resposta, enquanto <code>*Request</code> contém todas as informações sobre a requisição recebida. Simples, mas poderoso.</p>

<h2>Roteamento Avançado Sem Framework</h2>

<p>O roteamento é a capacidade de mapear diferentes caminhos de URL para diferentes handlers. A abordagem básica usando <code>http.HandleFunc()</code> funciona bem para aplicações pequenas, mas fica limitada quando você precisa de rotas parametrizadas, métodos HTTP específicos ou padrões mais complexos. Aqui está onde a coisa fica interessante: você pode construir um roteador sofisticado usando apenas Go puro.</p>

<h3>Por Que Não Usar o Roteamento Padrão?</h3>

<p>O <code>http.HandleFunc()</code> registra rotas de forma estática e não oferece suporte nativo para parâmetros dinâmicos. Se você tentar acessar <code>/usuarios/123</code>, o <code>http.HandleFunc()</code> não consegue extrair o <code>123</code> automaticamente. Além disso, ele trata todas as requisições da mesma forma independentemente do método HTTP (GET, POST, etc.).</p>

<p>Vamos construir um roteador personalizado que resolve esses problemas:</p>

<pre><code class="language-go">package main

import (

&quot;encoding/json&quot;

&quot;net/http&quot;

&quot;strings&quot;

)

// Handler é um tipo de função que define como tratamos requisições

type Handler func(http.ResponseWriter, *http.Request, map[string]string)

// Rota define um padrão de URL, método HTTP e handler

type Rota struct {

Metodo string

Padrao string

Handler Handler

}

// Roteador armazena todas as rotas registradas

type Roteador struct {

rotas []*Rota

}

// Registrar adiciona uma rota ao roteador

func (r *Roteador) Registrar(metodo, padrao string, handler Handler) {

r.rotas = append(r.rotas, &amp;Rota{

Metodo: metodo,

Padrao: padrao,

Handler: handler,

})

}

// extrairParametros compara um padrão com um caminho e extrai parâmetros

func extrairParametros(padrao, caminho string) (map[string]string, bool) {

partes_padrao := strings.Split(strings.Trim(padrao, &quot;/&quot;), &quot;/&quot;)

partes_caminho := strings.Split(strings.Trim(caminho, &quot;/&quot;), &quot;/&quot;)

if len(partes_padrao) != len(partes_caminho) {

return nil, false

}

params := make(map[string]string)

for i, parte := range partes_padrao {

// Se a parte começa com :, é um parâmetro

if strings.HasPrefix(parte, &quot;:&quot;) {

nome := strings.TrimPrefix(parte, &quot;:&quot;)

params[nome] = partes_caminho[i]

} else if parte != partes_caminho[i] {

// Se não é parâmetro e não corresponde, falha

return nil, false

}

}

return params, true

}

// ServeHTTP implementa a interface http.Handler

func (r Roteador) ServeHTTP(w http.ResponseWriter, req http.Request) {

for _, rota := range r.rotas {

// Verificar se o método HTTP corresponde

if rota.Metodo != req.Method {

continue

}

// Tentar extrair parâmetros

params, encontrado := extrairParametros(rota.Padrao, req.URL.Path)

if encontrado {

rota.Handler(w, req, params)

return

}

}

// Se nenhuma rota encontrada, retornar 404

w.WriteHeader(http.StatusNotFound)

json.NewEncoder(w).Encode(map[string]string{

&quot;erro&quot;: &quot;Rota não encontrada&quot;,

})

}

// Exemplo de handler com parâmetros

func obterUsuario(w http.ResponseWriter, r *http.Request, params map[string]string) {

usuarioID := params[&quot;id&quot;]

w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)

json.NewEncoder(w).Encode(map[string]string{

&quot;id&quot;: usuarioID,

&quot;nome&quot;: &quot;João Silva&quot;,

})

}

func listarUsuarios(w http.ResponseWriter, r *http.Request, params map[string]string) {

w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)

json.NewEncoder(w).Encode([]map[string]string{

{&quot;id&quot;: &quot;1&quot;, &quot;nome&quot;: &quot;João&quot;},

{&quot;id&quot;: &quot;2&quot;, &quot;nome&quot;: &quot;Maria&quot;},

})

}

func main() {

roteador := &amp;Roteador{}

// Registrar rotas

roteador.Registrar(&quot;GET&quot;, &quot;/usuarios&quot;, listarUsuarios)

roteador.Registrar(&quot;GET&quot;, &quot;/usuarios/:id&quot;, obterUsuario)

http.ListenAndServe(&quot;:8080&quot;, roteador)

}</code></pre>

<p>Neste exemplo, criamos um roteador que:</p>

<ol>

<li><strong>Armazena rotas</strong> com seu padrão, método HTTP e handler associado</li>

<li><strong>Extrai parâmetros</strong> analisando a URL — quando encontra <code>:id</code>, sabe que é um parâmetro dinâmico</li>

<li><strong>Implementa <code>http.Handler</code></strong> permitindo que seja usado diretamente com <code>http.ListenAndServe()</code></li>

<li><strong>Retorna 404</strong> se nenhuma rota corresponder</li>

</ol>

<p>Teste com <code>curl http://localhost:8080/usuarios/123</code> e você verá que o <code>123</code> é capturado como parâmetro.</p>

<h2>Middlewares: Tratamento Transversal de Requisições</h2>

<p>Middlewares são funções que &quot;embrulham&quot; seus handlers, permitindo executar lógica antes e depois da requisição ser processada. Pense em middlewares como um sistema de camadas: a requisição passa por todas as camadas antes de chegar ao handler real, e a resposta passa pelas mesmas camadas novamente.</p>

<h3>Entendendo a Cadeia de Middlewares</h3>

<p>Um middleware em Go é simplesmente uma função que recebe um <code>http.Handler</code> e retorna um novo <code>http.Handler</code>. Isso permite composição — você pode agrupar múltiplos middlewares e aplicar a qualquer rota. Os casos de uso mais comuns são: logging de requisições, autenticação, validação CORS, tratamento de erros e medição de performance.</p>

<p>Vamos construir um sistema robusto de middlewares:</p>

<pre><code class="language-go">package main

import (

&quot;encoding/json&quot;

&quot;fmt&quot;

&quot;log&quot;

&quot;net/http&quot;

&quot;time&quot;

)

// Middleware é uma função que recebe um handler e retorna um novo handler

type Middleware func(http.Handler) http.Handler

// middlewareLogging registra detalhes de cada requisição

func middlewareLogging(handler http.Handler) http.Handler {

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

inicio := time.Now()

// Executar o handler original

handler.ServeHTTP(w, r)

// Registrar informações após a requisição

duracao := time.Since(inicio)

log.Printf(&quot;[%s] %s %s - %v&quot;, r.Method, r.URL.Path, r.RemoteAddr, duracao)

})

}

// middlewareAutenticacao verifica um token simples no header

func middlewareAutenticacao(handler http.Handler) http.Handler {

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

token := r.Header.Get(&quot;Authorization&quot;)

// Token inválido ou ausente

if token != &quot;Bearer seu-token-secreto&quot; {

w.WriteHeader(http.StatusUnauthorized)

json.NewEncoder(w).Encode(map[string]string{

&quot;erro&quot;: &quot;Token inválido ou ausente&quot;,

})

return

}

// Token válido, continuar

handler.ServeHTTP(w, r)

})

}

// middlewareCORS adiciona headers CORS

func middlewareCORS(handler http.Handler) http.Handler {

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

w.Header().Set(&quot;Access-Control-Allow-Origin&quot;, &quot;*&quot;)

w.Header().Set(&quot;Access-Control-Allow-Methods&quot;, &quot;GET, POST, PUT, DELETE&quot;)

w.Header().Set(&quot;Access-Control-Allow-Headers&quot;, &quot;Content-Type, Authorization&quot;)

// Se for um OPTIONS request, responder e encerrar

if r.Method == http.MethodOptions {

w.WriteHeader(http.StatusOK)

return

}

handler.ServeHTTP(w, r)

})

}

// middlewarePanico recupera de panics na aplicação

func middlewarePanico(handler http.Handler) http.Handler {

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

defer func() {

if err := recover(); err != nil {

log.Printf(&quot;PANIC: %v&quot;, err)

w.WriteHeader(http.StatusInternalServerError)

json.NewEncoder(w).Encode(map[string]string{

&quot;erro&quot;: &quot;Erro interno do servidor&quot;,

})

}

}()

handler.ServeHTTP(w, r)

})

}

// aplicarMiddlewares aplica uma sequência de middlewares a um handler

func aplicarMiddlewares(handler http.Handler, middlewares ...Middleware) http.Handler {

// Aplicar middlewares na ordem reversa para que o primeiro middleware

// listado seja o primeiro a executar

for i := len(middlewares) - 1; i &gt;= 0; i-- {

handler = middlewares[i](handler)

}

return handler

}

// Handler que simula uma operação

func handlerDados(w http.ResponseWriter, r *http.Request) {

w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)

json.NewEncoder(w).Encode(map[string]interface{}{

&quot;dados&quot;: &quot;Informação sensível&quot;,

&quot;timestamp&quot;: time.Now(),

&quot;autorizado&quot;: true,

})

}

func main() {

// Criar um handler base

handler := http.HandlerFunc(handlerDados)

// Aplicar middlewares — a ordem importa!

// Ordem de execução será: CORS → Autenticação → Logging → Panico → Handler

handler = aplicarMiddlewares(

handler,

middlewarePanico, // Executar por último (mas aplicado primeiro)

middlewareLogging,

middlewareAutenticacao,

middlewareCORS, // Executar por primeiro

)

http.Handle(&quot;/dados&quot;, handler)

fmt.Println(&quot;Servidor iniciado em :8080&quot;)

http.ListenAndServe(&quot;:8080&quot;, nil)

}</code></pre>

<p><strong>Por que a ordem é reversa?</strong> Quando aplicamos middlewares, cada um &quot;envolve&quot; o anterior. Se você pensa visualmente:</p>

<pre><code>Requisição → CORS → Autenticação → Logging → Panico → Handler</code></pre>

<p>Mas no código, você aplica na ordem inversa para conseguir esse resultado. Se você tentar acessar <code>curl -H &quot;Authorization: Bearer seu-token-secreto&quot; http://localhost:8080/dados</code>, verá o middleware de CORS permitindo, a autenticação validando o token, o logging registrando a requisição, e o handler finalmente executando.</p>

<h3>Contexto de Requisição com Middlewares</h3>

<p>Um padrão avançado é usar o contexto (<code>context.Context</code>) para passar dados entre middlewares e handlers. Isso é especialmente útil quando middlewares precisam compartilhar informações:</p>

<pre><code class="language-go">package main

import (

&quot;context&quot;

&quot;encoding/json&quot;

&quot;net/http&quot;

)

// HandlerComContexto é um tipo de handler que aceita contexto customizado

type HandlerComContexto func(http.ResponseWriter, *http.Request)

// middlewareUsuario extrai o usuário do token e o coloca no contexto

func middlewareUsuario(handler http.Handler) http.Handler {

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

// Simular extração de usuário do token

usuarioID := &quot;usuario-123&quot;

nomeUsuario := &quot;João&quot;

// Criar novo contexto com os dados do usuário

ctx := context.WithValue(r.Context(), &quot;usuarioID&quot;, usuarioID)

ctx = context.WithValue(ctx, &quot;nomeUsuario&quot;, nomeUsuario)

// Passar a requisição com o novo contexto

handler.ServeHTTP(w, r.WithContext(ctx))

})

}

// Handler que usa dados do contexto

func handlerPerfil(w http.ResponseWriter, r *http.Request) {

usuarioID := r.Context().Value(&quot;usuarioID&quot;).(string)

nomeUsuario := r.Context().Value(&quot;nomeUsuario&quot;).(string)

w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)

json.NewEncoder(w).Encode(map[string]string{

&quot;id&quot;: usuarioID,

&quot;nome&quot;: nomeUsuario,

})

}

func main() {

handler := http.HandlerFunc(handlerPerfil)

handler = middlewareUsuario(handler)

http.Handle(&quot;/perfil&quot;, handler)

http.ListenAndServe(&quot;:8080&quot;, nil)

}</code></pre>

<h2>Integração Completa: API REST Funcional</h2>

<p>Até agora vimos componentes isolados. Vamos agora montar uma API REST completa que integra roteamento avançado com middlewares robusos:</p>

<pre><code class="language-go">package main

import (

&quot;encoding/json&quot;

&quot;fmt&quot;

&quot;log&quot;

&quot;net/http&quot;

&quot;strings&quot;

&quot;time&quot;

)

// Tipos para nossa API

type Usuario struct {

ID string json:&quot;id&quot;

Nome string json:&quot;nome&quot;

Email string json:&quot;email&quot;

}

type Rota struct {

Metodo string

Padrao string

Handler http.Handler

}

type Roteador struct {

rotas []*Rota

}

// Banco de dados simulado

var usuarios = map[string]Usuario{

&quot;1&quot;: {ID: &quot;1&quot;, Nome: &quot;João Silva&quot;, Email: &quot;joao@example.com&quot;},

&quot;2&quot;: {ID: &quot;2&quot;, Nome: &quot;Maria Santos&quot;, Email: &quot;maria@example.com&quot;},

}

// Registrar adiciona uma rota com middlewares aplicados

func (r *Roteador) Registrar(metodo, padrao string, handler http.Handler) {

r.rotas = append(r.rotas, &amp;Rota{

Metodo: metodo,

Padrao: padrao,

Handler: handler,

})

}

// extrairParametros extrai parâmetros da URL

func extrairParametros(padrao, caminho string) (map[string]string, bool) {

partes_padrao := strings.Split(strings.Trim(padrao, &quot;/&quot;), &quot;/&quot;)

partes_caminho := strings.Split(strings.Trim(caminho, &quot;/&quot;), &quot;/&quot;)

if len(partes_padrao) != len(partes_caminho) {

return nil, false

}

params := make(map[string]string)

for i, parte := range partes_padrao {

if strings.HasPrefix(parte, &quot;:&quot;) {

nome := strings.TrimPrefix(parte, &quot;:&quot;)

params[nome] = partes_caminho[i]

} else if parte != partes_caminho[i] {

return nil, false

}

}

return params, true

}

// ServeHTTP implementa http.Handler

func (r Roteador) ServeHTTP(w http.ResponseWriter, req http.Request) {

for _, rota := range r.rotas {

if rota.Metodo != req.Method {

continue

}

params, encontrado := extrairParametros(rota.Padrao, req.URL.Path)

if encontrado {

// Passar parâmetros via contexto

ctx := req.Context()

for chave, valor := range params {

ctx = context.WithValue(ctx, &quot;param_&quot;+chave, valor)

}

rota.Handler.ServeHTTP(w, req.WithContext(ctx))

return

}

}

// 404

w.WriteHeader(http.StatusNotFound)

json.NewEncoder(w).Encode(map[string]string{&quot;erro&quot;: &quot;Não encontrado&quot;})

}

// Middlewares

func middlewareLogging(next http.Handler) http.Handler {

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

inicio := time.Now()

next.ServeHTTP(w, r)

log.Printf(&quot;[%s] %s - %v&quot;, r.Method, r.URL.Path, time.Since(inicio))

})

}

func middlewareContentType(next http.Handler) http.Handler {

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)

next.ServeHTTP(w, r)

})

}

func middlewareAutenticacao(next http.Handler) http.Handler {

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

// Rotas públicas não precisam de autenticação

if r.URL.Path == &quot;/usuarios&quot; &amp;&amp; r.Method == &quot;GET&quot; {

next.ServeHTTP(w, r)

return

}

token := r.Header.Get(&quot;Authorization&quot;)

if token != &quot;Bearer token-secreto&quot; {

w.WriteHeader(http.StatusUnauthorized)

json.NewEncoder(w).Encode(map[string]string{&quot;erro&quot;: &quot;Não autorizado&quot;})

return

}

next.ServeHTTP(w, r)

})

}

func aplicarMiddlewares(handler http.Handler, middlewares ...func(http.Handler) http.Handler) http.Handler {

for i := len(middlewares) - 1; i &gt;= 0; i-- {

handler = middlewares[i](handler)

}

return handler

}

// Handlers

func listarUsuarios(w http.ResponseWriter, r *http.Request) {

usuariosList := make([]Usuario, 0, len(usuarios))

for _, u := range usuarios {

usuariosList = append(usuariosList, u)

}

json.NewEncoder(w).Encode(usuariosList)

}

func obterUsuario(w http.ResponseWriter, r *http.Request) {

id := r.Context().Value(&quot;param_id&quot;).(string)

usuario, existe := usuarios[id]

if !existe {

w.WriteHeader(http.StatusNotFound)

json.NewEncoder(w).Encode(map[string]string{&quot;erro&quot;: &quot;Usuário não encontrado&quot;})

return

}

json.NewEncoder(w).Encode(usuario)

}

func criarUsuario(w http.ResponseWriter, r *http.Request) {

var usuario Usuario

if err := json.NewDecoder(r.Body).Decode(&amp;usuario); err != nil {

w.WriteHeader(http.StatusBadRequest)

json.NewEncoder(w).Encode(map[string]string{&quot;erro&quot;: &quot;JSON inválido&quot;})

return

}

usuarios[usuario.ID] = usuario

w.WriteHeader(http.StatusCreated)

json.NewEncoder(w).Encode(usuario)

}

func main() {

roteador := &amp;Roteador{}

// Aplicar middlewares para todas as rotas

baseHandler := aplicarMiddlewares(

http.Handler(roteador),

middlewareLogging,

middlewareContentType,

middlewareAutenticacao,

)

// Registrar rotas

roteador.Registrar(&quot;GET&quot;, &quot;/usuarios&quot;, http.HandlerFunc(listarUsuarios))

roteador.Registrar(&quot;GET&quot;, &quot;/usuarios/:id&quot;, http.HandlerFunc(obterUsuario))

roteador.Registrar(&quot;POST&quot;, &quot;/usuarios&quot;, http.HandlerFunc(criarUsuario))

http.Handle(&quot;/&quot;, baseHandler)

fmt.Println(&quot;API iniciada em :8080&quot;)

log.Fatal(http.ListenAndServe(&quot;:8080&quot;, nil))

}</code></pre>

<p>Note que falta <code>import &quot;context&quot;</code> — adicione no início do arquivo. Este exemplo completo demonstra:</p>

<ul>

<li><strong>Roteamento com parâmetros</strong> — <code>/usuarios/:id</code> extrai o ID da URL</li>

<li><strong>Passagem de contexto</strong> — Parâmetros são passados via <code>context.Context</code></li>

<li><strong>Middlewares globais</strong> — Todos os handlers recebem logging e validação de content-type</li>

<li><strong>Autenticação condicional</strong> — Apenas rotas POST/PUT/DELETE exigem token</li>

<li><strong>Tratamento de erros</strong> — Status HTTP apropriados para cada cenário</li>

</ul>

<p>Teste com:</p>

<pre><code class="language-bash"># Listar usuários (sem autenticação)

curl http://localhost:8080/usuarios

Obter usuário específico

curl http://localhost:8080/usuarios/1

Criar usuário (requer token)

curl -X POST -H &quot;Authorization: Bearer token-secreto&quot; \

-H &quot;Content-Type: application/json&quot; \

-d &#039;{&quot;id&quot;:&quot;3&quot;,&quot;nome&quot;:&quot;Pedro&quot;,&quot;email&quot;:&quot;pedro@example.com&quot;}&#039; \

http://localhost:8080/usuarios</code></pre>

<h2>Conclusão</h2>

<p>Você aprendeu que <strong>Go puro oferece poder suficiente para construir APIs REST profissionais</strong> sem precisar de frameworks externos. O pacote <code>net/http</code> combinado com padrões bem estruturados (implementar <code>http.Handler</code>, usar middlewares como wrappers de funções, aplicar contexto para compartilhar dados) resulta em código limpo, performático e fácil de testar.</p>

<p>O segundo ponto crucial é que <strong>middlewares em Go não são mágicos</strong> — são simplesmente funções que recebem handlers e retornam handlers, permitindo composição elegante. Essa simplicidade torna fácil adicionar logging, autenticação, tratamento de erros e qualquer outra lógica transversal sem poluir seus handlers.</p>

<p>Por fim, você viu que <strong>roteamento avançado é construível com menos de 50 linhas de código</strong> — extraindo parâmetros de URLs e mapeando métodos HTTP. Isso demonstra uma filosofia fundamental de Go: ser explícito e fazer muito com pouco, evitando abstrações desnecessárias que ocultam o que realmente está acontecendo.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://golang.org/pkg/net/http/" target="_blank" rel="noopener noreferrer">Net/HTTP Package - Documentação Oficial Go</a></li>

<li><a href="https://golang.org/pkg/context/" target="_blank" rel="noopener noreferrer">Context Package - Go Documentation</a></li>

<li><a href="https://golang.org/doc/articles/wiki/" target="_blank" rel="noopener noreferrer">Writing Web Applications - Go Tutorial</a></li>

<li><a href="https://github.com/golang-standards/project-layout" target="_blank" rel="noopener noreferrer">Building REST APIs in Go</a></li>

<li><a href="https://blog.golang.org/using-go-modules" target="_blank" rel="noopener noreferrer">The Go Blog: Using Go Modules</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Go

Middleware de Autenticação, Logging e Rate Limiting em Go na Prática
Middleware de Autenticação, Logging e Rate Limiting em Go na Prática

Introdução: Por que Middleware Importa em Go Middleware é um padrão fundament...

O que Todo Dev Deve Saber sobre Embedding em Go: Composição de Structs e Interfaces
O que Todo Dev Deve Saber sobre Embedding em Go: Composição de Structs e Interfaces

Embedding em Go: O que é e Por Que Importa Embedding é um mecanismo poderoso...

Boas Práticas de sync.WaitGroup e sync.Once em Go: Coordenação de Goroutines para Times Ágeis
Boas Práticas de sync.WaitGroup e sync.Once em Go: Coordenação de Goroutines para Times Ágeis

Entendendo Goroutines e a Necessidade de Sincronização Go foi projetado com c...