Understanding the Type Mismatch in Golang httprouter.Handle


When developing RESTful APIs in Golang, using routers like github.com/julienschmidt/httprouter is quite common due to its speed and flexibility. However, one of the frequent issues developers encounter is the type mismatch error when trying to pass a function as an HTTP handler, as seen in the following error message:

cannot use bookController.FindById (value of type func(writer http.ResponseWriter, requests http.Request, params httprouter.Params)) as httprouter.Handle value in argument to router.GET

In this article, we will explain the root cause of this issue, why it happens, and how to fix it properly.

# The Problem

The error occurs because the function signature you provided does not match the expected function signature of httprouter.Handle. Let’s break this down:

# Expected Signature

In httprouter, a handler function is expected to have the following signature:

type Handle func(w http.ResponseWriter, r *http.Request, ps httprouter.Params)

This means that for any function you wish to use as an HTTP handler with httprouter, it must accept:

  • A http.ResponseWriter (to write the HTTP response),
  • A pointer to http.Request (to read the incoming HTTP request),
  • httprouter.Params (to handle URL parameters such as /book/:id).

# Your Function Signature

From the error message, the function you’re passing has a signature like this:

func(writer http.ResponseWriter, requests http.Request, params httprouter.Params)

The key differences are:

  • requests http.Request should be *http.Request: The http.Request should be passed by reference (pointer) to allow the function to read the request body and headers efficiently.
  • The function signature should match httprouter.Handle exactly: Even a slight variation, such as passing http.Request by value instead of by pointer, will lead to a compile-time error.

# Why This Happens

In Go, the function signatures must match exactly when passing them as arguments. The router expects your function to have a specific signature (func(w http.ResponseWriter, r *http.Request, ps httprouter.Params)), and if your function does not meet this, the compiler will throw an error. Go is a statically typed language, so even if there’s a minor difference in the type (such as passing by value versus passing by pointer), the program won’t compile.

# The Fix

To resolve this issue, you need to ensure that all functions you use as handlers with httprouter conform to the expected signature. Specifically, http.Request should be passed as a pointer (*http.Request), and the httprouter.Params parameter should remain in the third position.

Here’s an example of how you can adjust the function signature:

# Original (Incorrect) Code:

func (controller *BookController) FindById(writer http.ResponseWriter, requests http.Request, params httprouter.Params) {
// handler logic
}

# Corrected Code:

func (controller *BookController) FindById(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
// handler logic
}

Now, the request parameter is a pointer (*http.Request), and this signature matches what httprouter expects.

# Example of a Complete Solution

Let’s take an example where the controller has several methods (e.g., FindById, Create, Update, etc.). We’ll refactor all of them to ensure they follow the correct signature for httprouter.

package controllers

import (
"net/http"
"scrach_api/data/request"
"scrach_api/data/response"
"scrach_api/helper"
"scrach_api/services"
"strconv"

"github.com/julienschmidt/httprouter"
)

type BookController struct {
BookService services.BookService
}

func NewBookController(bookService services.BookService) *BookController {
return &BookController{BookService: bookService}
}

func (controller *BookController) Create(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
bookCreateRequest := request.BookCreateRequest{}
helper.ReadRequestBody(request, &bookCreateRequest)

controller.BookService.Create(request.Context(), bookCreateRequest)
webResponse := response.WebResponse{
Code: 200,
Status: "OK",
Data: nil,
}
helper.WriteResponseBody(writer, webResponse)
}

func (controller *BookController) FindById(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
bookId := params.ByName("bookId")
id, err := strconv.Atoi(bookId)
helper.PanicIfError(err)

result := controller.BookService.FindById(request.Context(), id)
webResponse := response.WebResponse{
Code: 200,
Status: "OK",
Data: result,
}
helper.WriteResponseBody(writer, webResponse)
}

In the corrected code:

  • We ensure that the http.Request is passed by pointer (*http.Request).
  • The httprouter.Params remains as the third argument.
  • All functions now match the expected signature of httprouter.Handle.

# Conclusion

The error you encountered is a common one in Go when working with HTTP routers like httprouter. Go’s strict type system enforces that function signatures must match exactly, and this often means ensuring that parameters *http.Request are passed as pointers, not values. By adjusting the function signature to match what httprouter expects, you can resolve the error and ensure that your handler functions are correctly passed to the router.

Understanding this issue is a valuable learning point when working with statically typed languages like Go, where small details in function signatures can lead to significant problems if overlooked.