まずは project の作成から。
$ go mod init backend
client のインストール。
$ go get github.com/prisma/prisma-client-go
スキーマの作成。
$ npx prisma init
prisma/schema.prisma
と .env
(と .gitignore ) が生成される。
.env
ファイルは DATABASE_URL
を修正。
# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#using-environment-variables
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server and MongoDB (Preview).
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
DATABASE_URL="mysql://app:password@localhost:3306/app"
prisma/schema.prisma
は client
を prisma-client-go
に、 datasource
を mysql
に修正。
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "go run github.com/prisma/prisma-client-go"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
Task
モデルを定義する。
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "go run github.com/prisma/prisma-client-go"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
model Task {
id String @default(cuid()) @id
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String
status Boolean
desc String?
}
prisma クライアントの作成.
prisma/db/
が生成される。
$ go run github.com/prisma/prisma-client-go generate
DB にスキーマを反映させる。
$ go run github.com/prisma/prisma-client-go migrate dev --name create_task
~~
~~
migrations/
└─ 20211022082219_create_task/
└─ migration.sql
Your database is now in sync with your schema.
既にスキーマファイルが存在する場合。
go run github.com/prisma/prisma-client-go migrate dev
prisma/migrations/20211022082219_create_task/migration.sql
を見てみる。
-- CreateTable
CREATE TABLE `Task` (
`id` VARCHAR(191) NOT NULL,
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updatedAt` DATETIME(3) NOT NULL,
`title` VARCHAR(191) NOT NULL,
`status` BOOLEAN NOT NULL,
`desc` VARCHAR(191) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
テーブルも作成されている。
mysql> show tables;
+--------------------+
| Tables_in_app |
+--------------------+
| _prisma_migrations |
| Task |
+--------------------+
Comment
モデルを定義し、 Task
モデルと関連付けを行う。
model Task {
id String @default(cuid()) @id
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String
status Boolean
desc String?
comments Comment[]
}
model Comment {
id String @default(cuid()) @id
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
content String
task Task @relation(fields: [taskId], references: [id])
taskId String
migration を行う。
$ go run github.com/prisma/prisma-client-go migrate dev --name add_comment_model
~~
~~
migrations/
└─ 20211022083117_add_comment_model/
└─ migration.sql
Your database is now in sync with your schema.
migrations/20211022083117_add_comment_model/migration.sql
を見る。
-- CreateTable
CREATE TABLE `Comment` (
`id` VARCHAR(191) NOT NULL,
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updatedAt` DATETIME(3) NOT NULL,
`content` VARCHAR(191) NOT NULL,
`taskId` VARCHAR(191) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- AddForeignKey
ALTER TABLE `Comment` ADD CONSTRAINT `Comment_taskId_fkey` FOREIGN KEY (`taskId`) REFERENCES `Task`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
localhost:5555
で起動する。
$ npx prisma studio
tasks, err := client.Task.FindMany().Exec(context.Background())
tasks, err := client.Task.FindMany(
db.Task.Title.Equals("1st task")
// <model>.<field>.<method>.(value) 基本的にこの形式で使う
).Exec(context.Background())
schema.prisma
で @id
、 @unique
でマークされたもののみに使用可能。
task, err := client.Task.FindUnique(
db.Task.ID.Equals("1234567890")
).Exec(context.Background())
task, err := client.Task.FindFirst(
db.Task.Title.Equals("1st task")
).Exec(context.Background())
String Filter
tasks, err := client.Task.FindMany(
// Title が "1st task" と一致する Task を取得
db.Task.Title.Equals("1st task")
// Title に "task" を含む Task を取得
// db.Task.Title.Contains("task")
// Title が "1st" から始まる Task を取得
// db.Task.Title.StartsWith("1st")
// Title が "task" で終わる Task を取得
// db.Task.Title.EndsWith("task")
).Exec(context.Background())
Number Filter
// <Field> が 50 である <Model> を取得
db.<Model>.<Field>.Equals(50).Exec(context.Background())
// <Field> が 50 以下の <Model> を取得
db.<Model>.<Field>.Lte(50).Exec(context.Background())
// <Field> が 50 未満の <Model> を取得
db.<Model>.<Field>.Lt(50).Exec(context.Background())
// <Field> が 50 以上の <Model> を取得
db.<Model>.<Field>.Gte(50).Exec(context.Background())
// <Field> が 50 より大きいの <Model> を取得
db.<Model>.<Field>.Gte(50).Exec(context.Background())
Time Filter
tasks, err := client.Task.FindMany(
// 昨日作成された task を取得する
db.Task.CreatedAt.Equals(yesterday)
// 過去 6 時間で作られた task を取得する (createdAt > 6 hours ago)
// db.Task.Gt(time.Now().Add(-6 * time.Hour))
// 過去 6 時間で作られた task を取得する (createdAt >= 6 hours ago)
// db.Task.Gte(time.Now().Add(-6 * time.Hour))
// 昨日作成された task を取得する
// db.Task.Lt(time.Now().Truncate(24 * time.Hour))
// 昨日作成された task を取得する (本日 00:00:00 を含む)
// db.Task.Lte(time.Now().Truncate(24 * time.Hour))
).Exec(context.Background())
null であるものを取得する。
db.Task.Content.EqualsOptional(nil).Exec(context.Background())
content := "string"
db.Task.Content.EqualsOptional(&content).Exec(context.Background())
Not
db.Task.Not(
db.Task.Title.Equals("1st task")
).Exec(context.Background())
Or
db.Task.Or(
db.Task.Title.Equals("1st task"),
db.Task.Desc.Equals("new task")
).Exec(context.Background())
task の title が "1st task" で comment が "new content" であるもの task を取得する。
tasks, err := client.Task.FindMany(
db.Task.Title.Equals("1st task")
db.Task.Comments.Some(
db.Comment.Content.Equals("new content"),
),
).Exec(context.Background())
created, err := client.Task.CreateOne(
db.Task.Title.Set(newTask.Title),
db.Task.Status.Set(newTask.Status),
db.Task.Desc.Set(newTask.Desc),
).Exec(context.Background())
関連付けのあるデータの場合。
created, err := client.Comment.CreateOne(
db.Comment.Content.Set(newComment.Content),
db.Comment.Task.Link(
db.Task.ID.Equals(taskId),
),
).Exec(context.Background())
updated, err := client.Task.FindUnique(
db.Task.ID.Equals(taskId),
).Update(
db.Task.Title.Set(newTask.Title),
db.Task.Status.Set(newTask.Status),
db.Task.Desc.Set(newTask.Desc),
).Exec(context.Background())
_, err := client.Task.FindUnique(
db.Task.ID.Equals(taskId),
).Delete().Exec(context.Background())
今回は github.com/gorilla/mux
を使って API サーバを作った。
package main
import (
"backend/prisma/db"
"context"
"encoding/json"
"io"
"net/http"
"github.com/gorilla/mux"
)
type Task struct {
Id string `json:"id"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
Title string `json:"title"`
Status bool `json:"status"`
Desc string `json:"string"`
}
type Comment struct {
Id string `json:"id"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
Content string `json:"content"`
}
func main() {
client := db.NewClient()
if err := client.Prisma.Connect(); err != nil {
panic(err)
}
defer func() {
if err := client.Prisma.Disconnect(); err != nil {
panic(err)
}
}()
router := mux.NewRouter()
router.HandleFunc("/tasks", func(w http.ResponseWriter, r *http.Request) {
var newTask Task
err := r.ParseForm()
if err != nil {
io.WriteString(w, "ERROR: create task")
}
json.NewDecoder(r.Body).Decode(&newTask)
created, err := client.Task.CreateOne(
db.Task.Title.Set(newTask.Title),
db.Task.Status.Set(newTask.Status),
db.Task.Desc.Set(newTask.Desc),
).Exec(context.Background())
if err != nil {
io.WriteString(w, "ERROR: create task")
}
var response struct {
Code int `json:"code"`
Data []Task `json:"data"`
}
response.Code = 200
response.Data = append(response.Data, Task{
Id: created.ID,
CreatedAt: created.CreatedAt.String(),
UpdatedAt: created.UpdatedAt.String(),
Title: created.Title,
Status: created.Status,
Desc: func() string {
desc, ok := created.Desc()
if !ok {
desc = ""
}
return desc
}(),
})
resp, _ := json.Marshal(response)
w.Header().Set("Content-Type", "application/json")
w.Write(resp)
}).Methods("POST")
router.HandleFunc("/tasks/{task_id}", func(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
taskId := params["task_id"]
task, err := client.Task.FindUnique(
db.Task.ID.Equals(taskId),
).Exec(context.Background())
if err != nil {
io.WriteString(w, "ERROR: read task")
}
var response struct {
Code int `json:"code"`
Data []Task `json:"data"`
}
response.Code = 200
response.Data = append(response.Data, Task{
Id: task.ID,
CreatedAt: task.CreatedAt.String(),
UpdatedAt: task.UpdatedAt.String(),
Title: task.Title,
Status: task.Status,
Desc: func() string {
desc, ok := task.Desc()
if !ok {
desc = ""
}
return desc
}(),
})
resp, _ := json.Marshal(response)
w.Header().Set("Content-Type", "application/json")
w.Write(resp)
}).Methods("GET")
router.HandleFunc("/tasks/{task_id}", func(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
taskId := params["task_id"]
var newTask Task
err := r.ParseForm()
if err != nil {
io.WriteString(w, "ERROR: update task")
}
json.NewDecoder(r.Body).Decode(&newTask)
updated, err := client.Task.FindUnique(
db.Task.ID.Equals(taskId),
).Update(
db.Task.Title.Set(newTask.Title),
db.Task.Status.Set(newTask.Status),
db.Task.Desc.Set(newTask.Desc),
).Exec(context.Background())
if err != nil {
io.WriteString(w, "ERROR: update task")
}
var response struct {
Code int `json:"code"`
Data []Task `json:"data"`
}
response.Code = 200
response.Data = append(response.Data, Task{
Id: updated.ID,
CreatedAt: updated.CreatedAt.String(),
UpdatedAt: updated.UpdatedAt.String(),
Title: updated.Title,
Status: updated.Status,
Desc: func() string {
desc, ok := updated.Desc()
if !ok {
desc = ""
}
return desc
}(),
})
resp, _ := json.Marshal(response)
w.Header().Set("Content-Type", "application/json")
w.Write(resp)
}).Methods("POST")
router.HandleFunc("/tasks/{task_id}", func(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
taskId := params["task_id"]
_, err := client.Task.FindUnique(
db.Task.ID.Equals(taskId),
).Delete().Exec(context.Background())
if err != nil {
io.WriteString(w, "ERROR: delete task")
}
tasks, err := client.Task.FindMany().Exec(context.Background())
if err != nil {
io.WriteString(w, "ERROR: read all task")
}
var responseData []Task
for _, task := range tasks {
responseData = append(responseData, Task{
Id: task.ID,
CreatedAt: task.CreatedAt.String(),
UpdatedAt: task.UpdatedAt.String(),
Title: task.Title,
Status: false,
Desc: func() string {
desc, ok := task.Desc()
if !ok {
desc = ""
}
return desc
}(),
})
}
var response struct {
Code int `json:"code"`
Data []Task `json:"data"`
}
response.Code = 200
response.Data = append(response.Data, responseData...)
resp, _ := json.Marshal(response)
w.Header().Set("Content-Type", "application/json")
w.Write(resp)
}).Methods("DELETE")
router.HandleFunc("/tasks", func(w http.ResponseWriter, r *http.Request) {
tasks, err := client.Task.FindMany().Exec(context.Background())
if err != nil {
io.WriteString(w, "ERROR: read all task")
}
var responseData []Task
for _, task := range tasks {
responseData = append(responseData, Task{
Id: task.ID,
CreatedAt: task.CreatedAt.String(),
UpdatedAt: task.UpdatedAt.String(),
Title: task.Title,
Status: false,
Desc: func() string {
desc, ok := task.Desc()
if !ok {
desc = ""
}
return desc
}(),
})
}
var response struct {
Code int `json:"code"`
Data []Task `json:"data"`
}
response.Code = 200
response.Data = append(response.Data, responseData...)
resp, _ := json.Marshal(response)
w.Header().Set("Content-Type", "application/json")
w.Write(resp)
}).Methods("GET")
router.HandleFunc("/tasks/{task_id}/comments", func(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
taskId := params["task_id"]
var newComment Comment
err := r.ParseForm()
if err != nil {
io.WriteString(w, "ERROR: create comment")
}
json.NewDecoder(r.Body).Decode(&newComment)
created, err := client.Comment.CreateOne(
db.Comment.Content.Set(newComment.Content),
db.Comment.Task.Link(
db.Task.ID.Equals(taskId),
),
).Exec(context.Background())
if err != nil {
io.WriteString(w, "ERROR: create comment")
}
var response struct {
Code int `json:"code"`
Data []Comment `json:"data"`
}
response.Code = 200
response.Data = append(response.Data, Comment{
Id: created.ID,
CreatedAt: created.CreatedAt.String(),
UpdatedAt: created.UpdatedAt.String(),
Content: created.Content,
})
resp, _ := json.Marshal(response)
w.Header().Set("Content-Type", "application/json")
w.Write(resp)
}).Methods("POST")
router.HandleFunc("/tasks/{task_id}/comments", func(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
taskId := params["task_id"]
comments, err := client.Comment.FindMany(
db.Comment.TaskID.Equals(taskId),
).Exec(context.Background())
if err != nil {
io.WriteString(w, "ERROR: read comment")
}
var responseData []Comment
for _, comment := range comments {
responseData = append(responseData, Comment{
Id: comment.ID,
CreatedAt: comment.CreatedAt.String(),
UpdatedAt: comment.UpdatedAt.String(),
Content: comment.Content,
})
}
var response struct {
Code int `json:"code"`
Data []Comment `json:"data"`
}
response.Code = 200
response.Data = append(response.Data, responseData...)
resp, _ := json.Marshal(response)
w.Header().Set("Content-Type", "application/json")
w.Write(resp)
}).Methods("GET")
router.HandleFunc("/tasks/{task_id}/comments/{comment_id}", func(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
commentId := params["comment_id"]
var newComment Comment
err := r.ParseForm()
if err != nil {
io.WriteString(w, "ERROR: update comment")
}
json.NewDecoder(r.Body).Decode(&newComment)
updated, err := client.Comment.FindUnique(
db.Comment.ID.Equals(commentId),
).Update(
db.Comment.Content.Set(newComment.Content),
).Exec(context.Background())
if err != nil {
io.WriteString(w, "update error")
}
var response struct {
Code int `json:"code"`
Data []Comment `json:"comment"`
}
response.Code = 200
response.Data = append(response.Data, Comment{
Id: updated.ID,
CreatedAt: updated.CreatedAt.String(),
UpdatedAt: updated.UpdatedAt.String(),
Content: updated.Content,
})
resp, _ := json.Marshal(response)
w.Header().Set("Content-Type", "application/json")
w.Write(resp)
}).Methods("POST")
router.HandleFunc("/tasks/{task_id}/comments/{comment_id}", func(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
taskId := params["task_id"]
commentId := params["comment_id"]
_, err := client.Comment.FindUnique(
db.Comment.ID.Equals(commentId),
).Delete().Exec(context.Background())
if err != nil {
io.WriteString(w, "ERROR: delete comment")
}
comments, err := client.Comment.FindMany(
db.Comment.TaskID.Equals(taskId),
).Exec(context.Background())
if err != nil {
io.WriteString(w, "ERROR: read all comment")
}
var responseData []Comment
for _, comment := range comments {
responseData = append(responseData, Comment{
Id: comment.ID,
CreatedAt: comment.CreatedAt.String(),
UpdatedAt: comment.UpdatedAt.String(),
Content: comment.Content,
})
}
var response struct {
Code int `json:"code"`
Data []Comment `json:"data"`
}
response.Code = 200
response.Data = append(response.Data, responseData...)
resp, _ := json.Marshal(response)
w.Header().Set("Content-Type", "application/json")
w.Write(resp)
}).Methods("DELETE")
server := http.Server{
Addr: "0.0.0.0:8080",
Handler: router,
}
server.ListenAndServe()
}