Back-End

Back-End

Criando um CRUD completo com Go e MongoDB

6 set, 2018
Publicidade

Introdução

Depois de algum tempo sem escrever sobre Go, hoje eu vou mostrar como criar uma API RESTful utilizando ele junto com o MongoDB.

Para os próximos passos você precisa ter instalado no seu computador as seguintes ferramentas:

  • GoLang
  • MongoDB: utilizarei ele dentro de um contêiner Docker
  • Editor de Textos: utilizarei o Visual Studio Code

No final deste artigo você terá criado uma API utilizando os principais verbos HTTP. Caso tenha interesse em baixar a versão final do código que iremos desenvolver nesse artigo, segue o seu link no GitHub: go-restapi.

Criação do projeto

Para criar um novo projeto, abra um terminal no seu computador e navegue até o seu diretório do seu GOPATH. Em seguida, entre na pasta /src/github.com/seu_usuario_git/ e crie um novo projeto. Para esse artigo eu irei chamar de go-restapi, mas você pode escolher um outro de sua preferência.

mkdir -p %GOPATH%/src/github.com/{your username}/go-restapi Windows
mkdir -p $GOPATH/src/github.com/{your username}/go-restapi Linux

Agora que você já tem o caminho que utilizará para o seu projeto criado, o próximo passo será criar a sua estrutura. Para isso, crie a seguinte estrutura de arquivos e diretórios:

Estrutura do projeto

Analisando eles, você tem:

  • config.toml: iremos passar os dados de conexão do nosso db
  • config/config.go: arquivo de configuração, ele irá ler o dados do arquivo config.toml
  • main.go: principal arquivo do nosso projeto
  • config/dao/movies_dao.go: será o nosso repositório, aqui nós iremos interagir com o mongoDB
  • models/movie.go: nossa model
  • router/movie_router.go: arquivo contendo as nossas rotas

O próximo passo será baixar os pacotes necessários para o nosso projeto. Para isso, execute o seguinte comando no seu terminal:

go get github.com/BurntSushi/toml gopkg.in/mgo.v2 github.com/gorilla/mux

  • toml: iremos utilizar para trabalhar com parser e encoder
  • mux: para criação das nossas rotas
  • mgo: MongoDB driver

Com a estrutura básica pronta e os pacotes importados, vamos agora atualizar os arquivos criados no passo anterior:

config/config.go

package config

import (
	"log"

	"github.com/BurntSushi/toml"
)

type Config struct {
	Server   string
	Database string
}

func (c *Config) Read() {
	if _, err := toml.DecodeFile("config.toml", &c); err != nil {
		log.Fatal(err)
	}
}
  • 01: nome do nosso package
  • 03 a 07: importando os pacotes necessários para o nosso package
  • 09 a 12: criando uma struct para passarmos os dados de conexão do nosso db
  • 14 a 18: estamos lendo o arquivo config.toml; passaremos esses dados para nossa struct

config/db/movies_dao.go

package dao

