Skip to content

Instantly share code, notes, and snippets.

@gilly7
Forked from developer1622/main.go
Created February 6, 2022 10:20
Show Gist options
  • Save gilly7/30b53d7308da54ab9e53dac566aa2628 to your computer and use it in GitHub Desktop.
Save gilly7/30b53d7308da54ab9e53dac566aa2628 to your computer and use it in GitHub Desktop.
Basic REST API in Golang (Simple non persistent CRUD App on Contact)
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