Build Your First REST API with Go

@imJenal

Hi! I'm Jyotsna

Agenda

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

@imJenal

Application Programming Interface

API

@imJenal

@imJenal

@imJenal

  • REpresentational State Transfer
     
  • Streamlined and lightweight web service
     
  • Core Principles: Performance, scalability, simplicity, portability
     
  • REST is an architectural style, or design pattern, for APIs.

REST, RESTful

@imJenal

REST
CLIENT

REST
SERVER

@imJenal

Why REST in Go?

@imJenal

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

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 Endpoints

JWT

@imJenal

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

@imJenal

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, "" 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

@imJenal

Bad URL

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

@imJenal

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

@imJenal

Error Handling

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

@imJenal

Status Codes

  • 200 OK : Everything is working
     
  • 201 OK : New resource has been created
     
  • 400 Bad Request: Request can't be served
     
  • 404 Not Found : No resource behind the URI
     
  • 500 Internal Server Error

@imJenal

REST API Versioning

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

@imJenal

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

Documentation

@imJenal

@ imJenal 

@ imJenal 

https://slides.com/jenal/guvi

Build Your First REST API

By Jyotsna Gupta

Build Your First REST API

Guvi | March 15, 2021

  • 816