RESTful API Development with Go

  FOSSCON India 2019

Jyotsna Gupta

  • Software Developer at CoffeeBeans Consulting
     
  • Mozilla TechSpeaker

 

Agenda

  • APIs
  • Intro to REST
  • Creating Endpoints
  • HTTP Methods
  • Securing API
  • Writing Tests
  • Best Practices
Image Source: www.calhoun.io/

@imJenal

APIs

@imJenal

  • Representational State Transfer
  • Streamlined and lightweight web service
  • Core Principles: Performance, scalability, simplicity, portability

REST, RESTful

@imJenal

REST
CLIENT

REST
SERVER

Why REST in Go?

@imJenal

Because of Go

Image Source: www.uihere.com

Let's Go

Image Source: www.deviantart.com/

@imJenal

Basic Example

@imJenal

package main

import (
    "net/http"
)


func main() {
   http.ListenAndServe(":3000", nil)
}

main.go

Gorilla Mux

@imJenal

  • HTTP request multiplexer
     
  • A powerful URL router and dispatcher
     
  • matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL
     
import "github.com/gorilla/mux"

Router

@imJenal

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "github.com/gorilla/mux"
)

func main() {
    router := mux.NewRouter()
    router.HandleFunc("/", HomeHandler)
    router.HandleFunc("/movie/{id}", MovieHandler)
    log.Fatal(http.ListenAndServe(":3000", router))
}
main.go

HTTP Methods

@imJenal

C R U D

Create

Read

Update

Delete

GET

PUT

POST

DELETE

package main

import (
	"fmt"
	"log"
	"net/http"
	"github.com/gorilla/mux"
)

func AllMoviesEndPoint(w http.ResponseWriter, r *http.Request) {}
func FindMovieEndpoint(w http.ResponseWriter, r *http.Request) {}
func CreateMovieEndPoint(w http.ResponseWriter, r *http.Request) {}
func UpdateMovieEndPoint(w http.ResponseWriter, r *http.Request) {}
func DeleteMovieEndPoint(w http.ResponseWriter, r *http.Request) {}

func main() {
	router := mux.NewRouter()
	router.HandleFunc("/movies", AllMoviesEndPoint).Methods("GET")
	router.HandleFunc("/movies", CreateMovieEndPoint).Methods("POST")
	router.HandleFunc("/movies", UpdateMovieEndPoint).Methods("PUT")
	router.HandleFunc("/movies", DeleteMovieEndPoint).Methods("DELETE")
	router.HandleFunc("/movies/{id}", FindMovieEndpoint).Methods("GET")
	log.Fatal(http.ListenAndServe(":3000",router))
}

@imJenal

@imJenal

Securing API

func main() {
    router := mux.NewRouter()
    allowedHeaders := handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type", "Authorization"})
    
    allowedMethods := handlers.AllowedMethods([]string{"GET", "POST", "PUT", "HEAD", "OPTIONS"})
    
    allowedOrigins := handlers.AllowedOrigins([]string{*})
    
    log.Fatal(http.ListenAndServe(":3000", handlers.CORS(allowedHeaders, allowedMethods, allowedOrigins)(router)))
}

CORS

Writing Tests

@imJenal

package main

func Add(value1 int, value2 int) int {
    return value1 + value2
}

func main() { }
main.go

@imJenal

package main

import (
    "testing"
    "github.com/stretchr/testify/assert"
)

func TestAdd(t *testing.T) {
    total := Add(1, 3)
    assert.NotNil(t, total, "The `total` should not be `nil`")
    assert.Equal(t, 4, total, "Expecting `4`")
}

main_test.go

Test endpoints

@imJenal


func TestEndpointGET(t *testing.T) {
    request, _ := http.NewRequest("GET", "/create", nil)
    response := httptest.NewRecorder()
    router := mux.NewRouter()
    router.Handle("/create", EndpointGET).Methods("GET")
    router.ServeHTTP(response, request)
    assert.Equal(t, 200, response.Code, "OK response is expected")
}

Endpoint with GET

@imJenal

func TestEndpointPOST(t *testing.T) {
    person := &Person{
        Firstname: "Nic",
        Lastname: "Raboy"
    }
    jsonPerson, _ := json.Marshal(person)
    request, _ := http.NewRequest("POST", "/create", bytes.NewBuffer(jsonPerson))
    response := httptest.NewRecorder()
    router := mux.NewRouter()
    router.Handle("/create", EndpointPOST).Methods("POST")
    router.ServeHTTP(response, request)
    assert.Equal(t, 200, response.Code, "OK response is expected")
}

Endpoint with POST

@imJenal

Best Practices

@imJenal

Abstract vs Concrete APIs

For the REST APIs,
the concrete is better than abstract

/entities

  • /owners
  • /blogs
  •  /blogposts

Good URL

  • GET /movies – gets all the movies
  • GET /movies/12 – gets the movies with the id 12
  • POST /movies – adds a new movie and returns the details
  • DELETE /movies/12 – removes the movies with the id 12
  • GET /actor/3/movies – gets all the movies of the actor with id 3

Error Handling

  • Request: GET https://api.twitter.com/1.1/account/settings.json
     
  • Response: Status Code 400
{"errors":[{"code":215,"message":""Bad Authentication data"}]}

Status Codes

  • 200 OK
  • 400 Bad Request
  • 500 Internal Server Error
  • 404 Not Found

REST API Versioning

https://api.example.com/v1/authors/2/movies/13

Documentation

@imJenal

Slides: https://slides.com/jenal/fosscon-go2019 

RESTful API Development using Go

By Jyotsna Gupta

RESTful API Development using Go

FOSSCON India | August 30, 2019

  • 747