programming

03 Learn About Route Pattern

Use of Named Parameter

Does the Pattern Router have a Pattern? That’s right, every endpoint has a parameter pattern in a URL and is often called a Named Parameter. Named Parameter is a pattern for creating parameters using names. Each parameter name begins with a colon : followed by the parameter name. For example, an example like the one below.

Pattern/user/:user
/user/santeknomatch
/user/kamumatch
/user/santekno/profilenot match
/user/not match

We will try to implement how these Named Parameter are created in Golang. First, we need to create a handler function as below.

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

Next, create a GET router method in the main.go file to initialize the Named Parameter endpoint that we created as below.

router.GET("/product/:id/items/:itemId", NamedParameterHandler)

Then run the program and do a test using curl.

curl --location --request GET 'localhost:8080/product/1/items/2'

Then the results will appear as below.

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

Apart from manual testing, we make sure we also make unit tests from the handlers that we have created.

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

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

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

Use of Catch All Parameters

Apart from Named Parameter, the route pattern also has something called Catch All Parameters which is to catch all parameters which usually start with a star *, then followed by the name of the parameter and must be at the last position of the URL. For more details, here is an example of the pattern below.

Pattern/src/*filepath
/user/not match
/user/namafilematch
/user/subdirektori/namafilematch

The way we implement Catch All Parameters is the same as Named Parameter but the difference is in the sign, if Named Parameter uses a colon : whereas in Catch All Parameters it uses an asterisk *.

OK, let’s try creating a handler function first.

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

Then we add a router with the GET method

router.GET("/images/*image", CatchAllParameterHandler)

Run the program and we try to do a test using this curl.

curl --location --request GET 'localhost:8080/images/small/image.jpg'

After running it, you will see output and print something like this.

➜  santekno-hugo git:(main) ✗ curl --location --request GET 'localhost:8080/images/small/image.jpg'
Image /small/image.jpg%

We also add unit tests to the handler that we have created to ensure that our code really meets our expectations.

func TestCatchAllParameterHandler(t *testing.T) {
	type args struct {
		image string
	}
	tests := []struct {
		name string
		args args
		want string
	}{
		{
			name: "get image",
			args: args{
				image: "photo.jpg",
			},
			want: "Image photo.jpg",
		},
		{
			name: "get image with path",
			args: args{
				image: "small/photo.jpg",
			},
			want: "Image small/photo.jpg",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			request := httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost/images/%s", tt.args.image), nil)
			recorder := httptest.NewRecorder()
			CatchAllParameterHandler(recorder, request, httprouter.Params{
				{
					Key:   "image",
					Value: tt.args.image,
				},
			})

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

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