gogo-chi

chi.URLParam not working when handler is defined outside main package


So I am new to go and I currently try to build a little REST-API using chi (and I love it). Yesterday I run into a problem, that I cannot quite understand.

In my little test-project I have a main.go file which contains the main function with router instantiation, adding middlewares and starting the server:

func main() {
    router := chi.NewRouter()
    // Middleware
    router.Use(middleware.RequestID)
    router.Use(middleware.RealIP)
    router.Use(middleware.Logger)
    router.Use(middleware.Recoverer)
    // Routes
    router.Post("/login", users.Login)
    router.Post("/register", users.Register)
    router.With(users.LoginRequired).Route("/users", func(r chi.Router) {
        r.Get("/{user_id}", users.GetUser)
    })
    // Start Server
    port := ":8080"
    log.Printf("Server starting at port %v\n", port)
    log.Fatal(http.ListenAndServe(port, router))
}

First the problem didn't exist because I defined all the handler functions within my main.go file and the GetUser-function worked as expected and returned a user from my "Database" (array with 3 users):

func GetUser(w http.ResponseWriter, r *http.Request) {
    uID := chi.URLParam(r, "user_id") // Problem when not in main -> uID = ""
    id, err := strconv.Atoi(uID)
    if err != nil {
        log.Printf("Error while parsing int: %v\n", err)
        // TODO: return error 400
    }
    user := DataBase[id-1]
    response, err := json.Marshal(user)

    if err != nil {
        log.Printf("Error while marshalling user: %v\n", err)
    }
    w.Write(response)
}

As soon as I moved this function out of the main.go file into another package called users the chi.URLParam function returns an empty string and cannot find the URLParam anymore. I read it has something to do with the context, but I cannot wrap my head around that I have to place functions inside the main-file if I want to use the chi functions.

Am I missing something?

UPDATE

As requested I removed everything except the GetUser function. My main.go file currently looks like this:

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "strconv"

    "github.com/MyUserName/MyProjectName/internals/users"
    "github.com/go-chi/chi/v5"
)

func GetUser(w http.ResponseWriter, r *http.Request) {

    id, err := strconv.Atoi(chi.URLParam(r, "user_id"))
    if err != nil {
        log.Printf("Error while parsing int: %v\n", err)
        // TODO: return error 400
    }
    log.Printf("ID=%v, Current Database=%v\n", id, users.DataBase)
    user := users.DataBase[id-1]
    response, err := json.Marshal(user)

    if err != nil {
        log.Printf("Error while marshalling user: %v\n", err)
    }
    w.Write(response)
}

func main() {
    router := chi.NewRouter()
    // Routes
    router.Get("/users/{user_id}", GetUser)
    // Start Server
    port := ":8080"
    log.Printf("Server starting at port %v\n", port)
    log.Fatal(http.ListenAndServe(port, router))
}

and my users package looks like this:

package users

import (
    "encoding/json"
    "log"
    "net/http"
    "strconv"

    "github.com/MyUserName/MyProjectName/internals/models"
    "github.com/go-chi/chi"
)

var (
    DataBase = make([]models.User, 0)
)

func GetUser(w http.ResponseWriter, r *http.Request) {

    id, err := strconv.Atoi(chi.URLParam(r, "user_id"))
    if err != nil {
        log.Printf("Error while parsing int: %v\n", err)
        // TODO: return error 400
    }
    log.Printf("ID=%v, Current Database=%v\n", id, DataBase)
    user := DataBase[id-1]
    response, err := json.Marshal(user)

    if err != nil {
        log.Printf("Error while marshalling user: %v\n", err)
    }
    w.Write(response)
}

func init() {
    initUser := []models.User{
        {
            ID:       1,
            UserName: "john",
            Password: "doe",
        },
        {
            ID:       2,
            UserName: "max",
            Password: "mustermann",
        },
        {
            ID:       3,
            UserName: "jane",
            Password: "doe",
        },
    }
    for _, user := range initUser {
        DataBase = append(DataBase, user)
    }
    log.Println("Initializing Database")
}

When I use the function from the users package it does not work and is still an empty string, if I use the function from the main.go file it works.

UPDATE

So apparently I am to stupid to import the same packages twice. In my main file I used "github.com/go-chi/chi/v5" and in my users package I used "github.com/go-chi/chi". Using the same resolved the issue, thanks a lot


Solution

  • Adding answer because the comments just saved me! Thanks to mkopriva's comment.

    Check that all files in your go solution have the same version of chi in use. If you're using VSCode it may import a different version than you expect. In my code I had one file with

    import(
      "github.com/go-chi/chi"
    )
    

    and in the other

    import(
      "github.com/go-chi/chi/v5"
    )
    

    This meant that when I was calling into middleware function to extract URLParams the context was not finding a value.

    TL;DR Check that all files use same version of Chi!