Reputation: 2978
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:
products.go
and users.go
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 packagesmain.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,
})
}
Upvotes: 0
Views: 2289
Reputation: 38343
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
.
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
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")
}
Upvotes: 2