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
- 846