I have a restful API utilizing mux and mongo-driver. Following a tutorial, I attempted to setup the server and mongo client like so in the main package:
package main
import (
"context"
"fmt"
"net/http"
"time"
"github.com/gorilla/mux"
c "github.com/moonlightfight/elo-backend/config"
"github.com/moonlightfight/elo-backend/routes/admin"
"github.com/moonlightfight/elo-backend/routes/tournament"
"github.com/spf13/viper"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
var client *mongo.Client
func main() {
// Set the file name of the configurations file
viper.SetConfigName("config")
// Set the path to look for the configurations file
viper.AddConfigPath(".")
// Enable VIPER to read Environment Variables
viper.AutomaticEnv()
viper.SetConfigType("yml")
var configuration c.Configurations
if err := viper.ReadInConfig(); err != nil {
fmt.Printf("Error reading config file, %s", err)
}
err := viper.Unmarshal(&configuration)
if err != nil {
fmt.Printf("Unable to decode into struct, %v", err)
}
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
clientOptions := options.Client().ApplyURI(fmt.Sprintf("mongodb+srv://%s:%[email protected]/%s?retryWrites=true&w=majority", configuration.Database.DBUser, configuration.Database.DBPass, configuration.Database.DBName))
port := fmt.Sprintf(":%d", configuration.Server.Port)
mongo.Connect(ctx, clientOptions)
router := mux.NewRouter()
router.HandleFunc("/api/admin", admin.CreateAdminEndpoint).Methods("POST")
router.HandleFunc("/api/admin/login", admin.AdminLoginEndpoint).Methods("POST")
router.HandleFunc("/api/tournament/getfromweb", tournament.GetTournamentData).Methods("GET")
fmt.Printf("server listening on http://localhost%v", port)
http.ListenAndServe(port, router)
}
Now, in order to manage my code more concisely, I set up modules (as you can see in the imports on main) to handle the functions that mux will use for the endpoints.
On one specific case (handling the "/api/admin" endpoint:
package admin
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"time"
"github.com/dgrijalva/jwt-go"
c "github.com/moonlightfight/elo-backend/config"
m "github.com/moonlightfight/elo-backend/models"
"github.com/spf13/viper"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"golang.org/x/crypto/bcrypt"
)
var client *mongo.Client
// other code here
func CreateAdminEndpoint(response http.ResponseWriter, request *http.Request) {
response.Header().Set("content-type", "application/json")
var admin m.Admin
err := json.NewDecoder(request.Body).Decode(&admin)
if err != nil {
log.Println(err)
}
// encrypt user password
admin.Password = HashPassword(admin.Password)
fmt.Println(client)
collection := client.Database("test").Collection("Admin")
ctx, ctxErr := context.WithTimeout(context.Background(), 5*time.Second)
if ctxErr != nil {
log.Println(ctxErr)
}
result, resErr := collection.InsertOne(ctx, admin)
if resErr != nil {
log.Println(resErr)
}
json.NewEncoder(response).Encode(result)
}
And when ran, I receive the following error:
2021/06/05 02:02:39 http: panic serving [::1]:53359: runtime error: invalid memory address or nil pointer dereference
This points to the line where I define collection in the endpoint function, which logged as having a nil value. I am clearly not getting the mongo client properly defined in the module, and not sure of the best practice for maintaining this client connection across multiple modules.
The standard way of doing this while avoiding globals would be to define a struct
that represents your server, and its methods would be the handlers. Then the methods share the struct's data, and you place things like your mongo client in there.
Something like this (in your admin
package):
type Server struct {
client *mongo.Client
}
func NewServer(client *mongo.Client) *Server {
return &Server{client: client}
}
func (srv *Server) CreateAdminEndpoint(response http.ResponseWriter, request *http.Request) {
// ...
// use srv.client here
//
}
And now in main
, you create the server like this:
client, err := mongo.Connect(...)
// handle err!
srv := admin.NewServer(client)
router := mux.NewRouter()
router.HandleFunc("/api/admin", srv.CreateAdminEndpoint).Methods("POST")