tutorial: add web service tutorial using Go and Gin

This adds an introductory tutorial on building a small RESTful API using Go
and the Gin web framework.

This change includes content on:

- Using the Gin web framework
  (https://pkg.go.dev/github.com/gin-gonic/gin).
- Creating an API that includes three HTTP method-and-endpoint
  combinations.
- GET and POST actions that convert between a Go struct and JSON.
- Supporting URL path parameters.
- Go command-line tools such as `go get` and `go run`.

This content is written in the enhanced Markdown format supported by the
interactive tutorial application in the Google Cloud Shell Editor.
For more, see https://cloud.google.com/shell/docs/cloud-shell-tutorials/tutorials

Change-Id: I216ec12763af83db257c978997a2987eae418995
Reviewed-on: https://go-review.googlesource.com/c/tour/+/328611
Trust: Steve Traut <straut@google.com>
Reviewed-by: Russ Cox <rsc@golang.org>
diff --git a/tutorial/web-service-gin.md b/tutorial/web-service-gin.md
new file mode 100644
index 0000000..b471cb7
--- /dev/null
+++ b/tutorial/web-service-gin.md
@@ -0,0 +1,655 @@
+---
+title: Developing a RESTful API with Go and Gin
+description: Using the Gin web framework to develop a RESTful API.
+author: straut@google.com
+tags: Go
+date_published: 
+---
+
+# Developing a RESTful API with Go and Gin
+
+This tutorial introduces the basics of writing a RESTful web service API with Go
+and the [Gin Web Framework](https://gin-gonic.com/docs/) (Gin).
+
+You'll get the most out of this tutorial if you have a basic familiarity with Go
+and its tooling. If this is your first exposure to Go, please see
+[Tutorial: Get started with Go](https://golang.org/doc/tutorial/getting-started)
+for a quick introduction.
+
+Gin simplifies many coding tasks associated with building web applications,
+including web services. In this tutorial, you'll use Gin to route requests,
+retrieve request details, and marshal JSON for responses.
+
+In this tutorial, you will build a RESTful API server with two endpoints. Your
+example project will be a repository of data about vintage jazz records.
+
+The tutorial includes the following sections:
+
+1. Design API endpoints.
+2. Create a folder for your code.
+3. Create the data.
+4. Write a handler to return all items.
+5. Write a handler to add a new item.
+6. Write a handler to return a specific item.
+
+## Design API endpoints
+
+You'll build an API that provides access to a store selling vintage recordings
+on vinyl. So you'll need to provide endpoints through which a client can get
+and add albums for users.
+
+When developing an API, you typically begin by designing the endpoints. Your
+API's users will have more success if the endpoints are easy to understand.
+
+Here are the endpoints you'll create in this tutorial.
+
+/albums
+*   `GET` – Get a list of all albums, returned as JSON.
+*   `POST` – Add a new album from request data sent as JSON.
+
+/albums/:id
+*   `GET` – Get an album by its ID, returning the album data as JSON.
+
+Next, you'll create a project for your code.
+
+## Create a project for your code
+
+To begin, create a project for the code you'll write.
+
+1. Ensure that the **cloudshell_open** folder is selected.
+
+2. Click the <walkthrough-editor-spotlight spotlightId="menu-file">File Menu</walkthrough-editor-spotlight>,
+    then click **New Folder**.
+
+3. In the **New Folder** dialog, enter `web-service-gin` for the folder name,
+    then click **OK**.
+
+4. Click the <walkthrough-editor-spotlight spotlightId="menu-file">File Menu</walkthrough-editor-spotlight>,
+    then click **Open Workspace**.
+
+5. In the **Open Workspace** dialog, select the cloudshell_open/web-service-gin
+    folder you just created, then click **Open**.
+
+6. Click the <walkthrough-editor-spotlight spotlightId="menu-terminal-new-terminal">New Terminal</walkthrough-editor-spotlight>
+    menu command to open a new Cloud Shell terminal.
+
+    The terminal prompt should be in the web-service-gin directory.
+
+7. Create a module in which you can manage dependencies.
+
+    Run the `go mod init` command, giving it the path of the module your code
+    will be in.
+
+    ```bash
+    go mod init example.com/web-service-gin
+    go: creating new go.mod: module example.com/web-service-gin
+    ```
+
+    This command creates a <walkthrough-editor-spotlight spotlightId="navigator" spotlightItem="go.mod">go.mod file</walkthrough-editor-spotlight>
+    in which dependencies you might add will be listed for tracking. For more,
+    be sure to see [Managing dependencies](https://golang.org/doc/modules/managing-dependencies).
+
+Next, you'll design data structures for handling data.
+
+## Create the data
+
+To keep things simple for the tutorial, you'll store data in memory. A more
+typical API would interact with a database.
+
+Note that storing data in memory means that the set of albums will be lost each
+time you stop the server, then recreated when you start it.
+
+### Write the code
+
+1. Click the <walkthrough-editor-spotlight spotlightId="menu-file">File Menu</walkthrough-editor-spotlight>, then click **New File**.
+
+2. In the **New File** dialog, enter `main.go` for the file name, then click **OK**.
+
+3. Into main.go, at the top of the file, paste the package declaration below.
+
+    ```
+    package main
+    ```
+
+    A standalone program (as opposed to a library) is always in package `main`.
+
+4. Beneath the package declaration, paste the following declaration of an
+    `album` struct. You'll use this to store album data in memory.
+
+    Struct tags such as ``json:"artist"`` specify what a field's name should be
+    when the struct's contents are serialized into JSON. Without them, the JSON
+    would use the struct's capitalized field names – a style not as common in JSON.
+
+    ```
+    // album represents data about a record album.
+    type album struct {
+    	ID     string  `json:"id"`
+    	Title  string  `json:"title"`
+    	Artist string  `json:"artist"`
+    	Price  float64 `json:"price"`
+    }
+    ```
+
+5. Beneath the struct declaration you just added, paste the following slice of
+    `album` structs containing data you'll use to start.
+
+    ```
+    // albums slice to seed record album data.
+    var albums = []album{
+    	{ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
+    	{ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
+    	{ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
+    }
+    ```
+
+Next, you'll write code to implement your first endpoint.
+
+## Write a handler to return all items
+
+When the client makes a request at `GET /albums`, you want to return all the
+albums as JSON. 
+
+To do this, you'll write the following:
+
+*   Logic to prepare a response
+*   Code to map the request path to your logic
+
+Note that this is the reverse of how they'll be executed at runtime, but you're
+adding dependencies first, then the code that depends on them.
+
+### Write the code
+
+As you follow these steps, ignore the errors visible in the editor. You'll fix
+these in **Run the code**, below. 
+
+1. Beneath the struct code you added in the preceding section, paste the
+    following code to get the album list.
+
+    This `getAlbums` function creates JSON from the slice of `album` structs,
+    writing the JSON into the response.
+
+    ```
+    // getAlbums responds with the list of all albums as JSON.
+    func getAlbums(c *gin.Context) {
+    	c.IndentedJSON(http.StatusOK, albums)
+    }
+    ```
+
+    In this code, you:
+
+    *   Write a `getAlbums` function that takes a [`gin.Context`](https://pkg.go.dev/github.com/gin-gonic/gin#Context)
+        parameter. Note that you could have given this function any name – neither
+        Gin nor Go require a particular function name format. 
+
+        `gin.Context` is the most important part of Gin. It carries request
+        details, validates and serializes JSON, and more. (Despite the similar
+        name, this is different from Go's built-in [`context`](https://golang.org/pkg/context/)
+        package.)
+
+    *   Call [`Context.IndentedJSON`](https://pkg.go.dev/github.com/gin-gonic/gin#Context.IndentedJSON)
+        to serialize the struct into JSON and add it to the response.
+
+        The function's first argument is the HTTP status code you want to send to
+        the client. Here, you're passing the [`StatusOK`](https://pkg.go.dev/net/http#StatusOK)
+        constant from the `net/http` package to indicate `200 OK`.
+
+        Note that you can replace `Context.IndentedJSON` with a call to
+        [`Context.JSON`](https://pkg.go.dev/github.com/gin-gonic/gin#Context.JSON)
+        to send more compact JSON. In practice, the indented form is much easier to
+        work with when debugging and the size difference is usually small.
+
+2. Near the top of main.go, just beneath the `albums` slice declaration, paste
+    the code below to assign the handler function to an endpoint path.
+
+    This sets up an association in which `getAlbums` handles requests to the
+    `/albums` endpoint path.
+
+    ```
+    func main() {
+    	router := gin.Default()
+    	router.GET("/albums", getAlbums)
+    
+    	router.Run("localhost:8080")
+    }
+    ```
+
+    In this code, you:
+
+    *   Initialize a Gin router using [`Default`](https://pkg.go.dev/github.com/gin-gonic/gin#Default).
+    *   Use the [`GET`](https://pkg.go.dev/github.com/gin-gonic/gin#RouterGroup.GET)
+        function to associate the `GET` HTTP method and `/albums` path with a handler
+        function.
+
+        Note that you're passing the _name_ of the `getAlbums` function. This is
+        different from passing the _result_ of the function, which you would do by
+        passing `getAlbums()` (note the parenthesis).
+
+    *   Use the [`Run`](https://pkg.go.dev/github.com/gin-gonic/gin#Engine.Run)
+        function to attach the router to an `http.Server` and start the server.
+
+3. Near the top of main.go, just beneath the package declaration, import the
+    packages you'll need to support the code you've just written.
+
+    The first lines of code should look like this:
+
+    ```
+    package main
+
+    import (
+    	"net/http"
+
+    	"github.com/gin-gonic/gin"
+    )
+    ```
+
+4. Save main.go.
+
+### Run the code
+
+1. Begin tracking the Gin module as a dependency.
+
+    At the command line, use [`go get`](https://golang.org/cmd/go/#hdr-Add_dependencies_to_current_module_and_install_them)
+    to add the github.com/gin-gonic/gin module as a dependency for your module.
+    Use a dot argument to mean "get dependencies for code in the current directory."
+
+    ```bash
+    go get .
+    go get: added github.com/gin-gonic/gin v1.7.2
+    ```
+
+    Go resolved and downloaded this dependency to satisfy the `import`
+    declaration you added in the previous step.
+
+2. In the Cloud Shell terminal window, run the code.
+
+    Use a dot argument to mean "run the package in the current directory."
+
+    ```bash
+    go run .
+    ```
+
+    Once the code is running, you have a running HTTP server to which you can
+    send requests.
+
+3. Open a new terminal window in which to make requests to the running web service.
+
+    Click the <walkthrough-editor-spotlight spotlightId="menu-terminal-new-terminal">New Terminal</walkthrough-editor-spotlight>
+    menu command to open a new Cloud Shell terminal.
+
+4. In the second Cloud Shell terminal window, use `curl` to make a request to your
+    running web service.
+
+    ```bash
+    curl http://localhost:8080/albums
+    ```
+
+    The command should display the data you seeded the service with. 
+
+    ```
+    [
+            {
+                    "id": "1",
+                    "title": "Blue Train",
+                    "artist": "John Coltrane",
+                    "price": 56.99
+            },
+            {
+                    "id": "2",
+                    "title": "Jeru",
+                    "artist": "Gerry Mulligan",
+                    "price": 17.99
+            },
+            {
+                    "id": "3",
+                    "title": "Sarah Vaughan and Clifford Brown",
+                    "artist": "Sarah Vaughan",
+                    "price": 39.99
+            }
+    ]
+    ```
+
+You've started an API! In the next section, you'll create another endpoint with
+code to handle a `POST` request to add an item.
+
+## Write a handler to add a new item
+
+When the client makes a `POST` request at `/albums`, you want to add the album
+described in the request body to the existing albums data.
+
+To do this, you'll write the following:
+
+*   Logic to add the new album to the existing list.
+*   A bit of code to route the `POST` request to your logic.
+
+### Write the code
+
+1. Add code to add albums data to the list of albums.
+
+    Somewhere after the `import` statements, paste the following code. (The end
+    of the file is a good place for this code, but Go doesn't enforce the order
+    in which you declare functions.)
+
+    ```
+    // postAlbums adds an album from JSON received in the request body.
+    func postAlbums(c *gin.Context) {
+    	var newAlbum album
+
+    	// Call BindJSON to bind the received JSON to
+    	// newAlbum.
+    	if err := c.BindJSON(&newAlbum); err != nil {
+    		return
+    	}
+
+    	// Add the new album to the slice.
+    	albums = append(albums, newAlbum)
+    	c.IndentedJSON(http.StatusCreated, newAlbum)
+    }
+    ```
+
+    In this code, you:
+
+    *   Use [`Context.BindJSON`](https://pkg.go.dev/github.com/gin-gonic/gin#Context.BindJSON)
+        to bind the request body to `newAlbum`.
+    *   Append the `album` struct initialized from the JSON to the `albums`
+        slice.
+    *   Add a `201` status code to the response, along with JSON representing
+        the album you added.
+
+2. Change your `main` function so that it includes the `router.POST` function,
+    as in the following.
+
+    ```
+    func main() {
+    	router := gin.Default()
+    	router.GET("/albums", getAlbums)
+    	router.POST("/albums", postAlbums)
+
+    	router.Run("localhost:8080")
+    }
+    ```
+
+    In this code, you:
+
+    *   Associate the `POST` method at the `/albums` path with the `postAlbums`
+        function.
+
+        With Gin, you can associate a handler with an HTTP method-and-path
+        combination. In this way, you can separately route requests sent to a
+        single path based on the method the client is using.
+
+4. Save main.go.
+
+### Run the code
+
+1. In the first Cloud Shell terminal window, if the server is still running from the last section, stop it.
+
+2. In the first Cloud Shell terminal window, run the code.
+
+    ```bash
+    go run .
+    ```
+
+3. In the other Cloud Shell terminal window, use the following `curl` command to make
+    a request to your running web service.
+
+    ```bash
+    curl http://localhost:8080/albums \
+        --include \
+        --header "Content-Type: application/json" \
+        --request "POST" \
+        --data '{"id": "4","title": "The Modern Sound of Betty Carter","artist": "Betty Carter","price": 49.99}'
+    ```
+
+    The command should display headers and JSON for the added album.
+
+    ```
+    HTTP/1.1 201 Created
+    Content-Type: application/json; charset=utf-8
+    Date: Wed, 02 Jun 2021 00:34:12 GMT
+    Content-Length: 116
+
+    {
+        "id": "4",
+        "title": "The Modern Sound of Betty Carter",
+        "artist": "Betty Carter",
+        "price": 49.99
+    }
+    ```
+
+4. As in the previous section, use `curl` to retrieve the full list of albums,
+    which you can use to confirm that the new album was added.
+
+    ```bash
+    curl http://localhost:8080/albums \
+        --header "Content-Type: application/json" \
+        --request "GET"
+    ```
+
+    The command should display the album list. 
+
+    ```
+    [
+            {
+                    "id": "1",
+                    "title": "Blue Train",
+                    "artist": "John Coltrane",
+                    "price": 56.99
+            },
+            {
+                    "id": "2",
+                    "title": "Jeru",
+                    "artist": "Gerry Mulligan",
+                    "price": 17.99
+            },
+            {
+                    "id": "3",
+                    "title": "Sarah Vaughan and Clifford Brown",
+                    "artist": "Sarah Vaughan",
+                    "price": 39.99
+            },
+            {
+                    "id": "4",
+                    "title": "The Modern Sound of Betty Carter",
+                    "artist": "Betty Carter",
+                    "price": 49.99
+            }
+    ]
+    ```
+
+In the next section, you'll add code to handle a `GET` for a specific item.
+
+## Write a handler to return a specific item
+
+When the client makes a request to `GET /albums/[id]`, you want to return the
+album whose ID matches the `id` path parameter.
+
+To do this, you will:
+
+*   Add logic to retrieve the requested album.
+*   Map the path to the logic.
+
+### Write the code
+
+1. Beneath the `postAlbums` function you added in the preceding section, paste
+    the following code to retrieve a specific album.
+
+    This `getAlbumByID` function will extract the ID in the request path, then
+    locate an album that matches.
+
+    ```
+    // getAlbumByID locates the album whose ID value matches the id
+    // parameter sent by the client, then returns that album as a response.
+    func getAlbumByID(c *gin.Context) {
+    	id := c.Param("id")
+
+    	// Loop over the list of albums, looking for
+    	// an album whose ID value matches the parameter.
+    	for _, a := range albums {
+    		if a.ID == id {
+    			c.IndentedJSON(http.StatusOK, a)
+    			return
+    		}
+    	}
+    	c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
+    }
+    ```
+
+    In this code, you:
+
+    *   Use [`Context.Param`](https://pkg.go.dev/github.com/gin-gonic/gin#Context.Param)
+        to retrieve the `id` path parameter from the URL. When you map this
+        handler to a path, you'll include a placeholder for the parameter in the
+        path.
+    *   Loop over the `album` structs in the slice, looking for one whose `ID`
+        field value matches the `id` parameter value. If it's found, you serialize
+        that `album` struct to JSON and return it as a response with a `200 OK`
+        HTTP code.
+
+        As mentioned above, a real-world service would likely use a database
+        query to perform this lookup.
+
+    *   Return an HTTP `404` error with [`http.StatusNotFound`](https://pkg.go.dev/net/http#StatusNotFound)
+        if the album isn't found.
+
+2. Finally, change your `main` so that it includes a new call to `router.GET`,
+    where the path is now `/albums/:id`, as shown in the following example.
+
+    ```
+    func main() {
+    	router := gin.Default()
+    	router.GET("/albums", getAlbums)
+    	router.GET("/albums/:id", getAlbumByID)
+    	router.POST("/albums", postAlbums)
+
+    	router.Run("localhost:8080")
+    }
+    ```
+
+    In this code, you:
+
+    *   Associate the `/albums/:id` path with the `getAlbumByID` function. In
+        Gin, the colon preceding an item in the path signifies that the item is
+        a path parameter.
+
+### Run the code
+
+1. In the first Cloud Shell terminal window, if the server is still running from the last section, stop it.
+
+2. In the first Cloud Shell terminal window, run the code.
+
+    ```bash
+    go run .
+    ```
+
+3. In the other Cloud Shell terminal window, use `curl` to make a request to your
+    running web service.
+
+    ```bash
+    curl http://localhost:8080/albums/2
+    ```
+
+    The command should display JSON for the album whose ID you used. If the album
+    wasn't found, you'll get JSON with an error message.
+
+    ```
+    {
+            "id": "2",
+            "title": "Jeru",
+            "artist": "Gerry Mulligan",
+            "price": 17.99
+    }
+    ```
+
+Continue to the last section for links to useful content.
+
+## Conclusion
+
+Congratulations! You've just used Go and Gin to write a simple RESTful web service.
+
+Suggested next topics:
+
+*   If you're new to Go, you'll find useful best practices described in
+    [Effective Go](https://golang.org/doc/effective_go) and
+    [How to write Go code](https://golang.org/doc/code).
+*   The [Go Tour](https://tour.golang.org/welcome/1) is a great step-by-step
+    introduction to Go fundamentals.
+*   For more about Gin, see the [Gin Web Framework package documentation](https://pkg.go.dev/github.com/gin-gonic/gin)
+    or the [Gin Web Framework docs](https://gin-gonic.com/docs/).
+
+Continue to the next section to view the full code for the application you build
+with this tutorial.
+
+## Completed code
+
+This section contains the code for the application you build with this tutorial.
+
+```
+package main
+
+import (
+	"net/http"
+
+	"github.com/gin-gonic/gin"
+)
+
+// album represents data about a record album.
+type album struct {
+	ID     string  `json:"id"`
+	Title  string  `json:"title"`
+	Artist string  `json:"artist"`
+	Price  float64 `json:"price"`
+}
+
+// albums slice to seed record album data.
+var albums = []album{
+	{ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
+	{ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
+	{ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
+}
+
+func main() {
+	router := gin.Default()
+	router.GET("/albums", getAlbums)
+	router.GET("/albums/:id", getAlbumByID)
+	router.POST("/albums", postAlbums)
+
+	router.Run("localhost:8080")
+}
+
+// getAlbums responds with the list of all albums as JSON.
+func getAlbums(c *gin.Context) {
+	c.IndentedJSON(http.StatusOK, albums)
+}
+
+// postAlbums adds an album from JSON received in the request body.
+func postAlbums(c *gin.Context) {
+	var newAlbum album
+
+	// Call BindJSON to bind the received JSON to
+	// newAlbum.
+	if err := c.BindJSON(&newAlbum); err != nil {
+		return
+	}
+
+
+	// Add the new album to the slice.
+	albums = append(albums, newAlbum)
+	c.IndentedJSON(http.StatusCreated, newAlbum)
+}
+
+// getAlbumByID locates the album whose ID value matches the id
+// parameter sent by the client, then returns that album as a response.
+func getAlbumByID(c *gin.Context) {
+	id := c.Param("id")
+
+	// Loop through the list of albums, looking for
+	// an album whose ID value matches the parameter.
+	for _, a := range albums {
+		if a.ID == id {
+			c.IndentedJSON(http.StatusOK, a)
+			return
+		}
+	}
+	c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
+}
+```