While learning Go I decided to implement a simple REST API to get to know the language and since I am inexperienced in Go I would appreciate any feedback on the code.
Please post any thoughts you have. They don't have to be constructive, but if you have some constructive feedback that will be great.
Q: In particular, I would like to ask the next questions: Are there any bad patterns? Is there something you would do another way? What could be improved? Do I violate any code style rules (say, type names, variables)? Is it ok to place all this code in one file?
Note: I used gorilla/mux and gorm here.
type student struct {
ID string `gorm:"primary_key" json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
type App struct {
DB *gorm.DB
}
func (a *App) start() {
db, err := gorm.Open(
"postgres",
"user=go password=go dbname=go sslmode=disable")
if err != nil {
panic(err.Error())
}
a.DB = db
db.AutoMigrate(&student{})
r := mux.NewRouter()
r.HandleFunc("/students", a.getAllStudents).Methods("GET")
r.HandleFunc("/students", a.addStudent).Methods("POST")
r.HandleFunc("/students/{id}", a.updateStudent).Methods("PUT")
r.HandleFunc("/students/{id}", a.deleteStudent).Methods("DELETE")
http.ListenAndServe(":8080", r)
}
func (a *App) getAllStudents(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var all []student
err := a.DB.Find(&all).Error
if err != nil {
sendErr(w, http.StatusInternalServerError, err.Error())
} else {
json.NewEncoder(w).Encode(all)
}
}
func (a *App) addStudent(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
s := student{}
err := json.NewDecoder(r.Body).Decode(&s)
if err != nil {
sendErr(w, http.StatusBadRequest, err.Error())
} else {
err = a.DB.Save(&s).Error
if err != nil {
sendErr(w, http.StatusInternalServerError, err.Error())
} else {
w.WriteHeader(http.StatusCreated)
}
}
}
func (a *App) updateStudent(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
s := student{}
err := json.NewDecoder(r.Body).Decode(&s)
if err != nil {
sendErr(w, http.StatusBadRequest, err.Error())
} else {
s.ID = mux.Vars(r)["id"]
err = a.DB.Save(&s).Error
if err != nil {
sendErr(w, http.StatusInternalServerError, err.Error())
}
}
}
func (a *App) deleteStudent(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
s := student{}
a.DB.First(&s, mux.Vars(r)["id"])
err := a.DB.Delete(s).Error
if err != nil {
sendErr(w, http.StatusInternalServerError, err.Error())
}
}
func sendErr(w http.ResponseWriter, code int, message string) {
response, _ := json.Marshal(map[string]string{"error": message})
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
w.Write(response)
}
func main() {
app := App{}
app.start()
}
Update: I've got some great feedback and I have an additional question related to that. In both the answers you suggest me to get rid of else
blocks / use an early return. Is it a common practice in Go? Is it because of error handling in Go which makes you create more if/else blocks? I am asking because in other languages (such as Java) it is considered a bad practice. The main reason is that it hides complexity. Every time you create a return inside an if block you make a the code more flat hiding the real complexity.