DEV Community

Ramu Mangalarapu
Ramu Mangalarapu

Posted on

Simple REST API in Golang on Contact (non persistent and no authentication)

Hi,

Today, let us discuss how to build simple non persistent (no database connection) and no authentication required REST API in Golang.

Note: This is for totally beginners who wants to learn how to build REST APIs in Golang.

Building a web application is same in almost all the backend programming languages.

We usually look for web frameworks if we want to build REST APIs, in case of Golang, we have
echo,
gin,
fibre, etc.

we can also build applications directly without using any frameworks (violates the DRY principal).

Usually framework supports all the required components to build application such as routing, famous wire format parsers, middlewares, data base connectors, http clients and etc..

As we know REST APIs are nothing but just collection of resources. Here our resource is Contact, which can have fields like name, phone number, alternate phone number and email.

We are going to perform HTTP Get, Post, Put and Delete on this Contact resource. Here in the API we are not using any web framework but we are using 3rd party router library.


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))
}


Enter fullscreen mode Exit fullscreen mode

Total code link as Gist:
https://gist.github.com/developer1622/80820a946bd7986a5464e52eb098e7d5

In case if we want to test this APIs, please import the collection from this link and play around with it in postman client:

https://gist.github.com/developer1622/025eae8ade628ffc8a5d5238371958ad

I wish you all the best in your life.

I will keep sharing my knowledge about Golang in coming articles.
Thank you.

Top comments (0)