Go: RESTful - um exemplo completo com persistência

De Aulas

Afluentes: Sistemas Distribuídos e Mobile

Base de Dados

Para nosso exemplo de CRUD, vamos precisar de um local para armazenar nossas informações de forma persistente. Para isso, criamos uma tabela chamada users no Postgres. Abaixo temos a clausula CREATE TABLE da nossa tabela.

create table users (
    id integer not null,
    name varchar(50) not null,
    email varchar(50) not null,
    primary key (id)
);

Veja que ela é bastante simples, mas já serve como exemplo. Temos os atributros id do tipo integer e dois atributos, name e email, do tipo string, ou melhor, VARCHAR. O id é usado como chave primária.

database.go

package main

import (
	"database/sql"
	"fmt"

	_ "github.com/lib/pq"
)

// Database class
type Database struct {
	connection *sql.DB
	dbtype     string
	info       string
	err        error
}

// NewDatabase Constructor
func NewDatabase(dbhost string, dbtype string, dbname string, dbuser string, dbpass string) *Database {
	db := new(Database)
	db.dbtype = dbtype
	db.info = fmt.Sprintf("user=%s password=%s dbname=%s sslmode=disable", dbuser, dbpass, dbname)
	return db
}

// Get return connection
func (db *Database) Get() *sql.DB {
	if db.connection == nil {
		db.connection, db.err = sql.Open(db.dbtype, db.info)
	}
	return db.connection
}

// Close function
func (db *Database) Close() {
	db.connection.Close()
}

user.go

package main

type User struct {
	Id    int    `json:"id"`
	Name  string `json:"name"`
	Email string `json:"email"`
}

func NewUser(id int, name string, email string) *User {
	user := new(User)
	user.Id = id
	user.Name = name
	user.Email = email
	return user
}

userdao.go

package main

import "fmt"

type UserDAO struct {
	db    *Database
	users []*User
}

func NewUserDAO(db *Database) *UserDAO {
	userdao := new(UserDAO)
	userdao.db = db
	userdao.users = make([]*User, 0)
	return userdao
}

func (userdao *UserDAO) GetAll() []*User {
	userdao.users = make([]*User, 0)
	db := userdao.db.Get()
	rows, err := db.Query("select * from users")
	if err != nil {
		fmt.Println(err.Error())
	}
	for rows.Next() {
		var id int
		var name string
		var email string
		rows.Scan(&id, &name, &email)
		userdao.users = append(userdao.users, NewUser(id, name, email))
	}
	return userdao.users
}

func (userdao *UserDAO) Insert(user *User) {
	db := userdao.db.Get()
	query := "insert into users (id, name, email) values ($1, $2, $3)"
	_, err := db.Query(query, user.Id, user.Name, user.Email)
	if err != nil {
		fmt.Println(err.Error())
	}
}

func (userdao *UserDAO) Get(id string) *User {
	db := userdao.db.Get()
	query := "select * from users where id=$1"
	rows, err := db.Query(query, id)
	if err != nil {
		fmt.Println(err.Error())
	}
	if rows.Next() {
		var id int
		var name string
		var email string
		rows.Scan(&id, &name, &email)
		return NewUser(id, name, email)
	}
	return nil
}

func (userdao *UserDAO) Update(user *User) string {
	db := userdao.db.Get()
	query := "update users set name=$2, email=$3 where id=$1"
	_, err := db.Query(query, user.Id, user.Name, user.Email)
	if err != nil {
		fmt.Println(err.Error())
		return err.Error()
	}
	return "ok"
}

func (userdao *UserDAO) Delete(id string) string {
	db := userdao.db.Get()
	query := "delete from users where id=$1"
	_, err := db.Query(query, id)
	if err != nil {
		fmt.Println(err.Error())
		return err.Error()
	}
	return "ok"
}

main.go

package main

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

func main() {
	db := NewDatabase("localhost", "postgres", "backendcrud", "saulo", "1234")
	defer db.Close()
	dao := NewUserDAO(db)

	router := http.NewServeMux()

	router.HandleFunc("POST /users", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json;charset=UTF-8")
		w.Header().Set("Access-Control-Allow-Origin", "*")
		var user User
		err := json.NewDecoder(r.Body).Decode(&user)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		dao.Insert(NewUser(user.Id, user.Name, user.Email))
	})

	router.HandleFunc("GET /users", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json;charset=UTF-8")
		w.Header().Set("Access-Control-Allow-Origin", "*")
		users := dao.GetAll()
		json.NewEncoder(w).Encode(users)
	})

	router.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json;charset=UTF-8")
		w.Header().Set("Access-Control-Allow-Origin", "*")
		id := r.PathValue("id")
		user := dao.Get(id)
		json.NewEncoder(w).Encode(user)
	})

	router.HandleFunc("PUT /users", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json;charset=UTF-8")
		w.Header().Set("Access-Control-Allow-Origin", "*")
		var user User
		err := json.NewDecoder(r.Body).Decode(&user)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		out := dao.Update(NewUser(user.Id, user.Name, user.Email))
		json.NewEncoder(w).Encode(out)
	})

	router.HandleFunc("DELETE /users/{id}", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json;charset=UTF-8")
		w.Header().Set("Access-Control-Allow-Origin", "*")
		id := r.PathValue("id")
		out := dao.Delete(id)
		json.NewEncoder(w).Encode(out)
	})

	// Vamos precisar do OPTIONS porque o PUT primeiro manda um OPTION
	// Assim, se for localhost, avisamos o CORS que permitimos local
	router.HandleFunc("OPTIONS /users", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json;charset=UTF-8")
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE")
		w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
	})

	// O DELETE também chama o OPTIONS, mas envia também uma informação
	router.HandleFunc("OPTIONS /users/{id}", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json;charset=UTF-8")
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE")
		w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")
	})

	err := http.ListenAndServe("localhost:8080", router)
	if err != nil {
		fmt.Println(err.Error())
	}
}