This post explain a little bit how to write a basic API with Go WITHOUT any framework.
Basic Structure
To write some Web API with Go, we use something like describe in Simple HTTP File Server post. But instead of using the File Handler, we are going to write the API handlers.
Like this:
package main
import (
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// do something
})
http.ListenAndServe(":5000", nil)
}
Request/Response Handler
We are basically saying to handle (http.HandleFunc
) request to path /
with a function and it need to be with the signature:
func(w http.ResponseWriter, r *http.Request) {
// do something
}
That is what all languages have when dealing with http request:
- response (
http.ResponseWriter
) - request (
http.Request
)
Handling Requests
Methods
To handle the different types of methods (e.g. GET
, POST
), we use the variable r *http.Request
and access the field Method
.
We can switch between all methods or handle only one
func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
// do something
}
}
func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
// do something
case http.MethodPost:
// do something
default:
// handle other
}
}
URL Params
Query
To retrieve a Query param:
func(w http.ResponseWriter, r *http.Request) {
fmt.Println(r.URL.Query().Get("param"))
}
Path
To retrierve a path param:
id := strings.TrimPrefix(r.URL.Path, "/path/")
The problem with that is to process subresources, Like /book/123/author
.
That end up with a more complex code which I am not to approach here. For that, use a web framework with a router.
Request Body
text/plain
To read request body
import "io/ioutil"
...
func(w http.ResponseWriter, r *http.Request) {
bodyBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
// handle read errors
}
fmt.Println(string(bodyBytes))
}
application/json
Let’s set a data structure and map JSON fields with golang struct tags and declare a value for it
type Request struct {
Name string `json:"name"`
Age int `json:"my_age"`
}
Now we read it and UnMarshal
it
func(w http.ResponseWriter, r *http.Request) {
bodyBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
// handle read errors
}
var req Request
err := json.Unmarshal(bodyBytes, &req)
if err != nil {
// handle unmarshal errors
}
fmt.Println(req)
}
Writing Responses
Status Code
func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
}
Content Type
func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json")
}
Response Body
text/plain
To write something to the response, we’re going to use the fmt
package as follows:
func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "something to format: %s", "a value")
}
application/json
Let’s set a data structure and map JSON fields with golang struct tags and declare a value for it
type Response struct {
Name string `json:"name"`
Age int `json:"my_age"`
AnotherField string
}
var response = Response{
Name: "John Doe",
Age: 25,
AnotherField: "some string",
}
Now we write to response writer.
We need to change the content type, transform the struct to a array of bytes and write the string to response:
func(w http.ResponseWriter, r *http.Request) {
// changing content type
w.Header().Add("Content-Type", "application/json")
// transforming to a array of bytes
bytes, err := json.Marshall(response)
if err != nil {
// handle marshalling errors
}
// writing
fmt.Fprint(w, string(bytes))
}
Then we got the reponse
{"name":"John Doe","my_age":25,"AnotherField":"some string"}
To pretty print that JSON, just change json.Marshal
to json.MarshalIndent
:
bytes, _ := json.MarshalIndent(response, "", " ")
The third parameter identify what to use as indentation, which is 2 spaces in this example.
{
"name": "John Doe",
"my_age": 25,
"AnotherField": "some string"
}
Wrapping Up
That is not even close to what we can do with Golang to write a API. The problem here is to write and write, over and over again the same thing.
Here comes the web frameworks. They provide a bunch of code structure to easy write an API, like routing, body parsers, error handling, static file serving, etc.
They are all alike, so choose one that fits better for you. From now on, I am going to choose one: Echo.