This chapter demonstrates building a fully functional RESTful API for managing a movie collection. You’ll learn how to structure REST endpoints, handle JSON data, work with HTTP methods, and use the Gorilla Mux router for dynamic URL parameters.
This project is part of Chapter 9: Web Basics and serves as the foundation for understanding HTTP servers in Go.
A complete CRUD (Create, Read, Update, Delete) API that manages movies with directors. The API uses in-memory storage and demonstrates RESTful principles.
Go’s encoding/json package provides powerful serialization capabilities. For a struct to be JSON-encodable, fields must be exported (capitalized).
type Movie struct { ID string `json:"id"` Isbn string `json:"isbn"` Title string `json:"title"` Director *Director `json:"director"`}type Director struct { Firstname string `json:"firstname"` Lastname string `json:"lastname"`}
Common Pitfall: If struct fields are lowercase (unexported), they won’t be visible to the JSON encoder. Always capitalize field names and use struct tags for JSON keys.
While Go’s standard library provides http.ServeMux, Gorilla Mux offers advanced routing features like URL parameters, method-based routing, and regex patterns.
func getMovies(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(movies)}
This handler:
Sets the Content-Type header to indicate JSON response
Encodes the entire movies slice directly to the response writer
func getMovie(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") params := mux.Vars(r) for _, movie := range movies { if movie.ID == params["id"] { json.NewEncoder(w).Encode(movie) return } }}
func createMovies(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") var movie Movie json.NewDecoder(r.Body).Decode(&movie) movies = append(movies, movie) json.NewEncoder(w).Encode(movie)}
1
Decode Request Body
json.NewDecoder(r.Body).Decode(&movie) reads the request body and unmarshals JSON into the movie struct.
2
Store the Movie
Append the new movie to the in-memory slice.
3
Return Created Resource
Encode and return the created movie to confirm the operation.
func updateMovies(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") params := mux.Vars(r) for index, movie := range movies { if movie.ID == params["id"] { // Remove old movie movies = append(movies[:index], movies[index+1:]...) // Decode and add new movie var movie Movie json.NewDecoder(r.Body).Decode(&movie) movies = append(movies, movie) json.NewEncoder(w).Encode(movie) return } }}
This implementation uses a replace strategy: it removes the old entry and adds the new one. In production, you’d typically update fields in place.
func deleteMovies(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") params := mux.Vars(r) for index, movie := range movies { if movie.ID == params["id"] { movies = append(movies[:index], movies[index+1:]...) break } }}
The slice manipulation movies[:index] and movies[index+1:] creates a new slice excluding the deleted element.