import (
	"log"

	. "github.com/programadriano/go-restapi/models"
	mgo "gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

type MoviesDAO struct {
	Server   string
	Database string
}

var db *mgo.Database

const (
	COLLECTION = "movies"
)

func (m *MoviesDAO) Connect() {
	session, err := mgo.Dial(m.Server)
	if err != nil {
		log.Fatal(err)
	}
	db = session.DB(m.Database)
}

func (m *MoviesDAO) GetAll() ([]Movie, error) {
	var movies []Movie
	err := db.C(COLLECTION).Find(bson.M{}).All(&movies)
	return movies, err
}

func (m *MoviesDAO) GetByID(id string) (Movie, error) {
	var movie Movie
	err := db.C(COLLECTION).FindId(bson.ObjectIdHex(id)).One(&movie)
	return movie, err
}

func (m *MoviesDAO) Create(movie Movie) error {
	err := db.C(COLLECTION).Insert(&movie)
	return err
}

func (m *MoviesDAO) Delete(id string) error {
	err := db.C(COLLECTION).RemoveId(bson.ObjectIdHex(id))
	return err
}

func (m *MoviesDAO) Update(id string, movie Movie) error {
	err := db.C(COLLECTION).UpdateId(bson.ObjectIdHex(id), &movie)
	return err
}
  • 11 a 14: estamos criando uma struct para conexão com o banco de dados
  • 18 a 20: estamos passando a collection que mapearemos com o nosso repositório
  • 22 a 28: estamos criando uma session com o nosso banco de dados – estamos utilizando o método Dial do pacote mgo. Caso dê algum problema ele mata a session, caso tudo esteja ok, ele passa o valor da session para variável db
  • 30 a 34: criando o nosso método GetAll, esse método deve retornar um json
  • 36 a 40: o nosso método GetByID
  • 42 a 44: o método Create
  • 47 a 50: o método Delete
  • 52 a 56: o método Update

models/movie.go

package models

import "gopkg.in/mgo.v2/bson"

type Movie struct {
	ID          bson.ObjectId `bson:"_id" json:"id"`
	Name        string        `bson:"name" json:"name"`
	ThubImage   string        `bson:"thumb_image" json:"thumb_image"`
	Description string        `bson:"description" json:"description"`
	Active      bool          `bson:"active" json:"active"`
}

Esse é a nossa Movie model. Eu criei alguns campos básicos para o nosso CRUD.

router/movie_router.go

package movierouter

import (
	"encoding/json"
	"net/http"

	"github.com/gorilla/mux"
	. "github.com/programadriano/go-restapi/config/dao"
	. "github.com/programadriano/go-restapi/models"
	"gopkg.in/mgo.v2/bson"
)

var dao = MoviesDAO{}

func respondWithError(w http.ResponseWriter, code int, msg string) {
	respondWithJson(w, code, map[string]string{"error": msg})
}

func respondWithJson(w http.ResponseWriter, code int, payload interface{}) {
	response, _ := json.Marshal(payload)
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(code)
	w.Write(response)
}

func GetAll(w http.ResponseWriter, r *http.Request) {
	movies, err := dao.GetAll()
	if err != nil {
		respondWithError(w, http.StatusInternalServerError, err.Error())
		return
	}
	respondWithJson(w, http.StatusOK, movies)
}

func GetByID(w http.ResponseWriter, r *http.Request) {
	params := mux.Vars(r)
	movie, err := dao.GetByID(params["id"])
	if err != nil {
		respondWithError(w, http.StatusBadRequest, "Invalid Movie ID")
		return
	}
	respondWithJson(w, http.StatusOK, movie)
}

func Create(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	var movie Movie
	if err := json.NewDecoder(r.Body).Decode(&movie); err != nil {
		respondWithError(w, http.StatusBadRequest, "Invalid request payload")
		return
	}
	movie.ID = bson.NewObjectId()
	if err := dao.Create(movie); err != nil {
		respondWithError(w, http.StatusInternalServerError, err.Error())
		return
	}
	respondWithJson(w, http.StatusCreated, movie)
}

func Update(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	params := mux.Vars(r)
	var movie Movie
	if err := json.NewDecoder(r.Body).Decode(&movie); err != nil {
		respondWithError(w, http.StatusBadRequest, "Invalid request payload")
		return
	}
	if err := dao.Update(params["id"], movie); err != nil {
		respondWithError(w, http.StatusInternalServerError, err.Error())
		return
	}
	respondWithJson(w, http.StatusOK, map[string]string{"result": movie.Name + " atualizado com sucesso!"})
}

func Delete(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	params := mux.Vars(r)
	if err := dao.Delete(params["id"]); err != nil {
		respondWithError(w, http.StatusInternalServerError, err.Error())
		return
	}
	respondWithJson(w, http.StatusOK, map[string]string{"result": "success"})
}

Pensando que o nosso projeto pode ter mais de uma rota, eu separei elas dentro da pasta router, assim conseguimos ter um código mais organizado.

  • 15 à 17: estamos criando um método que pode ser chamado no caso de algum erro
  • 19 à 23: estamos criando um response para retornos com sucesso, note que estamos setando no header para que ele retorne um json
  • 26 à 88: estamos buscando os valores na nossa dao, validando e em caso de sucesso ou erro. Para isso nós utilizamos os métodos: respondWithJson e respondWithError.

config.toml

server="localhost"
database="movies_db"

Dados de conexão do nosso banco de dados.

main.go

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/gorilla/mux"
	. "github.com/programadriano/go-restapi/config"
	. "github.com/programadriano/go-restapi/config/dao"
	movierouter "github.com/programadriano/go-restapi/router"
)

