Create a fully functional API server in Go using only the standard library in under 5 minutes
4 min read
If this is not the first article you read about Golang (Go), no matter the author, you probably already know that Go is a programming language that was created to be simple, fast, and efficient. And it was a language born for the web, so it has a very powerful standard library for web development.
In Go you can, only using the standard libs, to create a web server, a web client, and even a proxy server. In other languages, such as Node.js (where part of my background is), you need third party libs such as express to create a web server, and axios to create a resilient web client. However, you don't need any third party libs to create a web server or a web client in Go.
Let's see some snippets that could help you to start prototyping your next API.
Handlers and Middlewares
Handlers are just functions that receive a http.ResponseWriter
and a *http.Request
as parameters. The http.ResponseWriter
is used to write the response to the client, and the *http.Request
is used to read the request from the client. We'll see more about these two types later.
// Standard HTTP handler function signature.
func myHandler(w http.ResponseWriter, r *http.Request) {
// Your code here
}
And what about middlewares? Middlewares are just functions that receive a http.Handler
as parameter and return a http.Handler
. To continue the request execution, you need to call the ServeHTTP
method of the http.Handler
that you received as parameter.
// Standard middleware function signature.
func myMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Your middleware code here
next.ServeHTTP(w, r) // Continue the request execution
})
}
http.Request
and http.ResponseWriter
Now, let's do a real quick overview of the http.Request
and http.ResponseWriter
types.
The http.Request
type is a struct that contains all the information about the request that the client made to the server. It contains the request method, the request URL, the request headers, the request body, and much more.
ip := r.RemoteAddr // Get the IP address that sent the request.
method := r.Method // Get the request method.
path := r.URL.Path // Get the request URL path.
id := r.URL.Query().Get("id") // Get the query string parameter "id".
accept := r.Header.Get("Accept") // Get the request "Accept" header.
body, err := ioutil.ReadAll(r.Body) // Read the request body into a []byte slice
And the http.ResponseWriter
type is an interface that allows us to write the response to the client. It contains methods to set the response headers, to set the response status code, and to write the response body.
// Remember: All headers must be set before calling w.WriteHeader() or w.Write().
w.Header().Set("Location", "/article/1")
w.WriteHeader(201)
w.Write([]byte("Created"))
There are also some shortcuts to write error responses.
http.NotFound(w, r)
http.Error(w, "Bad Request", 400)
// Or the same using constants
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
Some useful constants are:
http.MethodGet // "GET"
http.MethodPost // "POST"
http.StatusMovedPermanently // 301
http.StatusTemporaryRedirect // 307
http.StatusBadRequest // 400
http.StatusUnauthorized // 401
http.StatusForbidden // 403
http.StatusNotFound // 404
http.StatusMethodNotAllowed // 405
http.StatusInternalServerError // 500
Creating the server
Okay, now we know how to create handlers and middlewares, and how to read requests and write responses. Let's see how to create a server.
First, we need to define a http.ServeMux
to register our handlers. A http.ServeMux
is a struct that acts as the router of our server.
mux := http.NewServeMux()
mux.HandleFunc("/foo", fooHandler) // Matches the path "/foo" only.
mux.HandleFunc("/foo/", barHandler) // Matches all paths that begin with "/foo/".
// Register a static file server for all URL paths that begin "/static/".
fs := http.FileServer(http.Dir("./path/to/static/dir"))
mux.Handle("/static/", http.StripPrefix("/static", fs))
Methods are checked inside the handlers. For example, imagine that the fooHandler
should only handle GET
requests. You can check the request method inside the handler and return an error if the method is not the expected.
func fooHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
// Your code here
}
And finally, we need to create a http.Server
and call the ListenAndServe
method to start the server.
srv := &http.Server{
Addr: ":4080", // localhost:4080
Handler: mux,
ErrorLog: log.New(...),
IdleTimeout: time.Minute,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
err := srv.ListenAndServe()
log.Fatal(err)
Adding context to requests
Context is a fundamental concept in Go. It allows us to pass data between functions and goroutines. And it's very useful to pass data between middlewares and handlers. You can add context to requests too using the http.Request.WithContext
method.
type contextKey string
var contextKeySite = contextKey("site")
// Add a value to request context. Note: This creates a new copy of the request.
ctx = context.WithValue(r.Context(), contextKeySite, "aalonso.dev")
r = r.WithContext(ctx)
// Retrieve the value from request context.
site, ok := r.Context().Value(contextKeySite).(string)
Wrapping up
And that's it. As you saw, the net/http
package has everything you need to start prototyping your next API. You can create a server, add handlers and middlewares, read requests, write responses, and add context to requests just using the standard library.
Now you are ready to start prototyping your next API in Go. Do you want more? I wrote a similar article, with all you need to know to integrate a SQL database in golang. Check it out!