Implement simple CRUD with Go + MySQL + Docker

Introduction

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)

Launch Go with Docker

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 !.

Create a web page in Go

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.

Start MySQL with Docker

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.

Connect Go and MySQL

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.

Implement CRUD

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}}.

in conclusion

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

Implement simple CRUD with Go + MySQL + Docker
Implement CRUD with Spring Boot + Thymeleaf + MySQL
A simple CRUD app made with Nuxt / Laravel (Docker)
Rails + MySQL environment construction with Docker
[Environment construction with Docker] Rails 6 & MySQL 8
mysql doesn't start up with docker.
Update MySQL from 5.7 to 8.0 with Docker
Implement a simple Web REST API server with Spring Boot + MySQL
Create Rails 6 + MySQL environment with Docker compose
Deploy to heroku with Docker (Rails 6, MySQL)
Edit Mysql with commands in Docker environment
Create a MySQL environment with Docker from 0-> 1
[docker] [nginx] Make a simple ALB with nginx
Laravel + MySQL + phpMyadmin environment construction with Docker
Allow image posting with [Docker + WordPress + MySQL]
Create a simple bulletin board with Java + MySQL
Practice making a simple chat app with Docker + Sinatra
Implement a simple Rest API with Spring Security with Spring Boot 2.0
Build Rails (API) x MySQL x Nuxt.js environment with Docker
Rails deploy with Docker
Run Pico with docker
Explode Docker with WSL2
Use Puphpeteer with Docker
Operate Emby with Docker
Use ngrok with Docker
Run Payara with Docker
Php settings with Docker
[Rails] Development with MySQL
I tried to build an API server with Go (Echo) x MySQL x Docker x Clean Architecture
Getting Started with Docker
Disposable PHP with Docker
Install Composer with Docker
I tried to make an introduction to PHP + MySQL with Docker
Create a simple CRUD with SpringBoot + JPA + Thymeleaf ③ ~ Add Validation ~
How to build [TypeScript + Vue + Express + MySQL] environment with Docker ~ Express ~
Easy environment construction of MySQL and Redis with Docker and Alfred
Make a simple CRUD with SpringBoot + JPA + Thymeleaf ① ~ Hello World ~
Rails application development environment construction with Docker [Docker, Rails, Puma, Nginx, MySQL]
How to use mysql with M1 mac Docker preview version
Make a simple CRUD with SpringBoot + JPA + Thymeleaf ⑤ ~ Template standardization ~
docker-compose.yml when you want to keep mysql running with docker
A Simple CRUD Sample Using Java Servlet / JSP and MySQL
Implement a simple Rest API with Spring Security & JWT with Spring Boot 2.0
Measures for permissions when building MySQL with Docker on WSL2