Como construir um aplicativo web Go com Gi, MongoDB e AI
Avalie esse Tutorial
Criar aplicativos com o Go oferece muitas vantagens. A linguagem é rápida, simples e leve, ao mesmo tempo em que oferece suporte a recursos poderosos, como simultaneidade, digitação forte e uma biblioteca padrão robusta. Neste tutorial, usaremos o popular framework Gin web junto com o MongoDB para criar um aplicativo web baseado em Go.
O Gin é um framework web simples para Go que oferece uma maneira fácil de criar servidores web e APIs. Ele é rápido, leve e modular, o que o torna ideal para criar microsserviços e APIs, mas pode ser facilmente estendido para a criação de aplicativos completos.
Usaremos o Gin para criar um aplicativo Web com três endpoints que se conectam a um MongoDB database. O MongoDB é um banco de dados NoSQL orientado a documentos popular que armazena dados em documentos do tipo JSON. O MongoDB é uma ótima opção para criar aplicativos modernos.
Em vez de criar toda a aplicação manualmente, aproveitaremos um assistente de codificação de IA da Sourcegraph chamado Cody para nos ajudar a criar nossa aplicação Go. O Cody é o único assistente de IA que conhece toda a sua base de código e pode ajudar você a gravar, depurar, testar e documentar seu código. Usaremos muitos desses recursos ao desenvolvermos nosso aplicativo hoje.
Antes de começar, você precisará:
- Familiaridade básica com a sintaxe Go e MongoDB.
- Sourcegraph Cody instalado no seu IDE favorito. (Para este tutorial, usaremos o VS Code). Obtenha-o gratuitamente.
Após atender aos pré-requisitos, você estará pronto para desenvolver. Vamos lá!
Começaremos criando um novo projeto Go para nosso aplicativo. Para este exemplo, vamos nomear o projeto mflix, então bora criar o diretório do projeto e navegar por ele:
1 mkdir mflix 2 cd mflix
1 go mod init mflix
Agora que temos nosso módulo Go criado, vamos instalar as dependências do nosso projeto. Vamos mantê-lo simples e apenas instalar as bibliotecas
gin
e mongodb
.1 go get github.com/gin-gonic/gin 2 go get go.mongodb.org/mongo-driver/mongo
Com nossas dependências obtidas e instaladas, estamos prontos para começar a criar nosso aplicativo.
Para começar a desenvolver nosso aplicativo, vamos criar nosso ponto de entrada criando um arquivo main.go. Em seguida, embora possamos configurar nosso aplicativo manualmente, vamos aproveitar o Cody para criar nosso ponto de partida. Na janela de bate-papo do Cody, podemos solicitar a criação de um aplicativo Go Gin básico.
O Cody gerou um bom ponto de partida para nós. Ele importou o framework Gin, criou uma função
main
, e instanciou um aplicativo Gin básico com uma única rota que imprime a mensagem Hello World
. Bom começo.1 package main 2 3 import ( 4 "github.com/gin-gonic/gin" 5 ) 6 7 func main() { 8 r := gin.Default() 9 r.GET("/", func(c *gin.Context) { 10 c.JSON(200, gin.H{ 11 "message": "Hello World", 12 }) 13 }) 14 15 r.Run() 16 }
Vamos garantir que esse código seja executado. Inicie o servidor executando
go run main.go
na janela de terminal dentro do diretório mflix e, em seguida, navegue até localhost:8080, que é a porta padrão para um aplicativo Gin. Nosso código funciona e o resultado que devemos ver é:Temos um ótimo ponto de partida agora. A seguir, vamos adicionar nosso cliente MongoDB ao nosso aplicativo Gin. Poderíamos usar Cody novamente, mas para este, nós mesmos vamos gravar. Atualizaremos o código para o seguinte:
1 package main 2 3 import ( 4 // Add required Go packages 5 "context" 6 "log" 7 8 "github.com/gin-gonic/gin" 9 10 // Add the MongoDB driver packages 11 "go.mongodb.org/mongo-driver/mongo" 12 "go.mongodb.org/mongo-driver/mongo/options" 13 ) 14 15 // Your MongoDB Atlas Connection String 16 const uri = "YOUR-CONNECTION-STRING-HERE" 17 18 // A global variable that will hold a reference to the MongoDB client 19 var mongoClient *mongo.Client 20 21 22 // The init function will run before our main function to establish a connection to MongoDB. If it cannot connect it will fail and the program will exit. 23 func init() { 24 if err := connect_to_mongodb(); err != nil { 25 log.Fatal("Could not connect to MongoDB") 26 } 27 } 28 29 func main() { 30 r := gin.Default() 31 r.GET("/", func(c *gin.Context) { 32 c.JSON(200, gin.H{ 33 "message": "Hello World", 34 }) 35 }) 36 37 r.Run() 38 } 39 40 // Our implementation logic for connecting to MongoDB 41 func connect_to_mongodb() error { 42 serverAPI := options.ServerAPI(options.ServerAPIVersion1) 43 opts := options.Client().ApplyURI(uri).SetServerAPIOptions(serverAPI) 44 45 client, err := mongo.Connect(context.TODO(), opts) 46 if err != nil { 47 panic(err) 48 } 49 err = client.Ping(context.TODO(), nil) 50 mongoClient = client 51 return err 52 }
Certifique-se de definir sua string de conexão do MongoDB Atlas na linha 12 da variável
const uri
. Caso contrário, o programa não será executado. Você pode obter sua string de conexão do MongoDB Atlas navegando até o dashboard do Atlas, clicando no botão "Connect" no cluster de banco de dados e selecionando o driver que está usando.Se precisar de mais ajuda para configurar o MongoDB Atlas cluster e carregar os dados de exemplo, confira o guia"How to Use a Sample Database with MongoDB " . O banco de dados com o qual trabalharemos é chamado
sample_mflix
e a collection nesse banco de dados que usaremos é chamada movies
. Esse conjunto de dados contém uma lista de filmes com várias informações, como roteiro, gênero, ano de lançamento e muito mais.Agora que temos nosso MongoDB database configurado em nossa aplicação Go, estamos prontos para começar a criar nossos endpoints adicionais. Como trabalharemos com o conjunto de dados de amostra que contém informações de filmes, criaremos três endpoints com base no trabalho com os dados de filmes:
- Um endpoint para obter uma lista de todos os filmes.
- Um endpoint para obter um único filme com base em um
id
fornecido. - Um endpoint para executar uma agregação na coleção de filmes.
Podemos fazer isso manualmente ou, se você é novo em escrever aplicativos Go, pode perguntar ao Cody. Vamos perguntar ao Cody.
Cody nos deu três endpoints prontos para uso.
Esse endpoint entrará no banco de dados
sample_mflix
e, em seguida, na coleção movies
e recuperará todos os filmes.1 // GET /movies - Get all movies 2 func getMovies(c *gin.Context) { 3 // Find movies 4 cursor, err := mongoClient.Database("sample_mflix").Collection("movies").Find(context.TODO(), bson.D{{}}) 5 if err != nil { 6 c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 7 return 8 } 9 10 // Map results 11 var movies []bson.M 12 if err = cursor.All(context.TODO(), &movies); err != nil { 13 c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 14 return 15 } 16 17 // Return movies 18 c.JSON(http.StatusOK, movies) 19 }
O segundo endpoint retornará um filme específico com base no
id
fornecido na coleção movies
no banco de dados sample_mflix
.1 // GET /movies/:id - Get movie by ID 2 func getMovieByID(c *gin.Context) { 3 // Get movie ID from URL 4 id := c.Param("id") 5 6 // Find movie by ID 7 var movie bson.M 8 err := mongoClient.Database("sample_mflix").Collection("movies").FindOne(context.TODO(), bson.D{{"_id", id}}).Decode(&movie) 9 if err != nil { 10 c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 11 return 12 } 13 14 // Return movie 15 c.JSON(http.StatusOK, movie) 16 }
O terceiro e último endpoint nos permitirá executar agregações na coleção de filmes. Operações de agregação processam diversos documentos e geram resultados calculados. Assim, com esse endpoint, o usuário final pode passar qualquer pipeline de agregação válido do MongoDB para executar várias análises na coleção
movies
.Observe que as agregações são muito avançadas e, em um ambiente de produção, você provavelmente não gostaria de habilitar esse nível de acesso por meio de payloads de solicitação HTTP. Mas, para fins de execução deste tutorial, optamos por manter isso. Como tarefa de casa, para aprendizado adicional, tente usar o Cody para limitar o número de estágios ou os tipos de operações que o usuário final pode executar nesse endpoint.
1 // POST /movies/aggregations - Run aggregations on movies 2 func aggregateMovies(c *gin.Context) { 3 // Get aggregation pipeline from request body 4 var pipeline interface{} 5 if err := c.ShouldBindJSON(&pipeline); err != nil { 6 c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 7 return 8 } 9 10 // Run aggregations 11 cursor, err := mongoClient.Database("sample_mflix").Collection("movies").Aggregate(context.TODO(), pipeline) 12 if err != nil { 13 c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 14 return 15 } 16 17 // Map results 18 var result []bson.M 19 if err = cursor.All(context.TODO(), &result); err != nil { 20 c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 21 return 22 } 23 24 // Return result 25 c.JSON(http.StatusOK, result) 26 }
Agora que implementamos nossos endpoints, vamos adicioná-los ao roteador para que possamos chamá-los. Mais uma vez, podemos usar outro recurso da Cody, o preenchimento automático, para nos fornecer de forma inteligente as conclusões das declarações, de modo que não precisemos escrever todo o código.
Nossa função
main
deve agora ter a seguinte aparência:1 func main() { 2 r := gin.Default() 3 r.GET("/", func(c *gin.Context) { 4 c.JSON(200, gin.H{ 5 "message": "Hello World", 6 }) 7 }) 8 r.GET("/movies", getMovies) 9 r.GET("/movies/:id", getMovieByID) 10 r.POST("/movies/aggregations", aggregateMovies) 11 12 r.Run() 13 }
Agora que temos nossas rotas configuradas, vamos testar nossa aplicação para garantir que tudo esteja funcionando bem. Reinicie o servidor e navegue até localhost:8080/movies. Se tudo correr bem, você verá uma grande lista de filmes retornados no formato JSON na janela do navegador. Se não vir isso, verifique o console do IDE para ver quais erros são mostrados.
Vamos testar o segundo endpoint. Escolha qualquer
id
da coleção de filmes e navegue até localhost:8080/movies/{{id} – por exemplo, localhost:8080/movies/573a1390f29313caabcd42e8. Se tudo correr bem, você verá esse único filme listado. Mas se você estiver seguindo este tutorial, na verdade, não verá o filme.O problema é que, em nossa implementação da função
getMovie
, estamos aceitando o valor de id
como uma string
, enquanto o tipo de dados em nosso MongoDB database é um ObjectID
. Portanto, quando executamos o método FindOne
e tentamos fazer a correspondência entre o valor da string de id
ao valor de ObjectID
, não obtemos uma correspondência.Vamos pedir ao Cody que nos ajude a corrigir isso, convertendo a entrada de string que recebemos em um
ObjectID
.Nossa função
getMovieByID
atualizada é a seguinte:1 func getMovieByID(c *gin.Context) { 2 3 // Get movie ID from URL 4 idStr := c.Param("id") 5 6 // Convert id string to ObjectId 7 id, err := primitive.ObjectIDFromHex(idStr) 8 if err != nil { 9 c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 10 return 11 } 12 13 // Find movie by ObjectId 14 var movie bson.M 15 err = mongoClient.Database("sample_mflix").Collection("movies").FindOne(context.TODO(), bson.D{{"_id", id}}).Decode(&movie) 16 if err != nil { 17 c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 18 return 19 } 20 21 // Return movie 22 c.JSON(http.StatusOK, movie) 23 }
Dependendo do seu IDE, talvez seja necessário adicionar a dependência
primitive
em sua declaração de importação. A declaração de importação final é semelhante a:1 import ( 2 "context" 3 "log" 4 "net/http" 5 6 "github.com/gin-gonic/gin" 7 "go.mongodb.org/mongo-driver/bson" 8 "go.mongodb.org/mongo-driver/bson/primitive" 9 "go.mongodb.org/mongo-driver/mongo" 10 "go.mongodb.org/mongo-driver/mongo/options" 11 )
Se examinarmos o novo código fornecido pelo Cody, poderemos ver que agora estamos obtendo o valor de nosso parâmetro
id
e armazenando-o em uma variável chamada idStr
. Em seguida, usamos o pacote primitivo para tentar converter a string em um ObjectID
. Se idStr
for uma string válida que possa ser convertida em ObjectID
, então estaremos prontos e usaremos a nova variável id
ao fazer nossa operação FindOne
. Caso contrário, receberemos uma mensagem de erro de volta.Reinicie seu servidor e agora tente obter um único resultado de filme navegando até localhost:8080/movies/ {id}.
Para nosso endpoint final, estamos permitindo que o usuário final forneça um pipeline de agregação que executaremos na coleção
mflix
. O usuário pode fornecer qualquer agregação que desejar. Para testar esse endpoint, faremos uma solicitação POST para o localhost:8080/movies/aggregations. No corpo da solicitação, incluiremos nosso pipeline de agregação.Vamos executar uma agregação para retornar uma contagem de filmes de comédia, agrupados por ano e em ordem decrescente. Novamente, lembre-se de que as agregações são muito poderosas e podem ser usadas de forma abusiva. Normalmente, não desejamos conceder acesso direto ao usuário final para gravar e executar suas próprias agregações ad hoc em uma solicitação HTTP, a menos que seja para uma ferramenta interna ou algo do tipo. Nosso pipeline de agregação ficará assim:
1 [ 2 {"$match": {"genres": "Comedy"}}, 3 {"$group": { 4 "_id": "$year", 5 "count": {"$sum": 1} 6 }}, 7 {"$sort": {"count": -1}} 8 ]
Ao executar essa agregação, obteremos um conjunto de resultados com a seguinte aparência:
1 [ 2 { 3 "_id": 2014, 4 "count": 287 5 }, 6 { 7 "_id": 2013, 8 "count": 286 9 }, 10 { 11 "_id": 2009, 12 "count": 268 13 }, 14 { 15 "_id": 2011, 16 "count": 263 17 }, 18 { 19 "_id": 2006, 20 "count": 260 21 }, 22 ... 23 ]
Parece que 2014 foi um grande ano para a comédia. Se você não estiver familiarizado com o funcionamento das agregações, confira os seguintes recursos:
Além disso, você pode pedir ao Cody uma explicação específica sobre como nossa função
aggregateMovies
funciona para entender melhor como o código é implementado usando o comando Cody /explain
.Escrevemos um servidor web Go usando Gin, MongoDB e Cody hoje. Embora o aplicativo possa não ser o código mais complexo, aprendemos como fazê-lo:
- Crie rotas e endpoints usando o framework web do Gin.
- Implementar o MongoDB em nosso aplicativo Gin.
- Fazer queries do MongoDB para recuperar dados.
- Executar agregações do MongoDB.
- Aproveitar o Cody para nos ajudar a gravarr, depurar e explicar o código.
A saída final documentada de todo o código que escrevemos nesta publicação está abaixo para sua referência:
1 // Declare the entry point into our application 2 package main 3 4 // Add our dependencies from the standard library, Gin, and MongoDB 5 import ( 6 "context" 7 "fmt" 8 "log" 9 "net/http" 10 11 "github.com/gin-gonic/gin" 12 "go.mongodb.org/mongo-driver/bson" 13 "go.mongodb.org/mongo-driver/bson/primitive" 14 "go.mongodb.org/mongo-driver/mongo" 15 "go.mongodb.org/mongo-driver/mongo/options" 16 ) 17 18 // Define your MongoDB connection string 19 const uri = "{YOUR-CONNECTION-STRING-HERE}" 20 21 // Create a global variable to hold our MongoDB connection 22 var mongoClient *mongo.Client 23 24 // This function runs before we call our main function and connects to our MongoDB database. If it cannot connect, the application stops. 25 func init() { 26 if err := connect_to_mongodb(); err != nil { 27 log.Fatal("Could not connect to MongoDB") 28 } 29 } 30 31 32 // Our entry point into our application 33 func main() { 34 // The simplest way to start a Gin application using the frameworks defaults 35 r := gin.Default() 36 37 // Our route definitions 38 r.GET("/", func(c *gin.Context) { 39 c.JSON(200, gin.H{ 40 "message": "Hello World", 41 }) 42 }) 43 r.GET("/movies", getMovies) 44 r.GET("/movies/:id", getMovieByID) 45 r.POST("/movies/aggregations", aggregateMovies) 46 47 // The Run() method starts our Gin server 48 r.Run() 49 } 50 51 // Implemention of the /movies route that returns all of the movies from our movies collection. 52 func getMovies(c *gin.Context) { 53 // Find movies 54 cursor, err := mongoClient.Database("sample_mflix").Collection("movies").Find(context.TODO(), bson.D{{}}) 55 if err != nil { 56 c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 57 return 58 } 59 60 // Map results 61 var movies []bson.M 62 if err = cursor.All(context.TODO(), &movies); err != nil { 63 c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 64 return 65 } 66 67 // Return movies 68 c.JSON(http.StatusOK, movies) 69 } 70 71 72 // The implementation of our /movies/{id} endpoint that returns a single movie based on the provided ID 73 func getMovieByID(c *gin.Context) { 74 75 // Get movie ID from URL 76 idStr := c.Param("id") 77 78 // Convert id string to ObjectId 79 id, err := primitive.ObjectIDFromHex(idStr) 80 if err != nil { 81 c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 82 return 83 } 84 85 // Find movie by ObjectId 86 var movie bson.M 87 err = mongoClient.Database("sample_mflix").Collection("movies").FindOne(context.TODO(), bson.D{{"_id", id}}).Decode(&movie) 88 if err != nil { 89 c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 90 return 91 } 92 93 // Return movie 94 c.JSON(http.StatusOK, movie) 95 } 96 97 // The implementation of our /movies/aggregations endpoint that allows a user to pass in an aggregation to run our the movies collection. 98 func aggregateMovies(c *gin.Context) { 99 // Get aggregation pipeline from request body 100 var pipeline interface{} 101 if err := c.ShouldBindJSON(&pipeline); err != nil { 102 c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 103 return 104 } 105 106 // Run aggregations 107 cursor, err := mongoClient.Database("sample_mflix").Collection("movies").Aggregate(context.TODO(), pipeline) 108 if err != nil { 109 c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 110 return 111 } 112 113 // Map results 114 var result []bson.M 115 if err = cursor.All(context.TODO(), &result); err != nil { 116 c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) 117 return 118 } 119 120 // Return result 121 c.JSON(http.StatusOK, result) 122 } 123 124 125 // Our implementation code to connect to MongoDB at startup 126 func connect_to_mongodb() error { 127 serverAPI := options.ServerAPI(options.ServerAPIVersion1) 128 opts := options.Client().ApplyURI(uri).SetServerAPIOptions(serverAPI) 129 130 client, err := mongo.Connect(context.TODO(), opts) 131 if err != nil { 132 panic(err) 133 } 134 err = client.Ping(context.TODO(), nil) 135 mongoClient = client 136 return err 137 }
O Go é uma linguagem de programação surpreendente e o Gin é um framework muito poderoso para desenvolver aplicativos web. Combinado com o MongoDB, com o driver nativo do MongoDB e com uma pequena ajuda do Cody, conseguimos criar esse aplicativo em pouco tempo.
Cody é o único assistente de AI que conhece toda a sua base de código. Neste tutorial, apenas começamos a explorar as possibilidades. Além do preenchimento automático e dos comandos que mostramos hoje, o Cody pode identificar problemas no código, documentar seu código, criar testes de unidade e oferecer suporte à criação de comandos personalizados para estendê-lo a qualquer caso de uso que você tenha. Experimente o Cody gratuitamente em cody.dev.
E se você tiver alguma dúvida ou comentário, vamos continuar a conversa em nossos fóruns de desenvolvedores!
O código inteiro do nosso aplicativo está acima, então não há repositório do GitHub para esse aplicativo simples. Feliz codificação.