Last active
May 31, 2023 08:42
-
-
Save juanpabloaj/c024b89f8be5bc8f3d01fde9a9acbad9 to your computer and use it in GitHub Desktop.
go api rest example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
*.db | |
api_rest |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"database/sql" | |
"encoding/json" | |
"log" | |
"net/http" | |
"strconv" | |
"github.com/gorilla/mux" | |
_ "github.com/mattn/go-sqlite3" | |
) | |
func respondWithError(w http.ResponseWriter, code int, message string) { | |
respondWithJSON(w, code, map[string]string{"error": message}) | |
} | |
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) | |
} | |
type App struct { | |
Router *mux.Router | |
DB *sql.DB | |
} | |
func (a *App) getUser(w http.ResponseWriter, r *http.Request) { | |
vars := mux.Vars(r) | |
id, err := strconv.Atoi(vars["id"]) | |
if err != nil { | |
respondWithError(w, http.StatusBadRequest, "Invalid user ID") | |
return | |
} | |
u := user{ID: id} | |
if err := u.getUser(a.DB); err != nil { | |
switch err { | |
case sql.ErrNoRows: | |
respondWithError(w, http.StatusNotFound, "User not found") | |
default: | |
respondWithError(w, http.StatusInternalServerError, err.Error()) | |
} | |
return | |
} | |
respondWithJSON(w, http.StatusOK, u) | |
} | |
func (a *App) getUsers(w http.ResponseWriter, r *http.Request) { | |
count, _ := strconv.Atoi(r.FormValue("count")) | |
start, _ := strconv.Atoi(r.FormValue("start")) | |
if count > 10 || count < 1 { | |
count = 10 | |
} | |
if start < 10 { | |
start = 0 | |
} | |
users, err := getUsers(a.DB, start, count) | |
if err != nil { | |
respondWithError(w, http.StatusInternalServerError, err.Error()) | |
return | |
} | |
respondWithJSON(w, http.StatusOK, users) | |
} | |
func (a *App) createUser(w http.ResponseWriter, r *http.Request) { | |
var u user | |
decoder := json.NewDecoder(r.Body) | |
if err := decoder.Decode(&u); err != nil { | |
respondWithError(w, http.StatusBadRequest, "Invalid request payload") | |
return | |
} | |
defer r.Body.Close() | |
if err := u.createUser(a.DB); err != nil { | |
respondWithError(w, http.StatusInternalServerError, err.Error()) | |
return | |
} | |
respondWithJSON(w, http.StatusCreated, u) | |
} | |
func (a *App) updateUser(w http.ResponseWriter, r *http.Request) { | |
vars := mux.Vars(r) | |
id, err := strconv.Atoi(vars["id"]) | |
if err != nil { | |
respondWithError(w, http.StatusBadRequest, "Invalid user ID") | |
return | |
} | |
var u user | |
decoder := json.NewDecoder(r.Body) | |
if err := decoder.Decode(&u); err != nil { | |
respondWithError(w, http.StatusBadRequest, "Invalid request payload") | |
return | |
} | |
defer r.Body.Close() | |
u.ID = id | |
if err := u.updateUser(a.DB); err != nil { | |
respondWithError(w, http.StatusInternalServerError, err.Error()) | |
return | |
} | |
respondWithJSON(w, http.StatusOK, u) | |
} | |
func (a *App) deleteUser(w http.ResponseWriter, r *http.Request) { | |
vars := mux.Vars(r) | |
id, err := strconv.Atoi(vars["id"]) | |
if err != nil { | |
respondWithError(w, http.StatusBadRequest, "Invalid User ID") | |
return | |
} | |
u := user{ID: id} | |
if err := u.deleteUser(a.DB); err != nil { | |
respondWithError(w, http.StatusInternalServerError, err.Error()) | |
return | |
} | |
respondWithJSON(w, http.StatusOK, map[string]string{"result": "success"}) | |
} | |
func (a *App) Initialize(user, password, dbname string) { | |
var err error | |
a.DB, err = sql.Open("sqlite3", "./app.db") | |
if err != nil { | |
log.Fatal(err) | |
} | |
a.Router = mux.NewRouter() | |
a.initializeRouters() | |
} | |
func (a *App) initializeRouters() { | |
a.Router.HandleFunc("/users", a.getUsers).Methods("GET") | |
a.Router.HandleFunc("/user", a.createUser).Methods("POST") | |
a.Router.HandleFunc("/user/{id:[0-9]+}", a.getUser).Methods("GET") | |
a.Router.HandleFunc("/user/{id:[0-9]+}", a.updateUser).Methods("PUT") | |
a.Router.HandleFunc("/user/{id:[0-9]+}", a.deleteUser).Methods("DELETE") | |
} | |
func (a *App) Run(addr string) { | |
log.Fatal(http.ListenAndServe(addr, a.Router)) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
func main() { | |
a := App{} | |
a.Initialize("BD_USERNAME", "DB_PASSWORD", "rest_api_example") | |
a.Run(":8080") | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bytes" | |
"encoding/json" | |
"fmt" | |
"log" | |
"net/http" | |
"net/http/httptest" | |
"os" | |
"strconv" | |
"testing" | |
) | |
var a App | |
func TestMain(m *testing.M) { | |
a = App{} | |
a.Initialize("BD_USERNAME", "DB_PASSWORD", "rest_api_example") | |
ensureTableExists() | |
code := m.Run() | |
clearTable() | |
os.Exit(code) | |
} | |
func ensureTableExists() { | |
if _, err := a.DB.Exec(tableCreationQuery); err != nil { | |
log.Fatal(err) | |
} | |
} | |
func clearTable() { | |
a.DB.Exec("DELETE FROM users") | |
a.DB.Exec("ALTER TABLE users AUTO_INCREMENT = 1") | |
} | |
const tableCreationQuery = ` | |
CREATE TABLE IF NOT EXISTS users | |
( | |
id INT AUTO_INCREMENT PRIMARY KEY, | |
name VARCHAR(50) NOT NULL, | |
age INT NOT NULL | |
)` | |
func executeRequest(req *http.Request) *httptest.ResponseRecorder { | |
rr := httptest.NewRecorder() | |
a.Router.ServeHTTP(rr, req) | |
return rr | |
} | |
func checkResponseCode(t *testing.T, expected, actual int) { | |
if expected != actual { | |
t.Errorf("Expected response code %d. Got %d\n", expected, actual) | |
} | |
} | |
func addUsers(count int) { | |
if count < 1 { | |
count = 1 | |
} | |
for i := 0; i < count; i++ { | |
statement := fmt.Sprintf("INSERT INTO users(name, age) VALUES('%s', %d)", ("User " + strconv.Itoa(i+1)), ((i + 1) * 10)) | |
a.DB.Exec(statement) | |
} | |
} | |
func TestEmptyTable(t *testing.T) { | |
clearTable() | |
req, _ := http.NewRequest("GET", "/users", nil) | |
response := executeRequest(req) | |
checkResponseCode(t, http.StatusOK, response.Code) | |
if body := response.Body.String(); body != "[]" { | |
t.Errorf("Expected an empty array. Got %s", body) | |
} | |
} | |
func TestGetNonExistentUser(t *testing.T) { | |
clearTable() | |
req, _ := http.NewRequest("GET", "/user/45", nil) | |
response := executeRequest(req) | |
checkResponseCode(t, http.StatusNotFound, response.Code) | |
var m map[string]string | |
json.Unmarshal(response.Body.Bytes(), &m) | |
if m["error"] != "User not found" { | |
t.Errorf("Expected the 'error' key of the response to be set to 'User not found'. Got '%s'", m["error"]) | |
} | |
} | |
func TestCreateUser(t *testing.T) { | |
clearTable() | |
payload := []byte(`{"name": "test user", "age": 30}`) | |
req, _ := http.NewRequest("POST", "/user", bytes.NewBuffer(payload)) | |
response := executeRequest(req) | |
checkResponseCode(t, http.StatusCreated, response.Code) | |
var m map[string]interface{} | |
json.Unmarshal(response.Body.Bytes(), &m) | |
if m["name"] != "test user" { | |
t.Errorf("Expected user name to be 'test user'. Got '%v'", m["name"]) | |
} | |
if m["age"] != 30.0 { | |
t.Errorf("Expected user name to be '30'. Got '%v'", m["age"]) | |
} | |
if m["id"] != 1.0 { | |
t.Errorf("Expected user ID to be '1'. Got '%v'", m["id"]) | |
} | |
} | |
func TestGetUser(t *testing.T) { | |
clearTable() | |
addUsers(1) | |
req, _ := http.NewRequest("GET", "/user/1", nil) | |
response := executeRequest(req) | |
checkResponseCode(t, http.StatusOK, response.Code) | |
} | |
func TestUpdateUser(t *testing.T) { | |
clearTable() | |
addUsers(1) | |
req, _ := http.NewRequest("GET", "/user/1", nil) | |
response := executeRequest(req) | |
var originalUser map[string]interface{} | |
json.Unmarshal(response.Body.Bytes(), &originalUser) | |
payload := []byte(`{"name": "test user - updated name", "age": 21}`) | |
req, _ = http.NewRequest("PUT", "/user/1", bytes.NewBuffer(payload)) | |
response = executeRequest(req) | |
checkResponseCode(t, http.StatusOK, response.Code) | |
var m map[string]interface{} | |
json.Unmarshal(response.Body.Bytes(), &m) | |
if m["id"] != originalUser["id"] { | |
t.Errorf("Expected the id to remain the same (%v). Got %v", originalUser["id"], m["id"]) | |
} | |
if m["name"] == originalUser["name"] { | |
t.Errorf("Expected the name to change from '%v' to '%v'. Got '%v'", originalUser["name"], m["name"], m["name"]) | |
} | |
if m["age"] == originalUser["age"] { | |
t.Errorf("Expected the age to change from '%v' to '%v'. Got '%v'", originalUser["age"], m["age"], m["age"]) | |
} | |
} | |
func TestDeleteUser(t *testing.T) { | |
clearTable() | |
addUsers(1) | |
req, _ := http.NewRequest("GET", "/user/1", nil) | |
response := executeRequest(req) | |
checkResponseCode(t, http.StatusOK, response.Code) | |
req, _ = http.NewRequest("DELETE", "/user/1", nil) | |
response = executeRequest(req) | |
checkResponseCode(t, http.StatusOK, response.Code) | |
req, _ = http.NewRequest("GET", "/user/1", nil) | |
checkResponseCode(t, http.StatusNotFound, response.Code) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
**/*.go { | |
prep: go test -v | |
} | |
**/*.go !**/*_test.go { | |
prep: go build | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"database/sql" | |
"fmt" | |
) | |
type user struct { | |
ID int `json:"id"` | |
Name string `json:"name"` | |
Age int `json:"age"` | |
} | |
func (u *user) getUser(db *sql.DB) error { | |
statement := fmt.Sprintf("SELECT name, age FROM users WHERE id=%d", u.ID) | |
return db.QueryRow(statement).Scan(&u.Name, &u.Age) | |
} | |
func (u *user) updateUser(db *sql.DB) error { | |
statement := fmt.Sprintf("UPDATE users SET name='%s', age=%d WHERE id=%d", u.Name, u.Age, u.ID) | |
_, err := db.Exec(statement) | |
return err | |
} | |
func (u *user) deleteUser(db *sql.DB) error { | |
statement := fmt.Sprintf("DELETE FROM users WHERE id=%d", u.ID) | |
_, err := db.Exec(statement) | |
return err | |
} | |
func (u *user) createUser(db *sql.DB) error { | |
statement := fmt.Sprintf("INSERT INTO users(name, age) VALUES('%s', %d)", u.Name, u.Age) | |
_, err := db.Exec(statement) | |
if err != nil { | |
return err | |
} | |
err = db.QueryRow("SELECT LAST_INSERT_ID()").Scan(&u.ID) | |
if err != nil { | |
return err | |
} | |
return nil | |
} | |
func getUsers(db *sql.DB, start, count int) ([]user, error) { | |
statement := fmt.Sprintf("SELECT id, name, age FROM users LIMIT %d OFFSET %d", count, start) | |
rows, err := db.Query(statement) | |
if err != nil { | |
return nil, err | |
} | |
defer rows.Close() | |
users := []user{} | |
for rows.Next() { | |
var u user | |
if err := rows.Scan(&u.ID, &u.Name, &u.Age); err != nil { | |
return nil, err | |
} | |
users = append(users, u) | |
} | |
return users, nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment