-
-
Save gilly7/30b53d7308da54ab9e53dac566aa2628 to your computer and use it in GitHub Desktop.
Basic REST API in Golang (Simple non persistent CRUD App on Contact)
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 ( | |
"encoding/json" | |
"fmt" | |
"log" | |
"net/http" | |
"github.com/julienschmidt/httprouter" | |
) | |
// Contact hold the contact resource collection. | |
// It is the basic building of this service. | |
type Contact struct { | |
Name string `json:"name"` | |
PhoneNumber string `json:"phone_number"` | |
AlternatePhoneNumber string `json:"alternate_phone_number"` | |
Email string `json:"email"` | |
} | |
// Contacts holds the list of contacts. | |
type Contacts []Contact | |
// Response useful to return unified result to client. | |
type Response struct { | |
Status string `json:"status"` | |
StatusCode int `json:"status_code"` | |
Error string `json:"error"` | |
Data map[string]interface{} `json:"data"` | |
} | |
// Note: To make things simple, we are using in memory data base here. | |
var contacts Contacts = []Contact{ | |
{"Pankaj", "12345", "6789", "pankaj@example.com"}, | |
{"Suraj", "12345", "6789", "suraj@example.com"}, | |
{"Anuj", "12345", "6789", "anuj@example.com"}, | |
{"Raj", "12345", "6789", "raj@example.com"}, | |
} | |
// Begineers understanding about client server communication about REST APIs: | |
// | |
// Usually developers builds Swagger (can be other thing also) API documentation (it tells about | |
// what is the URL consists of, payload and expected response, error codes, any auth mechanism enforces, different errors). | |
// | |
// Usual flow in building "REST APIs". | |
// In handler code, we receive (access to) req object along with URL params, query string | |
// and facility to write to response. | |
// We extract the required path params, query strings and request body (or we can call request payload). | |
// We usually receive in data interchange format like JSON, YML or etc.. | |
// We then deserialize to our native Golang data types. We validate, santize the data. | |
// If the data does not match with our validations, we throw 400 (client error). | |
// Then based on the business usecase either we store in the database (like all Read, Write, Update and Delete operations) | |
// or we call another REST APIs, we even do some computation with the data in return confirmed data interchange format. | |
// More in coming posts. | |
// Index is the starting endpoint of our contacts API. | |
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { | |
log.Println("I am in Index") // happiness of developer is proportional to the number logs in code, so always log things | |
writeResponse(w, "Success", http.StatusOK, "no error", map[string]interface{}{"key": "Someone doing same thing in another parallel universe"}) | |
} | |
// GetAllContacts fetches all the contact list. | |
func GetAllContacts(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { | |
log.Println("I am in GetAllContacts") | |
writeResponse(w, "Success", http.StatusOK, "no error", map[string]interface{}{"result": contacts}) | |
} | |
// GetContact fetchs contact by email. | |
func GetContact(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { | |
log.Println("I am in GetContact") | |
// let's extract the path param | |
email := ps.ByName("email") | |
found, index := fetchContactIndexByEmail(email) | |
if found { | |
writeResponse(w, "Success", http.StatusOK, "no error", map[string]interface{}{"result": contacts[index]}) | |
return | |
} | |
writeResponse(w, "Success", http.StatusOK, "no error", map[string]interface{}{"result": "no data found with the email address"}) | |
} | |
// CreateContact creates the new contact. | |
func CreateContact(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { | |
log.Println("I am in CreateContact") | |
c, err := readContact(r) | |
if err != nil { | |
writeResponse(w, "Failure", http.StatusOK, fmt.Errorf("invalid JSON: %v", err).Error(), nil) | |
return | |
} | |
// let us add to contacts list | |
contacts = append(contacts, c) | |
writeResponse(w, "Success", http.StatusCreated, "no error", map[string]interface{}{"result": c}) | |
} | |
// UpdateContact updates the contact if there is already one or it creates new one if | |
// none exists with the given email address. | |
func UpdateContact(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { | |
log.Println("I am in UpdateContact") | |
c, err := readContact(r) | |
if err != nil { | |
writeResponse(w, "Failure", http.StatusOK, fmt.Errorf("invalid JSON: %v", err).Error(), nil) | |
return | |
} | |
found, index := fetchContactIndexByEmail(c.Email) | |
if !found { | |
// we did not find the contact, so, let's create new one | |
contacts = append(contacts, c) | |
writeResponse(w, "Success", http.StatusCreated, "no error", map[string]interface{}{"result": c}) | |
return | |
} | |
// contact we want to update | |
contacts[index].Name = c.Name | |
contacts[index].Email = c.Email | |
contacts[index].PhoneNumber = c.PhoneNumber | |
contacts[index].AlternatePhoneNumber = c.AlternatePhoneNumber | |
writeResponse(w, "Success", http.StatusOK, "no error", map[string]interface{}{"result": c}) | |
} | |
// DeleteContact deletes contact by email. | |
func DeleteContact(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { | |
log.Println("I am in DeleteContact") | |
// let's extract the path param | |
email := ps.ByName("email") | |
found, index := fetchContactIndexByEmail(email) | |
// let's delete the contact by the index | |
contacts = removeContactByIndex(contacts, index) | |
if found { | |
writeResponse(w, "Success", http.StatusOK, "no error", map[string]interface{}{"result": "Successfully deleted the contact"}) | |
return | |
} | |
writeResponse(w, "Success", http.StatusOK, "no error", map[string]interface{}{"result": "no data found with the email address"}) | |
} | |
// writeResponse is common utility to write response back to the clients. | |
func writeResponse(res http.ResponseWriter, status string, statusCode int, err string, data map[string]interface{}) error { | |
res.Header().Set("Content-Type", "application/json") | |
return json.NewEncoder(res).Encode(Response{ | |
Status: status, | |
StatusCode: statusCode, | |
Error: err, | |
Data: data, | |
}) | |
} | |
// removeContactByIndex removes the contact and updates the contact list. | |
func removeContactByIndex(cs Contacts, index int) Contacts { | |
return append(cs[:index], cs[index+1:]...) | |
} | |
// fetchContactIndexByEmail finds the contact index in list. | |
func fetchContactIndexByEmail(email string) (found bool, index int) { | |
for i := 0; i < len(contacts); i++ { | |
if contacts[i].Email == email { | |
found = true | |
index = i | |
} | |
} | |
return | |
} | |
// readContact is a utility for creating and updating contact. | |
func readContact(r *http.Request) (Contact, error) { | |
var c Contact | |
if err := json.NewDecoder(r.Body).Decode(&c); err != nil { | |
return Contact{}, err | |
} | |
return c, nil | |
} | |
func main() { | |
// like all the backend programming languages, Go also has router | |
// her we are using 3rd party router. | |
router := httprouter.New() | |
// supported endpoints | |
// names are self explanatory | |
router.GET("/", Index) | |
router.GET("/contacts", GetAllContacts) | |
router.GET("/contacts/:email", GetContact) | |
router.POST("/contacts", CreateContact) | |
router.PUT("/contacts", UpdateContact) | |
router.DELETE("/contacts/:email", DeleteContact) | |
// let's start the server and wrap in Fatal routine. | |
log.Println("Starting the server at :8080 port") | |
log.Fatal(http.ListenAndServe(":8080", router)) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment