10

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

BlackCat
  • 160
  • 2
  • 8
  • 2
    Are you certain that the only thing you changed is to move a single function from main.go to it's own package and then in main.go you imported that package and prefixed the function's name with the package's name? Can you provide an [mcve](https://stackoverflow.com/help/minimal-reproducible-example) of both the working and the broken code? – mkopriva Mar 16 '21 at 19:17
  • 1
    What about `users.LoginRequired`? What does that do? Was it there when your code worked? What happens if you do `router.Get("/users/{user_id}", users.GetUser)` without using `router.With(users.LoginRequired)...`? – mkopriva Mar 16 '21 at 19:25
  • I have updated my post and removed everything except the GetUser function – BlackCat Mar 16 '21 at 19:39
  • 4
    `"github.com/go-chi/chi/v5"` != `"github.com/go-chi/chi"`. Use one version for your whole app and you should be ok. – mkopriva Mar 16 '21 at 19:43
  • 1
    I am so stupid, thanks a lot – BlackCat Mar 16 '21 at 19:46
  • 2
    Thank you! I thought I was going insane. The issue for me is that I suspect VS code auto-import doesn't find the `v5` version. So I've switched to whatever VS code auto-import finds and now it works. – Michael Tiller Jul 01 '21 at 15:32
  • 1
    I did the same stupid thing of having different versions because of VS Code auto import. – Lincoln Mullen Jan 17 '22 at 00:01
  • 1
    Wow! Thank you so much! I just spent hours trying to figure this one out. I suspect the VS code auto import is the culprit. – hanbot Apr 10 '23 at 08:44

1 Answers1

20

Adding answer because the comments just saved me!

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!

Greg the Incredulous
  • 1,676
  • 4
  • 29
  • 42