var dao = MoviesDAO{}
var config = Config{}

func init() {
	config.Read()

	dao.Server = config.Server
	dao.Database = config.Database
	dao.Connect()
}

func main() {
	r := mux.NewRouter()
	r.HandleFunc("/api/v1/movies", movierouter.GetAll).Methods("GET")
	r.HandleFunc("/api/v1/movies/{id}", movierouter.GetByID).Methods("GET")
	r.HandleFunc("/api/v1/movies", movierouter.Create).Methods("POST")
	r.HandleFunc("/api/v1/movies/{id}", movierouter.Update).Methods("PUT")
	r.HandleFunc("/api/v1/movies/{id}", movierouter.Delete).Methods("DELETE")

	var port = ":3000"
	fmt.Println("Server running in port:", port)
	log.Fatal(http.ListenAndServe(port, r))
}

Esse é o pacote principal do nosso projeto. Nele, nós temos:

  • 17 a 23: estamos inicializando e estabilizando a conexão com o nosso banco de dados
  • 25 a 36: estamos informando as rotas e a porta que o nosso projeto irá utilizar

Testando o projeto

Para validar o seu projeto, execute o comando go run main.go no seu terminal, em seguida abra o endereço http://localhost:3000/api/v1/movies no seu navegador.

Agora, para testarmos o nosso CRUD, eu irei utilizar o Postman, uma ferramenta gratuita que nos auxilia nos testes a requisições HTTP.

Com ele aberto, preencha os campos conforme está na imagem abaixo e clique em enviar.

Create

GoLang POST

Objeto utilizado:

{
  "name": "The Equalizer",
  "description": "Robert McCall (Denzel Washington), a man of mysterious origin who believes he has put the past behind him, dedicates himself to creating a quiet new life. However, when he meets Teri (Chloë Grace Moretz), a teenager who has been manhandled by violent Russian mobsters, he simply cannot walk away. With his set of formidable skills, McCall comes out of self-imposed retirement and emerges as an avenging angel, ready to take down anyone who brutalizes the helpless.",
  "thumb_image": "http://t2.gstatic.com/images?q=tbn:ANd9GcQkGfxoavBEp4fR6P-yi2mIkUl1aZHHFIietLK4GriI5YyvGSJ7",
  "active": true
}

Resultado:

resultado post

GetAll

Agora altere no seu Postman para Get. Abaixo você tem uma imagem com o resultado desse passo:

Go buscando todos registros em um banco mongoDB

GetByID

Para retornar um resultado pelo seu ID, basta informar o Id que deseja buscar e clicar em enviar.

Resultado:

Go buscando pelo ID

Update

Para demonstrar o update, atualize o seu post com os dados abaixo:

{
	"id":"5b8ab0132e9a2421907d5669",
  "name": "The Equalizer 2",
  "description": "If you have a problem and there is nowhere else to turn, the mysterious and elusive Robert McCall will deliver the vigilante justice you seek. This time, however, McCall's past cuts especially close to home when thugs kill Susan Plummer -- his best friend and former colleague. Now out for revenge, McCall must take on a crew of highly trained assassins who'll stop at nothing to destroy him.",
  "thumb_image": "http://t1.gstatic.com/images?q=tbn:ANd9GcRCss5jFvU87fla4jEIwpv9dUAdZKzeUYHY_mhqzDxrvX9ppWyJ",
  "active": true
}

Agora atualize informe o Id que deseja atualizar e clique em enviar novamente.

Resultado:

Go atualizando model

Delete

Para finalizar, vamos deletar o nosso registro. Para isso, basta alterar para Delete no seu Postman e enviar novamente. Abaixo você tem uma imagem demonstrando esse passo:

Go deletando registro em um banco de dados mongoDB

Com isso eu finalizo mais esse artigo. Espero que tenham gostado e até a próxima, pessoal!