DEV Community

Cover image for RestAPI Blog Post
AdrnlnJnky
AdrnlnJnky

Posted on

RestAPI Blog Post

In this Post I am going to talk about building a Rest API with a CRUD
interface. Over the next few blog posts I will build this out with several
features including an inventory of contacts and services, a contact list for
users, a secure log in and an interface to work with all of it. I am not going
to talk about installing Go or how packages work. I may do some of these types
of blog posts in the future but for now if you don't know the very basics with
go I recommend you go get Go installed and write a Hello World, then come
back. That said, I'm going dive straight into building the program.

Over the next few posts I am going to build this project that will have.

@. CRUD interface
@. Contacts
@. Users w/secure log in
@. contact Inventory
@. Services Inventory
@. Postgresql database for persistence
@. Some other stuff I have not thought of or decided on

In this post we are going to create the CRUD interface. In other words we are
going to write some code to add, change, look at, and delete things in our
database.

C R U D
create review update delete

I started with an outline. My outline is going to take several forms. This
first outline might seem rough but it's where I started. I figured there are
several ways to go about this. I could just build a main function and start
building out features and extract as I go. Another option is to create an
outline of the project and then build out features based on the outline. Here is
my first outline.

@. contacts/services/contacts/users -> all need a CRUD
-> I think I'll us gorilla/mux for this section.
@. review
@. find all contacts
@. find one contact
@. create
@. check that it does not exists
@. create the contact
@. update
@. check that it does exist
@. change one field
@. change several fields
@. delete
@. Check that it does exist
@. delete it
@. server
@. go http package is pretty cool
@. routers
@. gotta find the stuff
@. database
@. I think I'm going to use GORM for this

With at least an idea of structure set up I created a main.go and three folders:
users, dbase, contacts. I'm going to start with contacts, so I created a
contacts.go file in the contacts folder. I started out with some basic code; this is
in package contacts. To get this thing going I'm going to make my contact
struct very simple. We will come back and flush this out in a bit.

package contacts

import (
    "encoding/json"
    "fmt"
    "net/http"

    "github.com/gorilla/mux"
)

type Contact struct {
    Name   string
  E-mail string
  Phone  string
}

Enter fullscreen mode Exit fullscreen mode

Next I'm going to build my transactions for the Contacts. Those are find,
update, create and delete. First is the find:

