Zum Inhalt springen

Project Go: From Zero to Deploy -(Project 2)

Originally posted on Medium

Building a Simple RESTful API with Go (and Gorilla Mux)

by Arpit Mishra

Photo by Chinmay B on Unsplash

Every Go application starts with the main package. It’s the entry point of our program. We’ll also need a few essential packages:

package main
import (
"encoding/json" // For encoding and decoding JSON data
"fmt" // For formatted I/O (like printing to the console)
"log" // For logging errors and messages
"net/http" // Go's built-in HTTP package for web servers
"strconv" // For converting strings to integers (and vice-versa)
"github.com/gorilla/mux" // Our powerful router for handling HTTP requests
)

Defining Our Data Structure: The User Struct

Before we start handling requests, we need to define what a “user” looks like in our application. In Go, we use a struct for this:

type User struct {
ID int json:"id"
Name string json:"name"
Email string json:"email"
}

Notice those json:“id“, json:“name“, and json:“email“ tags? These are struct tags. They tell the encoding/json package how to map our struct fields to JSON keys when we encode or decode data. For example, the ID field in our Go struct will appear as „id“ in the JSON output.

Our “Database”: In-Memory User Storage

For simplicity, we’ll store our users right in memory using a global slice of Userstructs. This means our data won’t persist if the server restarts, but it’s perfect for a quick example.

var users = []User{
{ID: 1, Name: "John Doe", Email: "johndoe@example.com"},
{ID: 2, Name: "Jane Smith", Email: "jane@example.com"},
}
var nextID = 3
  • users: This slice holds our initial set of user data.

  • nextID: A simple counter to assign unique IDs to new users.

API Endpoints: The Handlers

Now, let’s build the functions (often called handlers in Go web development) that will respond to different HTTP requests. Each handler takes two arguments: http.ResponseWriter (to send responses back to the client) and *http.Request (to get information about the incoming request).

1. Get All Users (GET /users)

func getUsers(w http.ResponseWriter, r *http.Request) {
json.NewEncoder((w)).Encode(users)
}

This is straightforward:

  • We create a new JSON encoder associated with our http.ResponseWriter.
  • Then, we Encode our users slice directly to the response. This will convert our Go slice into a JSON array of user objects.

2. Get a Single User (GET /users/{id})

func getUser(w http.ResponseWriter, r *http.Request) {
idParam := mux.Vars(r)["id"] // Extract the 'id' from the URL path
id, err := strconv.Atoi(idParam) // Convert the string ID to an integer
if err != nil {
http.Error(w, "Invalid user ID", http.StatusBadRequest) // Handle non-integer IDs
return
}
for , user := range users { // Loop through our users
if user.ID == id {
json.NewEncoder(w). Encode(user) // Found it! Encode and send
return
}
}
http.Error(w, "User not found", http.StatusNotFound) // User not found
}

Here’s the breakdown:

  • mux.Vars(r)[„id“]: This is where Gorilla Mux shines! It extracts the value of the id variable from the URL path (e.g., if the URL is /users/1, idParam will be „1“).

  • strconv.Atoi(idParam): We convert the extracted string ID to an integer. If this conversion fails (e.g., if someone tries to access /users/abc), we send a 400 Bad Request error.

  • We then iterate through our users slice to find the user with the matching ID.
  • If found, we encode that single user object as JSON.
  • If no user with the given ID is found after checking all users, we return a 404 Not Found error.

3. Create a New User (POST /users)

Go

func createUser(w http.ResponseWriter, r *http.Request) {
var newUser User
json.NewDecoder(r.Body).Decode(&newUser) // Decode the JSON request body into a newUser struct
newUser.ID = nextID // Assign a new ID
nextID++
users = append(users, newUser) // Add the new user to our slice
w.WriteHeader(http.StatusCreated) // Set HTTP status code to 201 Created
json.NewEncoder(w).Encode((newUser)) // Respond with the newly created user
}

For creating a user:

  • We declare an empty User struct called newUser.
  • json.NewDecoder(r.Body).Decode(&newUser): We decode the JSON data from the request body directly into our newUser struct. The client sends us the name and email, and Decode populates our newUser struct with those values.

  • We assign a unique ID using our nextID counter and then increment nextID.
  • We append the newUser to our users slice.
  • Finally, we set the HTTP status code to 201 Created and send the newly created user object back in the response.

4. Update an Existing User (PUT /users/{id})

Go

