I'm trying to write a restful api on golang. For http router I use gin-gonic, to interact with the database I use gorm.
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
)
var db *gorm.DB
type Person struct {
ID uint `json:"id"`
FirstName string `json:"firstname"`
LastName string `json:"lastname"`
}
func main() {
// NOTE: See we’re using = to assign the global var
// instead of := which would assign it only in this function
db, err := gorm.Open("postgres", fmt.Sprintf("host=localhost sslmode=disable user=postgres password="))
if err != nil {
fmt.Println(err)
}
defer db.Close()
db.AutoMigrate(&Person{})
r := gin.Default()
r.GET("/people/", GetPeople)
r.GET("/people/:id", GetPerson)
r.POST("/people", CreatePerson)
r.Run(":8080")
}
func CreatePerson(c *gin.Context) {
var person Person
c.BindJSON(&person)
db.Create(&person)
c.JSON(200, person)
}
func GetPerson(c *gin.Context) {
id := c.Params.ByName("id")
var person Person
if err := db.Where("id = ?", id).First(&person).Error; err != nil {
c.AbortWithStatus(404)
fmt.Println(err)
} else {
c.JSON(200, person)
}
}
func GetPeople(c *gin.Context) {
var people []Person
if err := db.Find(&people).Error; err != nil {
c.AbortWithStatus(404)
fmt.Println(err)
} else {
c.JSON(200, people)
}
}
How do I split the code into multiple files so that a separate resource is in a separate file? How to use the router and database in another file?
UPDATE
With structure like this:
.
└── app
├── users.go
├── products.go
└── main.go
I have 2 problems:
db == nil in products.go and users.go
Redeclaration function (get, create ...) in different files, this solve by prefix in function declaration like CreateUser, CreateProduct, etc. But it may be solved by put code into another packages
main.go
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
)
var (
db *gorm.DB
r *gin.Engine
)
func init() {
db, err := gorm.Open("postgres", fmt.Sprintf("host=localhost sslmode=disable user=postgres password="))
if err != nil {
fmt.Println(err)
}
defer db.Close()
r = gin.Default()
}
func main() {
r.Run(":8080")
}
products.go
package main
import (
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
)
type Product struct {
gorm.Model
Code string
Price uint
}
func init() {
db.AutoMigrate(&Product{}) // db -> nil
r.GET("/products", get)
}
func get(c *gin.Context) {
var product Product
db.First(&product, 1)
c.JSON(200, gin.H{
"product": product,
})
}
users.go
package main
import (
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
)
type User struct {
gorm.Model
Name string
}
func init() {
db.AutoMigrate(&User{}) // db -> nil
r.GET("/users", get)
}
// ./users.go:19: get redeclared in this block
// previous declaration at ./products.go:20
func get(c *gin.Context) {
var user User
db.First(&user, 1)
c.JSON(200, gin.H{
"user": user,
})
}
Since your db var is defined at the package level it's basically a global for that package an can be referenced in any file that lives in that package.
For example, in a project like this:
.
└── app
├── a.go
├── b.go
├── c.go
└── main.go
If db is defined inside main.go at the package level, as in your example, then code in files a.go, b.go, and c.go can use db.
It works the other way as well, any resource handlers defined in a.go, b.go, and c.go can be reference in main.go. Which means that in each of those files you can define a function that takes a router, the gin router, and sets the corresponding handlers, then inside main.go's main function you call those functions passing in the router r.
Update
First off, you're calling defer db.Close() inside of you init function, which means that right after init returns your db gets closed which is absolutely not what you want. Using defer db.Close() in main is fine because main terminates when your app terminates, closing the db at that point makes sense, but when init terminates your app didn't even start properly, the main is just getting executed and you still need your db.
If you want to use the init functions in each file to do initialization specific to that file, you have to ensure that whatever those init functions depend on, is initialized before they get executed.
In your example all of your init functions depend on db and r so you need to make sure these two are not nil. I'm not exactly sure what, in Go, the order of execution is for multiple init functions in a single package but what I know for sure is that package level variable expressions get initialized before the init functions are executed.
So what you can do is to use a function call to initialize the two package level variables like so:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
)
var (
db = func() *gorm.DB {
db, err := gorm.Open("postgres", fmt.Sprintf("host=localhost sslmode=disable user=postgres password="))
if err != nil {
// if you can't open a db connection you should stop the app,
// no point in continuing if you can't do anything useful.
panic(err)
}
return db
}() // <- call the anon function to get the db.
r = gin.Default()
)
func main() {
// you can call defer db.Close() here but you don't really need to
// because after main exists, that is, your app terminates, db
// will be closed automatically.
r.Run(":8080")
}
As to your second problem, in Go init is a special case and by that I mean that you can have multiple init functions inside a single package, and even inside a single file. This is not true of any other identifiers that you declare.
That means that inside a package, and declared at the package level, you can have only one db identifier, one get identifier, only one User identifier, etc. Whether you use suffiex e.g. getUser or packages user.Get is entirely up to you.
Note that you can redeclare an identifier in another scope, let's say you have type User struct { ... at the package level, and then a function declared in the same pacakge can inside its own scope declare a variable like so var User = "whatever", although it's probably not the best idea it will compile.
For more details see:
Package initialization
Update 2
If you want to split your code into multiple packages you just put your files into separate folders and make sure that the package declaration at the top of your file has the correct package name.
Here's an example:
└── app/
├── main.go
├── product/
│ ├── product.go
│ └── product_test.go
└── user/
├── user.go
└── user_test.go
Now your app/user/user.go code could look something like this.
package user
import (
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
)
var db *gorm.DB
type User struct {
gorm.Model
Name string
}
// custom and exported Init function, this will not be called automatically
// by the go runtime like the special `init` function and therefore must be called
// manually by the package that imports this one.
func Init(gormdb *gorm.DB, r *gin.Engine) {
db = gormdb // set package global
db.AutoMigrate(&User{})
r.GET("/users", get)
}
func get(c *gin.Context) {
var user User
db.First(&user, 1)
c.JSON(200, gin.H{
"user": user,
})
}
your app/product/product.go ...
package product
import (
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
)
var db *gorm.DB
type Product struct {
gorm.Model
Code string
Price uint
}
// custom and exported Init function, this will not be called automatically
// by the go runtime like the special `init` function and therefore must be called
// manually by the package that imports this one.
func Init(gormdb *gorm.DB, r *gin.Engine) {
db = gormdb // set package global
db.AutoMigrate(&Product{})
r.GET("/products", get)
}
func get(c *gin.Context) {
var product Product
db.First(&product, 1)
c.JSON(200, gin.H{
"product": product,
})
}
And your entry point at app/main.go ...
package main
import (
"fmt"
// This assumes that the app/ folder lives directly in $GOPATH if that's
// not the case the import won't work.
"app/product"
"app/user"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
)
func main() {
db, err := gorm.Open("postgres", fmt.Sprintf("host=localhost sslmode=disable user=postgres password="))
if err != nil {
fmt.Println(err)
}
defer db.Close()
r := gin.Default()
// manually initialize imported packages
user.Init(db, r)
product.Init(db, r)
r.Run(":8080")
}
Related
I have declared a global var as suggested in this question Is it necessary to put templates into a map for reusing in Go?
I declared the global var in my main package before func main() but it is still not declared in another package.
package main
import{
"html/template"
.....
)
var tmpl = template.New("master")
func main() {
func init() {
_, err := tmpl.ParseGlob("templates/*.html")
if err != nil {
log.Fatalln("Error loading templates:", err)
}
....
}
In another package I write inside a function:
tmpl.ExecuteTemplate(w, "venue-index.html", res)
but I get an error
tmpl: undefined
I realise that there are other similar questions but the answers have not solved my problem. What am I doing wrong?
tmpl will not be visible from another package. To import variables from other packages you have to refer to them by a fully qualified name and have the variables start with an uppercase letter, pack.Tmpl etc.
Also it's impossible to import variables from main into other packages.
This is the solution I used for declaring templates in a helper package:
package helpers
import (
"html/template"
"log"
)
// Tmpl global template var
var Tmpl = template.New("master")
func init() {
_, err := Tmpl.ParseGlob("templates/*.html")
if err != nil {
log.Fatalln("Error loading templates:", err)
}
}
Then in my controllers package:
package controllers
import (
"myapp/helpers"
)
someFunc() {
....
helpers.Tmpl.ExecuteTemplate(w, "header.html", nil)
....
}
I've decided to try breaking my project into a MVC type view, so to start I wanted to put all my routing into a controller folder and I put my database connection into another folder titled db.
I can't figure out how to make the database connection work smoothly. Back when all of the files were in my package main I just called InitDb() in main and in all of my other files in the main package I had access to the db variable. Now that I made db it's down package and imported it, nothing is recognized.
I also don't know where to call InitDb() and defer db.Close() anymore since it's not all in main.
db/db.go
package database
import (
"fmt"
"database/sql"
)
var db *sql.DB
const (
dbhost = "localhost"
dbuser = "root"
dbpass = "password"
dbname = "user"
)
func InitDb() {
var err error
connectionString := fmt.Sprintf("%s:%s#/%s", dbuser, dbpass, dbname)
db, err = sql.Open("mysql", connectionString)
if err != nil {
panic(err)
}
err = db.Ping()
if err != nil {
panic(err)
}
fmt.Println("Successfully connected!")
}
controllers/index.go
package controllers
import (
"net/http"
"fmt"
"db"
"github.com/gorilla/mux"
)
func TestHandler(r *mux.Router)
r.HandleFunc("/index", test).Methods("GET")
}
func test(w http.ResponseWriter, r *http.Request) {
// database undefined
err := database.QueryRow("some sql statement")
CheckErr(err)
}
main.go
package main
import (
"net/http"
_ "github.com/go-sql-driver/mysql"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
controllers.TestHandler(r)
log.Fatal(http.ListenAndServe("localhost:8000", r))
}
While not required it is a good idea to have the package name be the same as the folder in which it lives, so either do:
db/db.go
package db
// ...
Or do:
database/db.go
package database
// ...
I would not recommend mixing the two.
You can have your database package export a Close function and have main call it when it's done.
database/db.go
package database
import (
"fmt"
"database/sql"
)
var db *sql.DB
func Close() {
db.Close()
}
// ...
main.go
package main
import "database"
func main() {
defer database.Close()
// ...
}
Or just don't close it in this case. When main exits the *sql.DB does not stay alive outside your program, it will not take up connection slots if the program is not running. Closing makes sense only if you're using multiple instances of *sql.DB and there is danger that they will start blocking while waiting for a connection. If you have only one that's shared by the whole program then you should be ok not calling defer close.
I understand the problem, as per the answer here, however, I could really use help or a more detailed code explanation of how it's overcome.
My situation is this: I used to have models and controllers separated, and in my models package I had a datastore.go file containing an interface of all the model functions:
package models
type DSDatabase interface {
CreateUser(ctx context.Context, username string, password []byte) (*datastore.Key, error)
// More model functions
}
type datastoreDB struct {
client *datastore.Client
}
var (
DB DSDatabase
_ DSDatabase = &datastoreDB{}
)
func init() {
// init datastore
}
This was all fine because the model functions were also located within the models package, so my functions in the controller package could freely call models.DB.CreateUser(ctx, "username", []byte("password")).
Now, I have decided to move all the above code to a datastore package, whereas the model for CreateUser is located in a user package. In other words, package user now contains both controller and model functions, for which the controller related functions rely on datastore package, while the DSDatabase interface rely on the user model functions.
I would really appreciate help figuring out how to overcome the import cycle while keeping the DSDatastore interface separate from all the other packages such as home and user.
in case the above is not clear enough, the above code has changed to:
package datastore
import (
"github.com/username/projectname/user"
)
type DSDatabase interface {
user.CreateUser(ctx context.Context, username string, passwoUserRegister(ctx context.Context, username string, password []byte) (*datastore.Key, error)
}
...
while in my user package I have this in a controller-related file:
package user
import (
"github.com/username/projectname/datastore"
)
func CreateUserPOST(w http.ResponseWriter, r *http.Request) {
// get formdata and such
datastore.DB.CreateUser(ctx, "username", []byte("password"))
}
and in another model-related file I have:
package user
import (
"github.com/username/projectname/datastore"
)
func (db *datastore.datastoreDB) CreateUser(ctx context.Context, username string) (*User, error) {
key := datastore.NameKey("User", username, nil)
var user User
err := db.client.Get(ctx, key, &user)
if err != nil {
return nil, err
}
return &user, nil
}
Which of course results in an import cycle, that I sadly can't figure out how to overcome..
First things first, you cannot define a method, in pacakge A, on a type declared in package B.
So this...
package user
import (
"github.com/username/projectname/datastore"
)
func (db *datastore.datastoreDB) CreateUser(ctx context.Context, username string) (*User, error) {
key := datastore.NameKey("User", username, nil)
var user User
err := db.client.Get(ctx, key, &user)
if err != nil {
return nil, err
}
return &user, nil
}
...should not even compile.
This here...
package datastore
import (
"github.com/username/projectname/user"
)
type DSDatabase interface {
user.CreateUser(ctx context.Context, username string, passwoUserRegister(ctx context.Context, username string, password []byte) (*datastore.Key, error)
}
...this is also invalid Go code.
As to your question... one thing you could do is to define the Datastore interface inside the user package and have the implementation live in another package, this lends itself nicely for when you need different implementations of one interface. If you do this your user package does not need to know about the datastore package anymore, the datastore package still has to know about the user package though, which is a OK.
An example:
package user
import (
"context"
)
type DSDatabase interface {
CreateUser(ctx context.Context, username string, password []byte) (*User, error)
// ...
}
// This can be set by the package that implements the interface
// or by any other package that imports the user package and
// a package that defines an implementation of the interface.
var DB DSDatabase
type User struct {
// ...
}
func CreateUserPOST(w http.ResponseWriter, r *http.Request) {
// get formdata and such
DB.CreateUser(ctx, "username", []byte("password"))
}
The package with the implementation of the interface:
package datastore
import (
"context"
"github.com/username/projectname/user"
)
// DB implements the user.DSDatabase interface.
type DB struct { /* ... */ }
func (db *DB) CreateUser(ctx context.Context, username string) (*user.User, error) {
key := datastore.NameKey("User", username, nil)
var user user.User
err := db.client.Get(ctx, key, &user)
if err != nil {
return nil, err
}
return &user, nil
}
func init() {
// make sure to initialize the user.DB variable that
// is accessed by the CreateUserPOST func or else you'll
// get nil reference panic.
user.DB = &DB{}
}
I'm trying to write my first beego web app and I'm trying to register some models, i register them in models/model.go's init() function, but when I run the command bee run, I get the following error:
no Model found, need register your model
main.go:
package main
import (
"fmt"
_ "test_blog/routers"
"time"
_ "github.com/lib/pq"
"github.com/astaxie/beego"
"github.com/astaxie/beego/orm"
)
func init() {
orm.RegisterDriver("postgres", orm.DRPostgres)
maxIdle := 30
maxConn := 30
orm.RegisterDataBase(
"default",
"postgres",
"postgres://user:password#localhost/test_db", maxIdle, maxConn
)
orm.DefaultTimeLoc = time.UTC
}
func main() {
// Database alias.
name := "default"
// Drop table and re-create.
force := false
// Print log.
verbose := false
// Error.
err := orm.RunSyncdb(name, force, verbose)
if err != nil {
fmt.Println(err)
}
beego.Run()
}
Note: force & verbose both were set to true before running bee run for the first time.
models.go:
package main
import "github.com/astaxie/beego/orm"
type User struct {
Name string
Posts []*Post `orm:"reverse(many)"`
}
type Post struct {
Title string `orm:"size(50)"`
Text string `orm:"size(4000)"`
Created time.Time `orm:"auto_now_add;type(datetime)"`
Updated time.Time `orm:"auto_now;type(datetime)"`
Author *User `orm:"rel(fk)"`
}
func init() {
orm.RegisterModel(new(User), new(Post))
}
try this:
in models.go, change package main to package models
in main.go, add import _ "test_blog/models"
I'm using Gin, https://gin-gonic.github.io/gin/, to build a simple RESTful JSON API with Golang.
The routes are setup with something like this:
func testRouteHandler(c *gin.Context) {
// do smth
}
func main() {
router := gin.Default()
router.GET("/test", testRouteHandler)
router.Run(":8080")
}
My question is how can I pass down an argument to the testRouteHandler function? For example a common database connection could be something that one wants to reuse among routes.
Is the best way to have this in a global variable? Or is there some way in Go to pass along an extra variable to the testRouteHandler function? Are there optional arguments for functions in Go?
PS. I'm just getting started in learning Go, so could be something obvious that I'm missing :)
I would avoid stuffing 'application scoped' dependencies (e.g. a DB connection pool) into a request context. Your two 'easiest' options are:
Make it a global. This is OK for smaller projects, and *sql.DB is thread-safe.
Pass it explicitly in a closure so that the return type satisfies gin.HandlerFunc
e.g.
// SomeHandler returns a `func(*gin.Context)` to satisfy Gin's router methods
// db could turn into an 'Env' struct that encapsulates all of your
// app dependencies - e.g. DB, logger, env vars, etc.
func SomeHandler(db *sql.DB) gin.HandlerFunc {
fn := func(c *gin.Context) {
// Your handler code goes in here - e.g.
rows, err := db.Query(...)
c.String(200, results)
}
return gin.HandlerFunc(fn)
}
func main() {
db, err := sql.Open(...)
// handle the error
router := gin.Default()
router.GET("/test", SomeHandler(db))
router.Run(":8080")
}
Using the link i posted on comments, I have created a simple example.
package main
import (
"log"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/mattn/go-sqlite3"
)
// ApiMiddleware will add the db connection to the context
func ApiMiddleware(db gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("databaseConn", db)
c.Next()
}
}
func main() {
r := gin.New()
// In this example, I'll open the db connection here...
// In your code you would probably do it somewhere else
db, err := gorm.Open("sqlite3", "./example.db")
if err != nil {
log.Fatal(err)
}
r.Use(ApiMiddleware(db))
r.GET("/api", func(c *gin.Context) {
// Don't forget type assertion when getting the connection from context.
dbConn, ok := c.MustGet("databaseConn").(gorm.DB)
if !ok {
// handle error here...
}
// do your thing here...
})
r.Run(":8080")
}
This is just a simple POC. But i believe it's a start.
Hope it helps.
Late to the party, so far here is my proposal. Encapsulate methods into the object with private/public vars in it:
package main
import (
"log"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/mattn/go-sqlite3"
)
type HandlerA struct {
Db gorm.DB
}
func (this *HandlerA) Get(c *gin.Context) {
log.Info("[%#f]", this.Db)
// do your thing here...
}
func main() {
r := gin.New()
// Init, should be separate, but it's ok for this sample:
db, err := gorm.Open("sqlite3", "./example.db")
if err != nil {
log.Fatal(err)
}
Obj := new(HandlerA)
Obj.Db = db // Or init inside Object
r := gin.New()
Group := r.Group("api/v1/")
{
Group.GET("/storage", Obj.Get)
}
r.Run(":8080")
}
Handler closures are a good option, but that works best when the argument is used in that handler alone.
If you have route groups, or long handler chains, where the same argument is needed in multiple places, you should set values into the Gin context.
You can use function literals, or named functions that return gin.HandlerFunc to do that in a clean way.
Example injecting configs into a router group:
Middleware package:
func Configs(conf APIV1Config) gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("configKey", conf) // key could be an unexported struct to ensure uniqueness
}
}
Router:
conf := APIV1Config{/* some api configs */}
// makes conf available to all routes in this group
g := r.Group("/api/v1", middleware.Configs(conf))
{
// ... routes that all need API V1 configs
}
This is also easily unit-testable. Assuming that you test the single handlers, you can set the necessary values into the mock context:
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Set("configKey", /* mock configs */)
apiV1FooHandler(c)
Now in the case of application-scoped dependencies (db connections, remote clients, ...), I agree that setting these directly into the Gin context is a poor solution.
What you should do then, is to inject providers into the Gin context, using the pattern outlined above:
Middleware package:
// provider could be an interface for easy mocking
func DBProvider(provider database.Provider) gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("providerKey", provider)
}
}
Router:
dbProvider := /* init provider with db connection */
r.Use(DBProvider(dbProvider)) // global middleware
// or
g := r.Group("/users", DBProvider(dbProvider)) // users group only
Handler (you can greatly reduce the boilerplate code by putting these context getters in some helper function):
// helper function
func GetDB(c *gin.Context) *sql.DB {
provider := c.MustGet("providerKey").(database.Provider)
return provider.GetConn()
}
func createUserHandler(c *gin.Context) {
db := GetDB(c) // same in all other handlers
// ...
}
I like wildneuro's example but would do a one liner to setup the handler
package main
import (
"log"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/mattn/go-sqlite3"
)
type HandlerA struct {
Db gorm.DB
}
func (this *HandlerA) Get(c *gin.Context) {
log.Info("[%#f]", this.Db)
// do your thing here...
}
func main() {
r := gin.New()
// Init, should be separate, but it's ok for this sample:
db, err := gorm.Open("sqlite3", "./example.db")
if err != nil {
log.Fatal(err)
}
r := gin.New()
Group := r.Group("api/v1/")
{
Group.GET("/storage", (&HandlerA{Db: db}).Get)
}
r.Run(":8080")
}
Let me try to explain in detail so that you won't get confused.
Depending on the incoming route, you want to call a controller function. Lets say your incoming route is /books and your controller is BooksController
Your BooksController will try to fetch the books from the database and returns a response.
Now, you want this a handler within your BooksController so that you can access database.
I would do something like this. Let's assume that you are using dynamoDB and the aws sdk provides *dynamodb.DynamoDB. Depending on your db, change this variable.
Create a struct as below.
type serviceConnection struct {
db *dynamoDB.DynamoDB
// You can have all services declared here
// which you want to use it in your controller
}
In your main function, get the db connection information. Let's say you already have a function initDatabaseConnection which returns a handler to db, something like below.
db := initDatabaseConnection() -> returns *dynamodb.DynamoDB
Set db to a struct variable.
conn := new(serviceConnection)
conn.db = db
Call the gin request method with a receiver handler as below.
r := gin.Default()
r.GET("/books", conn.BooksController)
As you see, the gin handler is a controller method which has your struct instance as a receiver.
Now, create a controller method with serviceConnection struct receiver.
func (conn *serviceConnection) BooksController(c *gin.Context) {
books := getBooks(conn.db)
}
As you see here, you have access to all the serviceConnection struct variables and you can use them in your controller.
Alright, I have given you a simple example. It should work. You can extend it as per your need
func main() {
router := gin.Default()
router.GET("/test/:id/:name", testRouteHandler)
router.Run(":8080")
}
func testRouteHandler(c *gin.Context) {
id := c.Params.ByName("id")
name := c.Params.ByName("name")
}
Now you will have to call your handler as below
http://localhost:8080/test/1/myname