(Oh by the way I'm using NVIM and at this point I recorded a register. My regerster will give me back (w http.ResponseWriter, r
*http.Request) {
, because it's going to save me a bunch of typing.

I'm going to use j for this register. So in vim from the normal mode I set my
curser on the last letter of a function and I typed:

qja(w http.ResponseWriter, r *http.Request) {}kq
I now have that whole thing in my j register which I reach in normal mode by
typeing @j.


func Getcontacts(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(contacts)
}
Enter fullscreen mode Exit fullscreen mode

I want to use json to pass the information around so the first thing I'm going
to do is set the header type to json. Then I just encode everything stored in my
contacts variable and send it back. Later we will connect this to a database
but for now I want to keep it simple and get it working.

Before I get to far ahead of myself I'm going to build the rest of my functions
to just return a simple message, then I'll set up our main function and see that
we are making our connections.

This is a pretty simple main function that will allow me to test my endpoints.
You could use something like Postman to test this or launch a server locally and then curl localhost:5445. This should return 'Home Page', but I get ahead of myself. Let me show you my main.go:

package main

import (
    "log"
    "net/http"

    "github.com/gorilla/mux"

    "gitlab.com/adrnlnjnky/RestAPI/contacts"
    "gitlab.com/adrnlnjnky/RestAPI/users"
)
Enter fullscreen mode Exit fullscreen mode

That stuffs important but hopefully we don't need to talk about it now. If we
do then head to (golang)[http://www.golang.org].

func main() {
    contacts.contacts = append(contacts.contacts, contacts.contact{ID: 1, Name: "CoCo Puffer Pants", Phone: 23.00, Email: "good@stuff"})
    contacts.contacts = append(contacts.contacts, contacts.contact{ID: 2, Name: "Lucky Dog", Phone: 3.00, Desc: "the@stuff"})
    contacts.contacts = append(contacts.contacts, contacts.contact{ID: 3, Name: "Dorin Dog", Phone: 300.00, Desc: "wool@stuff"})
    contacts.contacts = append(contacts.contacts, contacts.contact{ID: 4, Name: "Thorin Dog", Phone: 233.00, Desc: "dw@stuff"})
Enter fullscreen mode Exit fullscreen mode

Right up top I set up my in memory inventory so I have something to work and
test with.

    port := ":5445"
    r := mux.NewRouter()

    r.HandleFunc("/contacts", contacts.Getcontacts).Methods("GET")
    r.HandleFunc("/contacts/{name}", contacts.Getcontact).Methods("GET")
    r.HandleFunc("/contacts", contacts.Createcontact).Methods("POST")
    r.HandleFunc("/contacts/{name}", contacts.Updatecontact).Methods("PUT")
    r.HandleFunc("/contacts/{name}", contacts.Deletecontact).Methods("DELETE")

    if err := http.ListenAndServe(port, r); err != nil {
        log.Fatalf("could not listen on port %v. Error: %v", port, err)
    }
}
Enter fullscreen mode Exit fullscreen mode

Setting up some variables first to make things easier to read, then I use
Handlefunc to look for the contacts key word and then also look for the method
using the Methods option from gorilla/mux to determine the
GET/POST/PUT/DELETE options. Finally at the bottom I use ListenAndServe from
the net/http package to listen on the port variable and serve up the goods.

Now I can build the package and test the server. Right now all of my end points
simply return the list of contacts. Cool, things are working, Now to make them
work correctly.

func Getcontact(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    par := mux.Vars(r) // this sweet little bit gets the parameters from the call
    for _, target := range contacts {
        if target.Name == par["name"] {
            json.NewEncoder(w).Encode(target)
            return
        }
    }
    json.NewEncoder(w).Encode(&contact{})
}
Enter fullscreen mode Exit fullscreen mode

Getcontact needs to return only one contact not all of them so we need to do
some filtering. Gorilla/mux gives an easy way to extract the name. I pull that name and save it to par (parameters) and then I range over the contacts and find a match if it exists. I think the code is
pretty straight forward here.

My next feature is going to be CREATE.

func Createcontact(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    var contact contact
    _ = json.NewDecoder(r.Body).Decode(&contact)
    contacts = append(contacts, contact)
    json.NewEncoder(w).Encode(contact)
}

Enter fullscreen mode Exit fullscreen mode

Once the header business is handled I create a variable to hold the new contact,
append the Contacts list and then encode the information and send it back.

Now for Delete:

func Deletecontact(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    par := mux.Vars(r)
    for dex, target := range contacts {
        if target.Name == par["name"] {
            contacts = append(contacts[:dex], contacts[dex+1:]...)
            break
        }
    }
    json.NewEncoder(w).Encode(contacts)
}

Enter fullscreen mode Exit fullscreen mode

I'm using the par variable again to extract the desired contact to delete. I'm
ranging over the inventory again to find a match. Once the contact is located I
rebuild my contact variable with everything before and after the target contact,
deleting the contact from the inventory.

Update is going to use the same tricks as delete to remove the old data and then
add the contact back in with the new data.


func Updatecontact(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    par := mux.Vars(r)
    for dex, target := range contacts {
        if target.Name == par["name"] {
            contacts = append(contacts[:dex], contacts[dex+1:]...)
            var contact contact
            _ = json.NewDecoder(r.Body).Decode(&contact)
            contact.Name = par["name"]
            contacts = append(contacts, contact)
            json.NewEncoder(w).Encode(contact)
            return
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Like I said I used the functionality from delete to remove the contact to be
appended. I then the create funtionality to appended the inventory with the
corrected information.

To put this all together I have my project folder with:

go.mod
go.sum
main.go
contacts/contacts.go
README.md
dbase/
users/

I have created a few in memory contacts, set a variable for the port that the program will work on and a router variable.

Then I use HandleFunc to route the requests to the correct functions, and then set the program to listen and serve back data on the given port.

For the CRUD functions we take advantage of the gorilla/mux module and qualify contacts by name. In my next article I will set up some testing to cover the logic I've built so far. Once my tests are in place I will turn my attention
to making the data persist. Thank you for taking the time to read this post, come on back and see where it leads.

Tom Peltier
Smile it might hurt!

Top comments (0)