func updateUser(w http.ResponseWriter, r *http.Request) {
idParam := mux.Vars(r)["id"]
id, err := strconv.Atoi(idParam)
if err != nil {
http.Error(w, "Invalid user ID", http.StatusBadRequest)
return
}

var updatedUser User
json.NewDecoder(r.Body).Decode(&updatedUser) // Decode the request body (new name/email)
for i, user := range users {
if user.ID == id {
users[i].Name = updatedUser.Name // Update the name
users[i].Email = updatedUser.Email // Update the email
json.NewEncoder(w).Encode(users[i]) // Respond with the updated user
return
}
}
http.Error(w, "User not found", http.StatusNotFound)
}

Updating is similar to getting a single user, but with an extra step:

  • We extract and convert the id from the URL, handling errors as before.
  • We decode the incoming JSON from the request body into an updatedUser struct. This updatedUser will contain the new Name and Email we want to set.
  • We then iterate through our users slice. When we find the user with the matching ID, we update their Name and Email fields with the values from updatedUser.
  • Finally, we respond with the now-updated user.

5. Delete a User (DELETE /users/{id})

Go

func deleteUser(w http.ResponseWriter, r *http.Request) {
idParam := mux.Vars(r)["id"]
id, err := strconv.Atoi(idParam)
if err != nil {
http.Error(w, "Invalid user ID", http.StatusBadRequest)
return
}
for i, user := range users {
if user.ID == id {
// This line is a common Go idiom for deleting an element from a slice
users = append(users[:i], users[i+1:]...)
w.WriteHeader(http.StatusNoContent) // 204 No Content for successful deletion
return
}
}
http.Error(w, "User not found", http.StatusNotFound)
}

Deleting a user:

  • Again, we extract and convert the id.
  • We iterate to find the user.
  • users = append(users[:i], users[i+1:]…): This is a classic Go trick to remove an element from a slice. It effectively creates a new slice by appending the elements before the deleted item with the elements after it.

  • Upon successful deletion, we send a 204 No Content status code, as there’s typically no body to return for a successful deletion.

Setting Up the Router and Starting the Server (main ⁣function)

This is where all the pieces come together.

func main() {
router := mux.NewRouter() // Create a new Gorilla Mux router
// Define our API routes and map them to our handler functions
router.HandleFunc("/users", getUsers).Methods("GET")
router.HandleFunc("/users/{id:[0-9]+}", getUser).Methods("GET")
router.HandleFunc("/users", createUser).Methods("POST")
router.HandleFunc("/users/{id:[0-9]+}", updateUser).Methods("PUT")
router.HandleFunc("/users/{id:[0-9]+}", deleteUser).Methods("DELETE")
fmt.Println("Starting server on :8080")
// Start the HTTP server
log.Fatal(http.ListenAndServe(":8080", router))
}

Let’s break down the main function:

  • router := mux.NewRouter(): We initialize a new Gorilla Mux router. This router will be responsible for matching incoming HTTP requests to the correct handler functions.

  • router.HandleFunc(…): This is where we define our API routes.

  • The first argument is the path (e.g., /users, /users/{id}).
  • The second argument is the handler function that should be executed when a request matches that path.
  • .Methods(„GET“), .Methods(„POST“), etc.: This is a powerful feature of Gorilla Mux. It allows us to specify which HTTP methods (GET, POST, PUT, DELETE) our handler should respond to for a given path. This is crucial for building RESTful APIs, as different methods on the same URL perform different actions.

  • {id:[0-9]+}: This is a path variable in Gorilla Mux. It means that any sequence of one or more digits ([0-9]+) after /users/ will be captured as the id variable, which we can then retrieve using mux.Vars(r)[„id“].

  • fmt.Println(„Starting server on :8080“): A friendly message to let us know the server is starting.

  • log.Fatal(http.ListenAndServe(„:8080“, router)): This is the line that actually starts our web server.

  • :8080: The address and port our server will listen on.

  • router: We pass our Gorilla Mux router as the second argument. This tells the HTTP server to use our router to handle incoming requests.

  • log.Fatal(…): This is important. If http.ListenAndServe encounters an error (e.g., the port is already in use), it will return an error, and log.Fatal will print the error and exit the program.

How to Run This API

  • Save the code: Save the entire code block above as main.go in a new directory.

  • Initialize Go module: Open your terminal in that directory and run:

  • go mod init your-module-name
  • (Replace your-module-name with something like my-user-api).
  • go get github.com/gorilla/mux
  • Run the application:
go run main.go
  • You should see “Starting server on :8080”.

<img alt=““ height=“1″ src=“https://medium.com//stat?event=post.clientViewed&referrerSource=full_rss&postId=109a4249b47f“ width=“1″ />

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert