使用GraphQL、Go、React和TypeScript开发的Todo应用程序(后端部分)
首先
为了学习GraphQL,我做了一个Todo应用程序来进行实践。
在本文中,我将作为第一部分总结了使用GraphQL查询进行Todo应用程序的后端开发流程。
请查看部分2的前端篇章。
使用的技术堆栈
编程语言:Go、TypeScript
库:React、gqlgen、codegen、xorm
数据库:Postgres
gqlgen和codegen是可以从GraphQL模式定义自动生成Go和TypeScript代码的库。
xorm是一个Go的ORM库,使用了Postgres作为数据库。
待办事项应用程序的概述


请参考适当的源代码以解释与GraphQL相关的部分!?
目录结构
目录结构如下所示,在本篇文章中将创建app(后端应用程序代码)。
.
├── app - バックエンドのアプリケーションコード
│ ├── Dockerfile
│ ├── go.mod
│ ├── go.sum
│ ├── gqlgen.yml - gqlgenの設定ファイル
│ ├── graph - GraphQL関連のコード
│ │ ├── generated.go - gqlgenによって自動生成されるGoのコード
│ │ ├── model - GraphQLモデルを定義するGoのコード
│ │ │ ├── models_gen.go - gqlgenによって自動生成されるモデルのコード
│ │ │ └── todo.go
│ │ ├── resolver.go - GraphQLリゾルバのインターフェースを定義する
│ │ └── schema.resolvers.go - リゾルバインターフェースを具体的に実装するコード
│ ├── infrastructure
│ │ └── todo.go - インフラのTodoモデル
│ ├── server.go - サーバーコードのエントリーポイント
│ ├── sql
│ │ └── init.sql - 初期化SQLスクリプト
│ └── tools.go - gqlgenをインストール用のファイル
│
├── front - フロントエンドのアプリケーションコード
│ ├── Dockerfile
│ ├── codegen.yml - codegen(フロントのGraphQLコード生成)の設定ファイル
│ ├── graphql - GraphQLのクエリやスキーマを定義
│ │ ├── mutation - データを変更するGraphQLのmutation定義
│ │ │ ├── createTodo.graphql
│ │ │ ├── deleteTodo.graphql
│ │ │ └── updateTodoStatus.graphql
│ │ └── query - データを取得するGraphQLのquery定義
│ │ └── getAllTodos.graphql
│ ├── node_modules
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ ├── src - アプリケーションのソースコード
│ │ ├── App.tsx - メインのアプリケーションコンポーネント
│ │ ├── components - 再利用可能なUIコンポーネント
│ │ │ ├── CreateTodoForm.tsx
│ │ │ └── TodoList.tsx
│ │ ├── index.tsx - アプリケーションのエントリーポイント
│ │ └── types - TypeScriptの型定義
│ │ └── gen - codegenによって自動生成される型定義
│ │ ├── api.ts - APIの型定義
│ │ └── possibleTypes-result.ts
│ └── tsconfig.json
│
├── docker-compose.yml
└── schema.graphqls - 共通のGraphQLスキーマを定義
Todo应用的实践指南。
接下来我们将解释一下使用GraphQL开发Todo应用程序的后端步骤。
后端开发流程概述
-
- 项目设置
-
- 使用gqlgen创建模板
-
- 连接数据库的设置
-
- 定义GraphQL模式
-
- 使用gqlgen生成Go代码
- 实现解析器
Resolution是GraphQL的Query和Mutation被执行时调用的函数,用于实现具体的数据获取和数据操作逻辑。换句话说,它负责确定如何回应客户端的查询以及如何修改数据。
项目的设置
首先,在该目录下创建一个新的文件夹,并初始化Go模块。
mkdir ~/go/src/github.com/[username]/todoapp-graphql-go-react
cd ~/go/src/github.com/[username]/todoapp-graphql-go-react
mkdir app
cd app
go mod init github.com/[username]/todoapp-graphql-go-react
使用gqlgen创建模板
使用下面的gqlgen命令创建一个新项目的模板。
cd app
go run github.com/99designs/gqlgen init
请参考gqlgen的官方文档获取有关安装gqlgen和创建模板的详细信息?
与DB的连接设置
需要建立应用程序与数据库之间的连接。我们将使用 xorm 库来连接到 Postgres 数据库。在 server.go 文件中,我们将添加数据库连接的代码。
package main
import (
"log"
"net/http"
"os"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/playground"
_ "github.com/lib/pq"
"github.com/rs/cors"
"github.com/[username]/todoapp-graphql-go-react/app/graph"
"xorm.io/xorm"
)
const defaultPort = "8080"
func main() {
// DBへの接続情報を設定
connectionString := "user=postgres password=postgres dbname=testdb host=db port=5432 sslmode=disable"
engine, err := xorm.NewEngine("postgres", connectionString)
if err != nil {
log.Fatalln("error - create engine: ", err)
}
// DBへ接続
err = engine.Ping()
if err != nil {
log.Fatalln("error - connect DB: ", err)
}
log.Println("success - connect DB")
// 環境変数からポート番号を取得、設定されていない場合はデフォルト値を使用
port := os.Getenv("PORT")
if port == "" {
port = defaultPort
}
// gqlgenのサーバを新規作成し、リゾルバとしてDB接続を渡す
srv := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: &graph.Resolver{DB: engine}}))
// フロントエンドから接続可能にするためCORSを設定
c := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST", "OPTIONS"},
AllowedHeaders: []string{"Content-Type", "Authorization"},
AllowCredentials: true,
})
handler := c.Handler(http.DefaultServeMux)
// ルートURLでGraphQLのPlaygroundを起動
http.Handle("/", playground.Handler("GraphQL playground", "/query"))
// /queryのパスでGraphQLのエンドポイントを設定
http.Handle("/query", srv)
// 注意: Docker環境のportとローカル環境のportが違うため、実際にローカル環境から接続するportは異なり8081である
log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
// サーバを起動、エラーが発生した場合はログに出力して終了
log.Fatal(http.ListenAndServe(":"+port, handler))
}
在这里,我们需要在resolver.go的Resolver结构体中添加一个DB字段,以便在后续的实现中使用xorm引擎作为解析器。
package graph
import "xorm.io/xorm"
type Resolver struct {
// xormエンジンのインスタンスをフィールドとして保持
DB *xorm.Engine
}
GraphQL 的模式定义
在这个步骤中,我们将在Todo应用程序中进行查询和类型模式的定义。
通过这个模式定义,Query和Mutation的终端点被定义。Query用于获取数据,而Mutation用于修改数据。
# データ取得のためのスキーマ
type Query {
todos: [Todo!]!
}
# データ変更のためのスキーマ
type Mutation {
createTodo(todoInput: CreateTodoInput!): Todo!
updateTodoStatus(todoId: ID!, done: Boolean!): Boolean!
deleteTodo(todoId: ID!): Boolean!
}
# Todoの型定義
type Todo {
id: ID!
text: String!
done: Boolean!
}
# Todo作成の際に必要な引数を定義
input CreateTodoInput {
text: String!
}
使用gqlgen来自动生成Go代码。
使用gqlgen工具,可以自动从GraphQL模式生成Go代码。
cd app
go run github.com/99designs/gqlgen generate
生成自动化时,将根据 app/gqlgen.yml(用于从模式自动生成Go代码的配置文件)进行生成。
# スキーマ定義ファイルのパスを指定
schema:
- ../*.graphqls
# gqlgenによって自動生成される実行可能なGraphQLコードの設定
exec:
filename: graph/generated.go # 生成するファイル名
package: graph # 生成するパッケージ名
# gqlgenによって自動生成されるモデルコードの設定
model:
filename: graph/model/models_gen.go # 生成するファイル名
package: model # 生成するパッケージ名
# リゾルバの設定
resolver:
layout: follow-schema # スキーマと同じ構造でリゾルバを生成する
dir: graph # リゾルバを生成するディレクトリ
package: graph # リゾルバを生成するパッケージ名
# スキーマの型とGoの型のマッピング設定
models:
ID: # GraphQLのID型に対応するGoの型を指定
model:
- github.com/99designs/gqlgen/graphql.ID
- github.com/99designs/gqlgen/graphql.Int
- github.com/99designs/gqlgen/graphql.Int64
- github.com/99designs/gqlgen/graphql.Int32
Int: # GraphQLのInt型に対応するGoの型を指定
model:
- github.com/99designs/gqlgen/graphql.Int
- github.com/99designs/gqlgen/graphql.Int64
- github.com/99designs/gqlgen/graphql.Int32
解析器的实现
在实施解析器时,定义了每个上述终端的实际操作。例如,createTodo终端将创建一个新的待办事项并返回该待办事项。具体来说,createTodo将执行以下操作:
-
- 生成一个UUID并创建一个新的待办事项
-
- 将生成的待办事项保存到数据库中
- 如果没有发生错误,返回新的待办事项
和其他终端点一样,也会实现具体的数据操作和数据获取逻辑。
package graph
import (
"context"
"log"
"github.com/google/uuid"
"github.com/[username]/todoapp-graphql-go-react/app/graph/model"
"github.com/[username]/todoapp-graphql-go-react/app/infrastructure"
)
// 新しいTodoを作成するためのリゾルバ
func (r *mutationResolver) CreateTodo(ctx context.Context, todoInput model.CreateTodoInput) (*model.Todo, error) {
// 新しいUUIDの生成
newUUID, err := uuid.NewRandom()
if err != nil {
log.Printf("Error generating UUID: %v\n", err)
return nil, err
}
// 新しいTodoの作成
todo := &infrastructure.Todo{
ID: newUUID.String(),
Text: todoInput.Text,
}
// DBに新しいTodoを挿入
_, err = r.DB.Insert(todo)
if err != nil {
log.Printf("Error insert todo: %v\n", err)
return nil, err
}
// 新しいTodoを返す
return model.NewTodo(todo), nil
}
// Todoのステータスを更新するためのリゾルバ
func (r *mutationResolver) UpdateTodoStatus(ctx context.Context, todoID string, done bool) (bool, error) {
todo := &infrastructure.Todo{
Done: done,
}
// 指定されたIDのTodoのステータスを更新
_, err := r.DB.ID(todoID).Cols("done").Update(todo)
if err != nil {
log.Printf("Error update todo status: %v\n", err)
return false, err
}
// 更新成功を返す
return true, nil
}
// Todoを削除するためのリゾルバ
func (r *mutationResolver) DeleteTodo(ctx context.Context, todoID string) (bool, error) {
// 指定されたIDのTodoを削除
_, err := r.DB.ID(todoID).Delete(&infrastructure.Todo{})
if err != nil {
log.Printf("Error delete todo: %v\n", err)
return false, err
}
// 削除成功を返す
return true, nil
}
// 全てのTodoを取得するためのリゾルバ
func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) {
var todos []*infrastructure.Todo
// DBから全てのTodoを取得
r.DB.AllCols().Find(&todos)
// 取得したTodoを返す
return model.NewTodos(todos), nil
}
以下的步骤是使用GraphQL和Go开发Todo应用程序后端的基本步骤。如果需要,可以参考各个技术的官方文档,了解每个步骤的详细说明和各个技术的基本知识!?
最后
虽然只是开发了一个简单的Todo应用程序,但通过实践学习了GraphQL的基础部分,感觉很好!由于还有一些尚未学习的范围,我将继续努力学习。
感谢您阅读到这里!
请参考以下链接