How to Build Your First API in 10 Minutes Using Go
It seems like everywhere you look these days, someone is talking about APIs. Whether it’s a company trying to sell you a new API-based product, or a blog post about the latest and greatest API framework, it’s hard to escape the API hype. But what exactly is an API?
An API (Application Programming Interface) is a way for two pieces of software to communicate with each other. When you visit a website, your browser is actually making an API request to a web server. The server then responds with the data that makes up the website, which your browser then renders into a human-readable format.
In this post, we’re going to look at how to build a simple API using the Go programming language. We’ll start with a brief introduction to Go, then we’ll dive right into building our API. By the end, you should have a good understanding of how to build basic APIs using Go.
What is Go?
Go is a programming language created by Google. It’s a statically typed, compiled language that feels a lot like a dynamic language like Python. It’s also designed to be easy to read and write, and it has some features that make it well suited for building APIs, such as built-in support for JSON.
If you’re not familiar with Go, I recommend checking out the Go Tour, which is a great way to get started with the language.
Setting up our API
Now that we’ve got a brief introduction to Go out of the way, let’s start building our API. The first thing we need to do is create a new file called main.go. We’re going to put all of our API code in this file.
Next, we need to import the two packages we’ll be using:
import (
"encoding/json"
"net/http"
)
The first package, encoding/json, provides us with the ability to encode and decode JSON data. We’ll be using this to send JSON responses back to the client. The second package, net/http, provides us with a way to create an HTTP server and handle HTTP requests.
Next, we need to define our API endpoint. We’re going to keep it simple and just have a single endpoint that returns a list of users. Our endpoint will be a GET request to the /users path.
func usersHandler(w http.ResponseWriter, r *http.Request) {
// our code will go here
}
The usersHandler function takes two parameters: w and r. The w parameter is an http.ResponseWriter, which we can use to write our HTTP response. The r parameter is an http.Request, which contains information about the request, such as the URL, headers, and form data.
Inside the usersHandler function, we need to first set the Content-Type header to application/json. This tells the client that the response body will be JSON-encoded data.
w.Header().Set("Content-Type", "application/json")
Next, we need to create our response data. We’re going to create a slice of User structs. A struct is a way to group together related data. In this case, our User struct will have a name and an age.
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
var users = []User{
{
Name: "John Smith",
Age: 30,
},
{
Name: "Jane Doe",
Age: 20,
},
}
Note that we’ve also added some JSON annotations to our struct fields. These annotations tell the json package how to encode and decode the data. In this case, we’re telling it to use the field name as the key in the JSON object.
Now that we have our response data, we need to encode it to JSON. We can do this by calling the json.NewEncoder function. This function takes an http.ResponseWriter and returns a new JSON encoder. We can then use the encoder’s Encode method to encode our data to JSON and write it to the response writer.
func usersHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
encoder := json.NewEncoder(w)
encoder.Encode(users)
}
The last thing we need to do in our usersHandler function is to call the http.Error method. This method sends an HTTP error response with the given status code and error message. We’re going to call it with a status code of 500 (internal server error) in case there’s an error encoding the data to JSON.
func usersHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
encoder := json.NewEncoder(w)
err := encoder.Encode(users)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
Starting the Server
Now that we have our API endpoint defined, we need to start our HTTP server so that it can handle requests. We’re going to use the http.ListenAndServe function to do this. This function takes two parameters: an address to listen on and an http.Handler. We’re going to listen on port 8080, and we’re going to use our usersHandler function as the handler.
func main() {
http.HandleFunc("/users", usersHandler)
http.ListenAndServe(":8080", nil)
}
The http.HandleFunc function registers a handler function for a given URL path. In this case, we’re telling it to use our usersHandler function for requests to the /users path.
Testing our API
Now that our server is up and running, let’s test out our API. We can do this using a tool like curl, which is a command-line tool for making HTTP requests.
$ curl -i http://localhost:8080/users
HTTP/1.1 200 OK
Content-Type: application/json
Date: Sun, 10 Apr 2016 17:17:12 GMT
Content-Length: 50
continue.
[{"name":"John Smith","age":30},{"name":"Jane Doe","age":20}]
As you can see, we get a JSON-encoded response back from our API. Success!
Adding a POST Endpoint
Now that we have a basic GET endpoint working, let’s add a POST endpoint that allows us to create new users. Our POST endpoint will be a POST request to the /users path.
func usersCreateHandler(w http.ResponseWriter, r *http.Request) {
// our code will go here
}
Just like our GET endpoint, the usersCreateHandler function takes two parameters: w and r. We’re going to start by reading the request body into a variable. The request body is the JSON-encoded data that was sent in the request. We can read it into a variable using the ioutil.ReadAll function.
func usersCreateHandler(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
Note that we’re also using the return keyword to exit the function early if there’s an error reading the request body. This is a common pattern in Go code, and it helps to keep our code clean and easy to read.
Next, we need to decode the JSON-encoded data into a variable. We’re going to use the json.Unmarshal function to do this. This function takes a byte slice and a pointer to a variable, and it decodes the data into the variable.
func usersCreateHandler(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var user User
err = json.Unmarshal(body, &user)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
Note that we’re using the same pattern of exiting early if there’s an error.
Now that we have our user data, we need to add it to our slice of users. We can do this by appending it to the slice.
func usersCreateHandler(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var user User
err = json.Unmarshal(body, &user)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
users = append(users, user)
}
The last thing we need to do is write a JSON-encoded response back to the client. We can do this by using the same pattern we used in the GET endpoint:
func usersCreateHandler(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var user User
err = json.Unmarshal(body, &user)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
users = append(users, user)
w.Header().Set("Content-Type", "application/json")
encoder := json.NewEncoder(w)
encoder.Encode(user)
}
Note that we’re encoding and writing the user variable, not the users variable. This is because we want to return the newly created user, not the entire list of users.
Testing our POST Endpoint
Now that we have our POST endpoint defined, let’s test it out. We’re going to use curl again, but this time we’re going to use the -X POST flag to specify that we want to make a POST request. We’re also going to use the -d flag to specify the data we want to POST.
$ curl -i -X POST -d '{"name":"Bob","age":40}' http://localhost:8080/users
HTTP/1.1 200 OK
Content-Type: application/json
Date: Sun, 10 Apr 2016 17:36:57 GMT
Content-Length: 27
{"name":"Bob","age":40}
As you can see, we get a JSON-encoded response back from our API containing the newly created user. Success!
Wrapping Up
In this post, we’ve looked at how to build a simple API using Go. We started with a brief introduction to the language, then we dove right into building our API. We defined a GET endpoint that returns a list of users, and a POST endpoint that allows us to create new users. We also looked at how to test our API using curl.
If you’re interested in learning more about building APIs with Go, I recommend checking out the GoDocs for the net/http package. There’s also a great book called Building Microservices with Go that goes into more depth about building APIs and microservices with Go.
I hope this has been a helpful introduction to building APIs with Go. If you enjoyed it and found it helpful, I invite you to subscribe to my weekly newsletter. You'll receive exclusive content that I don't share on my blog, and I'll keep you updated on all the latest news and events. Click here to subscribe now. Also, If you have any questions or feedback, feel free to leave a DM me on Twitter.