programming

02 Learning About HTTP Router Params

Use of HTTP Router Params

The httprouter.Handle has an additional parameter, namely Params, which is used to store parameters sent from the client, but this Params is not a query for parameters but is a parameter from the URL. Sometimes we need to create URLs that are not fixed or can change, for example /product/1, /product/2 and so on.

ServerMux does not support this, so on the Router there are additional parameters, one of which is to handle things like this. However, for the Router we need to add something to the Route so that the URL Path becomes dynamic.

How to Implement

Previously we created an easy sample handler and how to call a function so that it can run on the project. Next we will try to create an endpoint that uses a dynamic URL as explained above.

Before going there, we will first change our code so that it is neatly arranged, namely the endpoint that we created in the previous post. become like this.

package main

import (
	"net/http"

	"github.com/julienschmidt/httprouter"
)

func main() {
	router := httprouter.New()

	router.GET("/", SmpleGetHandler)
	router.POST("/", SamplePostHandler)

	server := http.Server{
		Handler: router,
		Addr:    "localhost:8080",
	}

	server.ListenAndServe()
}

The code above is the contents of the main.go file. Then we create a new file handler.go whose contents are as below.

package main

import (
	"fmt"
	"net/http"

	"github.com/julienschmidt/httprouter"
)

func SampleGetHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	fmt.Fprint(w, "Hello Get")
}

func SamplePostHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	fmt.Fprint(w, "Hello Post")
}

Once our code is neat, we will continue adding an endpoint that implements dynamic URL params. So, add the code below to the handler.go file.

func GetUsedParamsHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	text := "Product " + p.ByName("id")
	fmt.Fprint(w, text)
}

Then we call the handler function in the main function like this.

router.GET("/product/:id",GetUsedParamsHandler)

Run the project or program above then try using curl to access the endpoint that we have created like this.

➜  santekno-hugo git:(main) ✗ curl --location --request GET 'http://localhost:8080/product/1'            
Product 1%

If we change the parameter 1 then a display will appear that corresponds to the input, for example 2 then Product 2 will appear.

Added Unit Tests

After we create the handler params above, we will also try to ensure that our handler really meets the needs we want, namely by creating a unit test from the function we created earlier. The following is the unit test function for the code above.

func TestGetUsedParamsHandler(t *testing.T) {
	type args struct {
		params string
	}
	tests := []struct {
		name string
		args args
		want string
	}{
		{
			name: "test get params with product 1",
			args: args{
				params: "1",
			},
			want: "Product 1",
		},
		{
			name: "test get params with product 2",
			args: args{
				params: "2",
			},
			want: "Product 2",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			request := httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost/product/%s", tt.args.params), nil)
			recorder := httptest.NewRecorder()
			GetUsedParamsHandler(recorder, request, httprouter.Params{
				{
					Key:   "id",
					Value: tt.args.params,
				},
			})

			response := recorder.Result()
			body, _ := io.ReadAll(response.Body)
			bodyString := string(body)

			assert.Equal(t, tt.want, bodyString)
		})
	}
}

Then we will also add other unit tests to the functions we created previously like this.

func TestSampleGetHandler(t *testing.T) {
	tests := []struct {
		name string
		want string
	}{
		{
			name: "test get",
			want: "Hello Get",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			request := httptest.NewRequest(http.MethodGet, "http://localhost/", nil)
			recorder := httptest.NewRecorder()
			SampleGetHandler(recorder, request, nil)

			response := recorder.Result()
			body, _ := io.ReadAll(response.Body)
			bodyString := string(body)

			if !reflect.DeepEqual(bodyString, tt.want) {
				t.Errorf("response = %v, want %v", bodyString, tt.want)
			}

			assert.Equal(t, tt.want, bodyString)
		})
	}
}

func TestSamplePostHandler(t *testing.T) {
	tests := []struct {
		name string
		want string
	}{
		{
			name: "test post",
			want: "Hello Post",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			request := httptest.NewRequest(http.MethodPost, "http://localhost/", nil)
			recorder := httptest.NewRecorder()
			SamplePostHandler(recorder, request, nil)

			response := recorder.Result()
			body, _ := io.ReadAll(response.Body)
			bodyString := string(body)

			assert.Equal(t, tt.want, bodyString)
		})
	}
}
comments powered by Disqus