The other day, I thought, "I don't plan to use it for business, but I should know about Go to some extent ...", so I implemented a simple CRUD using Go, so I will summarize how to do it as a memorandum. I will.
Basically, it is a little arranged by combining the contents of the following sites. Please refer to these sites as they have been very helpful in studying Go. Building a Go development environment with Docker Make a super simple web application with Go / Gin Introduction to Go Language-MySQL Connection- Create a DB container for docker-compose MySQL8.0 How to wait until MySQL starts with docker-compose up (2 types introduced)
First of all, we will launch Go with Docker. Directly under the working directory
DockerFile
FROM golang:latest
RUN mkdir /app
WORKDIR /app
docker-compose.yml
version: '3'
services:
go:
build:
context: .
dockerfile: DockerFile
command: /bin/sh -c "go run main.go"
stdin_open: true
tty: true
volumes:
- .:/app
main.go
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
Now when you do docker-compose up
, you'll see Hello, World! On the console.
If this comes out, the launch of Go is successful.
I will briefly explain each file.
・ DockerFile
Create a Go container (virtual environment).
By specifying WORKDIR / app
here, all subsequent operations will be performed under / app
.
・ Docker-compose.yml
Write the settings when launching the container created with DockerFile.
This will launch a container in DockerFile and hit the go run main.go
command in it to launch main.go.
・ Main.go I will write the processing related to Go here. This time, all you have to do is output Hello, World !.
Now that Go has started, let's create a web page using Go. This time I will use a framework called Gin. --Add installation to DockerFile --Added ports description to docker-compose.yml --Rewrite the contents of main.go --Create templates / index.html Please do.
DockerFile
FROM golang:latest
RUN mkdir /app
WORKDIR /app
RUN go get github.com/gin-gonic/gin
docker-compose.yml
version: '3'
services:
go:
build:
context: .
dockerfile: DockerFile
command: /bin/sh -c "go run main.go"
stdin_open: true
tty: true
volumes:
- .:/app
ports:
- 8080:8080
main.go
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.LoadHTMLGlob("templates/*.html")
router.GET("/", func(ctx *gin.Context){
ctx.HTML(200, "index.html", gin.H{})
})
router.Run()
}
templates/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Sample App</title>
</head>
<body>
<h1>Hello World!!!</h1>
</body>
</html>
with this
docker-compose build
docker-compose up -d
After going to and waiting for a while If you access [http: // localhost: 8080](http: // localhost: 8080), you will see Hello World !.
I will explain what I did this time.
This time I added a framework called Gin.
Gin is installed by the command go get github.com/gin-gonic/gin
after creating a container with DockerFile, and is called by main.go.
And the contents of templates are read in main.go,
router.GET("/", func(ctx *gin.Context){
ctx.HTML(200, "index.html", gin.H{})
})
Will be associated with templates / index.html for root ("/"). By the way, if you change the first argument ("/") of router.GET to "/ test" etc., it will not be [http: // localhost: 8080](http: // localhost: 8080) but [http: // localhost Index.html will be displayed at: 8080 / test](http: // localhost: 8080 / test).
Finally, add a port to docker-compose.yml to allow access to localhost: 8080.
At this point, you can create web pages with Go. However, in reality, when creating a Web service, connection with the DB is inevitable. So next, we will launch MySQL using Docker.
First in docker-compose.yml -Description of db container ・ Volume description Please add.
docker-compose.yml
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: go_database
MYSQL_USER: go_test
MYSQL_PASSWORD: password
TZ: 'Asia/Tokyo'
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
volumes:
- db-data:/var/lib/mysql
- ./db/my.cnf:/etc/mysql/conf.d/my.cnf
ports:
- 3306:3306
volumes:
db-data:
driver: local
Also, create a db directory and create the my.cnf file in it.
my.cnf
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_bin
default-time-zone = SYSTEM
log_timestamps = SYSTEM
default-authentication-plugin = mysql_native_password
[mysql]
default-character-set = utf8mb4
[client]
default-character-set = utf8mb4
(This area is the same as the reference page. Only the part related to the log did not work for some reason, so I removed it)
If you do docker-compose up -d
so far, the MySQL container should also start up.
Since there is only a description of the settings, the explanation here is omitted.
Since MySQL has started, I will connect it to Go immediately. This time we will use the sql driver and a framework called GORM for the connection. --Add installation to DockerFile --Added dependency description to docker-compose.yml --Added DB connection processing to main.go Please do.
DockerFile
FROM golang:latest
RUN mkdir /app
WORKDIR /app
RUN go get github.com/gin-gonic/gin
RUN go get github.com/go-sql-driver/mysql
RUN go get github.com/jinzhu/gorm
docker-compose.yml
version: '3'
services:
go:
build:
context: .
dockerfile: DockerFile
command: /bin/sh -c "go run main.go"
stdin_open: true
tty: true
volumes:
- .:/app
ports:
- 8080:8080
depends_on:
- "db"
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: go_database
MYSQL_USER: go_test
MYSQL_PASSWORD: password
TZ: 'Asia/Tokyo'
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
volumes:
- db-data:/var/lib/mysql
- ./db/my.cnf:/etc/mysql/conf.d/my.cnf
ports:
- 3306:3306
volumes:
db-data:
driver: local
main.go
package main
import (
"fmt"
"time"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db := sqlConnect()
defer db.Close()
router := gin.Default()
router.LoadHTMLGlob("templates/*.html")
router.GET("/", func(ctx *gin.Context){
ctx.HTML(200, "index.html", gin.H{})
})
router.Run()
}
func sqlConnect() (database *gorm.DB) {
DBMS := "mysql"
USER := "go_test"
PASS := "password"
PROTOCOL := "tcp(db:3306)"
DBNAME := "go_database"
CONNECT := USER + ":" + PASS + "@" + PROTOCOL + "/" + DBNAME + "?charset=utf8&parseTime=true&loc=Asia%2FTokyo"
count := 0
db, err := gorm.Open(DBMS, CONNECT)
if err != nil {
for {
if err == nil {
fmt.Println("")
break
}
fmt.Print(".")
time.Sleep(time.Second)
count++
if count > 180 {
fmt.Println("")
fmt.Println("DB connection failure")
panic(err)
}
db, err = gorm.Open(DBMS, CONNECT)
}
}
fmt.Println("DB connection successful")
return db
}
Now do docker compose up
and if the console says" DB connection successful ", it's successful.
Since sqlConnect is the main content added, I will explain it.
func sqlConnect() (database *gorm.DB) {
DBMS := "mysql"
USER := "go_test"
PASS := "password"
PROTOCOL := "tcp(db:3306)"
DBNAME := "go_database"
CONNECT := USER + ":" + PASS + "@" + PROTOCOL + "/" + DBNAME + "?charset=utf8&parseTime=true&loc=Asia%2FTokyo"
count := 0
db, err := gorm.Open(DBMS, CONNECT)
if err != nil {
for {
if err == nil {
fmt.Println("")
break
}
fmt.Print(".")
time.Sleep(time.Second)
count++
if count > 180 {
fmt.Println("")
fmt.Println("DB connection failure")
panic(err)
}
db, err = gorm.Open(DBMS, CONNECT)
}
}
fmt.Println("DB connection successful")
return db
}
The first half defines the information for connecting to the DB. Please enter the contents set in docker-compose.yml.
Then connect to the DB with db, err: = gorm.Open (DBMS, CONNECT)
. However, depending on the MySQL startup time, MySQL may not be ready by the time this command is executed.
Therefore, this code takes two measures.
The first is the dependency setting in docker-compose.yml. By setting depends_on here, the go container will be started after the db container is started.
The second is retry processing. Even after the db container is started, it takes time for MySQL to start, so if it does not connect to the DB, I wait 1 second and then retry. If this is the case, it will continue to retry when an error occurs, so make sure to return the error an appropriate number of times. In this code, if it doesn't connect for 3 minutes, an error will occur.
Finally, it is connected to MySQL, so let's finally implement CRUD processing and see the actual flow. Only main.go and index.html will be changed. --Create User definition --Migration --Implementation of post processing --Implementation of user addition form and user deletion button I will do it.
main.go
package main
import (
"fmt"
"strconv"
"time"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/go-sql-driver/mysql"
)
type User struct {
gorm.Model
Name string
Email string
}
func main() {
db := sqlConnect()
db.AutoMigrate(&User{})
defer db.Close()
router := gin.Default()
router.LoadHTMLGlob("templates/*.html")
router.GET("/", func(ctx *gin.Context){
db := sqlConnect()
var users []User
db.Order("created_at asc").Find(&users)
defer db.Close()
ctx.HTML(200, "index.html", gin.H{
"users": users,
})
})
router.POST("/new", func(ctx *gin.Context) {
db := sqlConnect()
name := ctx.PostForm("name")
email := ctx.PostForm("email")
fmt.Println("create user " + name + " with email " + email)
db.Create(&User{Name: name, Email: email})
defer db.Close()
ctx.Redirect(302, "/")
})
router.POST("/delete/:id", func(ctx *gin.Context) {
db := sqlConnect()
n := ctx.Param("id")
id, err := strconv.Atoi(n)
if err != nil {
panic("id is not a number")
}
var user User
db.First(&user, id)
db.Delete(&user)
defer db.Close()
ctx.Redirect(302, "/")
})
router.Run()
}
func sqlConnect() (database *gorm.DB) {
DBMS := "mysql"
USER := "go_test"
PASS := "password"
PROTOCOL := "tcp(db:3306)"
DBNAME := "go_database"
CONNECT := USER + ":" + PASS + "@" + PROTOCOL + "/" + DBNAME + "?charset=utf8&parseTime=true&loc=Asia%2FTokyo"
count := 0
db, err := gorm.Open(DBMS, CONNECT)
if err != nil {
for {
if err == nil {
fmt.Println("")
break
}
fmt.Print(".")
time.Sleep(time.Second)
count++
if count > 180 {
fmt.Println("")
panic(err)
}
db, err = gorm.Open(DBMS, CONNECT)
}
}
return db
}
templates/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Sample App</title>
</head>
<body>
<h2>Add user</h2>
<form method="post" action="/new">
<p>name<input type="text" name="name" size="30" placeholder="Please enter" ></p>
<p>mail address<input type="text" name="email" size="30" placeholder="Please enter" ></p>
<p><input type="submit" value="Send"></p>
</form>
<h2>User list</h2>
<table>
<tr>
<td>name</td>
<td>mail address</td>
</tr>
{{ range .users }}
<tr>
<td>{{ .Name }}</td>
<td>{{ .Email }}</td>
<td>
<form method="post" action="/delete/{{.ID}}">
<button type="submit">Delete</button>
</form>
</td>
</tr>
{{ end }}
</ul>
</body>
</html>
Now do docker-compose up -d
, and when you access [http: // localhost: 8080](http: // localhost: 8080), the user registration form will appear, and when you register the user, the user registered below The information will be displayed.
Also, even if you delete the container and raise it again, the registered users will not be deleted and will remain displayed in the user list.
Then I will explain the additional part.
First, I am creating a structure called User in main.go. In gorm.Model
, the contents necessary for the model such as id are put in User, and User-specific name and email are added.
This structure is reflected in the DB by db.AutoMigrate
.
Next, we will implement CRUD processing in each path.
Get the user list in the root path. Use db.Find (& users) to get the list of users in the DB as a User structure. By putting an Order in between, the old user comes up at the time of acquisition. The last retrieved user is passed to index.html.
The / new path creates a user based on the contents of the form. I get the content submitted by the form with ctx.PostForm and persist the content with db.Create. Redirect to root when you're done.
The / delete path specifies the id to delete the user. Here, the user's id is specified in the URL, but it is also obtained from ctx. Then, get the user from the contents with db.First, and delete the user with db.Delete. Note that the id is passed as a string, so strconv.Atoi converts it to an int type.
In index.html, form and table are created by the general html writing method.
Here, we are receiving the users passed from main.go in the form {{range .users}}
.
This time, I implemented a simple CRUD with Go + MySQL + Docker as an introduction of Web service development with Go. It's just a practice, so I don't think about validation or fine control. The content I did this time is a rudimentary one, but I think that you can actually create a Web service by expanding and complicating this content. If anyone wants to make something with Go, please refer to it!
Recommended Posts