使用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作为数据库。

待办事项应用程序的概述

todoapp.gif
スクリーンショット 2023-06-29 14.03.23.png

请参考适当的源代码以解释与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应用程序的后端步骤。

后端开发流程概述

    1. 项目设置

 

    1. 使用gqlgen创建模板

 

    1. 连接数据库的设置

 

    1. 定义GraphQL模式

 

    1. 使用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将执行以下操作:

    1. 生成一个UUID并创建一个新的待办事项

 

    1. 将生成的待办事项保存到数据库中

 

    如果没有发生错误,返回新的待办事项

和其他终端点一样,也会实现具体的数据操作和数据获取逻辑。

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的基础部分,感觉很好!由于还有一些尚未学习的范围,我将继续努力学习。

感谢您阅读到这里!

请参考以下链接

 

广告
将在 10 秒后关闭
bannerAds