Golangを使用してGraphQLAPIを構築し、DigitalOceanSpacesにファイルをアップロードする方法
著者は、 Diversity in Tech Fund を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。
序章
多くのアプリケーションにとって、望ましい機能の1つは、プロファイル画像をアップロードするユーザーの機能です。 ただし、この機能の構築は、ファイルのアップロードのサポートが組み込まれていないGraphQLを初めて使用する開発者にとっては難しい場合があります。
このチュートリアルでは、バックエンドアプリケーションから直接サードパーティのストレージサービスに画像をアップロードする方法を学習します。 GoバックエンドアプリケーションからS3互換のAWSGO SDKを使用するGraphQLAPIを構築し、高度にスケーラブルなオブジェクトストレージサービスである DigitalOceanSpacesに画像をアップロードします。 GoバックエンドアプリケーションはGraphQLAPIを公開し、DigitalOceanの ManagedDatabasesサービスによって提供されるPotsgreSQLデータベースにユーザーデータを保存します。
このチュートリアルの終わりまでに、Golangを使用してGraphQL APIを構築し、マルチパートHTTPリクエストからメディアファイルを受信し、 DigitalOceanSpaces内のバケットにファイルをアップロードできるようになります。
前提条件
このチュートリアルに従うには、次のものが必要です。
-
DigitalOceanアカウント。 アカウントをお持ちでない場合は、新しいアカウントにサインアップしてください。 このチュートリアルでは、DigitalOceanのスペースおよび管理対象データベースを使用します。
-
アクセスキーとアクセスシークレットを備えたDigitalOceanスペース。チュートリアルDigitalOceanスペースとAPIキーを作成する方法に従って作成できます。 スペースへの管理アクセスを管理する方法の製品ドキュメントも参照できます。
-
ローカルマシンにGoをインストールします。これは、シリーズGoのローカルプログラミング環境をインストールおよびセットアップする方法に従って実行できます。 このチュートリアルでは、Goバージョン1.17.1を使用しました。
-
Goシリーズのコーディング方法から得られるGolangの基本的な知識。 チュートリアルGoで最初のプログラムを作成する方法は、Golangプログラミング言語の優れた入門書です。
-
GraphQL の理解。これは、チュートリアル An Introduction toGraphQLにあります。
ステップ1— GolangGraphQLAPIのブートストラップ
このステップでは、Gqlgenライブラリーを使用してGraphQLAPIをブートストラップします。 Gqlgenは、GraphQLAPIを構築するためのGoライブラリです。 Gqglenが提供する2つの重要な機能は、スキーマファーストアプローチとコード生成です。 スキーマファーストのアプローチでは、最初にGraphQL Schema Definition Language (SDL)を使用してAPIのデータモデルを定義します。 次に、定義されたスキーマからAPIの定型コードを生成します。 コード生成機能を使用すると、APIのクエリリゾルバーとミューテーションリゾルバーが自動的に生成されるため、これらを手動で作成する必要はありません。
開始するには、以下のコマンドを実行してgqlgenをインストールします。
- go install github.com/99designs/[email protected]
次に、digitalocean
という名前のプロジェクトディレクトリを作成して、このプロジェクトのファイルを保存します。
- mkdir digitalocean
digitalocean
プロジェクトディレクトリに移動します。
- cd digitalocean
プロジェクトディレクトリから、次のコマンドを実行して、digitalocean
プロジェクト内のモジュールを管理するgo.mod
ファイルを作成します。
go mod init digitalocean
次に、nanoまたはお気に入りのテキストエディタを使用して、プロジェクトディレクトリ内にtools.go
という名前のファイルを作成します。
- nano tools.go
プロジェクトのツールとしてtools.go
ファイルに次の行を追加します。
// +build tools
package tools
import _ "github.com/99designs/gqlgen"
次に、 tidy コマンドを実行して、tools.go
ファイル内に導入されたgqlgen依存関係をインストールします。
- go mod tidy
最後に、インストールされたGqlgenライブラリを使用して、GraphQLAPIに必要な定型ファイルを生成します。
- gqlgen init
上記のgqlgen
コマンドを実行すると、GraphQLサーバーを実行するためのserver.go
ファイルと、GraphQLのスキーマ定義を含むschema.graphqls
ファイルを含むgraph
ディレクトリが生成されます。 API。
このステップでは、Gqlgenライブラリを使用してGraphQLAPIをブートストラップしました。 次に、GraphQLアプリケーションのスキーマを定義します。
ステップ2—GraphQLアプリケーションスキーマを定義する
このステップでは、gqlgen init
コマンドの実行時に自動的に生成されたschema.graphqls
ファイルを変更して、GraphQLアプリケーションのスキーマを定義します。 このファイルでは、ユーザー、クエリ、およびミューテーションのタイプを定義します。
graph
ディレクトリに移動し、GraphQLアプリケーションのスキーマを定義するschema.graphqls
ファイルを開きます。 ボイラープレートスキーマを次のコードブロックに置き換えます。このコードブロックは、User
タイプをQuery
で定義してすべてのユーザーデータを取得し、Mutation
でデータを挿入します。
scalar Upload
type User {
id: ID!
fullName: String!
email: String!
img_uri: String!
DateCreated: String!
}
type Query {
users: [User]!
}
input NewUser {
fullName: String!
email: String!
img_uri: String
DateCreated: String
}
input ProfileImage {
userId: String
file: Upload
}
type Mutation {
createUser(input: NewUser!): User!
uploadProfileImage(input: ProfileImage!): Boolean!
}
コードブロックは、すべてのユーザーを取得するために、2つのMutation
タイプと1つのQuery
タイプを定義します。 mutation は、GraphQLアプリケーションで既存のデータを挿入または変更するために使用され、 query は、RESTのGET
HTTP動詞と同様にデータをフェッチするために使用されます。 API。
上記のコードブロックのスキーマは、GraphQL Schema Definition Language を使用して、CreateUser
タイプを含むMutation
を定義しました。これは、NewUser
入力をパラメータを返し、単一のuser
を返します。 また、uploadProfileImage
タイプも含まれています。このタイプは、ProfileImage
を受け入れ、アップロード操作の成功のステータスを示すブール値を返します。
注: Gqlgenは、Uploadスカラー型を自動的に定義し、ファイルのプロパティを定義します。 これを使用するには、上記のコードブロックで行われたように、スキーマファイルの先頭で宣言するだけで済みます。
この時点で、アプリケーションのデータモデルの構造を定義しました。 次のステップは、Gqlgenのコード生成機能を使用して、スキーマのクエリとミューテーションリゾルバー関数を生成することです。
ステップ3—アプリケーションリゾルバーの生成
このステップでは、Gqlgenのコード生成機能を使用して、前のステップで作成したスキーマに基づいてGraphQLリゾルバーを自動的に生成します。 resolver は、GraphQLフィールドの値を解決または返す関数です。 この値は、オブジェクト、または文字列、数値、さらにはブール値などのスカラー型である可能性があります。
Gqlgenパッケージは、スキーマファーストアプローチに基づいています。 Gqlgenの時間節約機能は、schema.graphqls
ファイルで定義されたスキーマに基づいてアプリケーションのリゾルバーを生成する機能です。 この機能を使用すると、リゾルバーのボイラープレートコードを手動で記述する必要がありません。つまり、定義されたリゾルバーの実装に集中できます。
コード生成機能を使用するには、プロジェクトディレクトリで以下のコマンドを実行して、GraphQLAPIモデルファイルとリゾルバーを生成します。
- gqlgen generate
gqlgen
コマンドを実行すると、いくつかのことが起こります。 schema.resolvers.go
ファイルに関連する2つの検証エラーが出力され、いくつかの新しいファイルが生成され、プロジェクトのフォルダー構造が新しくなります。
treeコマンドを実行して、プロジェクトに追加された新しいファイルを表示します。
tree *
現在のディレクトリ構造は次のようになります。
Output
go.mod
go.sum
gqlgen.yml
graph
├── db.go
├── generated
│ └── generated.go
├── model
│ └── models_gen.go
├── resolver.go
├── schema.graphqls
└── schema.resolvers.go
server.go
tmp
├── build-errors.log
└── main
tools.go
2 directories, 8 files
プロジェクトファイルの中で、重要なファイルの1つはschema.resolvers.go
です。 これには、schema.graphqls
ファイルで以前に定義されたMutation
およびQuery
タイプを実装するメソッドが含まれています。
検証エラーを修正するには、schema.resolvers.go
ファイルの下部にあるCreateTodo
およびTodos
メソッドを削除します。 schema.graphqls
ファイルでタイプ定義が変更されたため、Gqlgenはメソッドをファイルの一番下に移動しました。
package graph
// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.
import (
"context"
"digitalocean/graph/generated"
"digitalocean/graph/model"
"fmt"
)
func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser) (*model.User, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *mutationResolver) UploadProfileImage(ctx context.Context, input model.ProfileImage) (bool, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *queryResolver) User(ctx context.Context) (*model.User, error) {
panic(fmt.Errorf("not implemented"))
}
// Mutation returns generated.MutationResolver implementation.
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
// !!! WARNING !!!
// The code below was going to be deleted when updating resolvers. It has been copied here so you have
// one last chance to move it out of harms way if you want. There are two reasons this happens:
// - When renaming or deleting a resolver the old code will be put in here. You can safely delete
// it when you're done.
// - You have helper methods in this file. Move them out to keep these resolver files clean.
func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) {
panic(fmt.Errorf("not implemented"))
}
schema.graphqls
ファイルで定義されているように、Gqlgenのコードジェネレーターは2つのミューテーションと1つのクエリリゾルバーメソッドを作成しました。 これらのリゾルバーは、次の目的を果たします。
-
CreateUser
:このミューテーションリゾルバーは、接続されたPostgresデータベースに新しいユーザーレコードを挿入します。 -
UploadProfileImage
:このミューテーションリゾルバーは、マルチパートHTTPリクエストから受信したメディアファイルをアップロードし、そのファイルをDigitalOceanSpaces内のバケットにアップロードします。 ファイルのアップロード後、アップロードされたファイルのURLが、以前に作成されたユーザーのimg_uri
フィールドに挿入されます。 -
Users
:このクエリリゾルバーは、データベースに既存のすべてのユーザーを照会し、それらを照会結果として返します。
MutationタイプとQueryタイプから生成されたメソッドを調べると、実行時にpanicとnot implemented
エラーが発生することがわかります。 これは、それらがまだ自動生成されたボイラープレートコードであることを示しています。 このチュートリアルの後半で、schema.resolver.go
ファイルに戻って、これらの生成されたメソッドを実装します。
この時点で、schema.graphqls
ファイルの内容に基づいてこのアプリケーションのリゾルバーを生成しました。 次に、Managed Databasesサービスを使用して、ミューテーションリゾルバーに渡されたデータを格納してユーザーを作成するデータベースを作成します。
ステップ4—DigitalOceanでのマネージドデータベースインスタンスのプロビジョニングと使用
このステップでは、DigitalOceanコンソールを使用してManaged Databasesサービスにアクセスし、このアプリケーションからのデータを格納するPostgreSQLデータベースを作成します。 データベースが作成されたら、詳細を.env
ファイルに安全に保存します。
アプリケーションは画像をデータベースに直接保存しませんが、それでも各ユーザーのレコードを挿入するためのデータベースが必要です。 保存されたレコードには、アップロードされたファイルへのリンクが含まれます。
ユーザーのレコードは、フルネーム、メール、 dateCreated、、および文字列データ型のimg_uriフィールドで構成されます。 img_uri フィールドには、ユーザーがこのGraphQL APIを介してアップロードし、DigitalOceanSpacesのバケット内に保存されている画像ファイルを指すURLが含まれています。
DigitalOceanダッシュボードを使用して、コンソールの[データベース]セクションに移動して新しいデータベースクラスターを作成し、提供されているデータベースのリストからPostgreSQLを選択します。 他のすべての設定はデフォルト値のままにし、下部のボタンを使用してこのクラスターを作成します。
データベースクラスターの作成プロセスは、完了するまでに数分かかります。
クラスタを作成したら、データベースクラスタページのはじめにの手順に従って、使用するクラスタを設定します。
はじめにガイドの2番目のステップで、続行をクリックします。これは後で実行しますテキストで続行します。 デフォルトでは、データベースクラスターはすべての接続に対して開かれています。
注:本番環境に対応したシナリオでは、2番目のステップの信頼できるソースの追加入力フィールドには、アプリケーションを実行しているDigitalOceanドロップレットのIPアドレスなどの信頼できるIPアドレスのみを含める必要があります。 開発中に、開発マシンのIPアドレスを信頼できるソースの追加入力フィールドに追加することもできます。
これらのインバウンドソースを許可するボタンをクリックして保存し、次の手順に進みます。
次のステップで、クラスターの接続の詳細が表示されます。 アクションドロップダウンをクリックし、接続の詳細オプションを選択して、クラスターの資格情報を見つけることもできます。
このスクリーンショットでは、右側の灰色のボックスは、作成されたデモクラスターの接続クレデンシャルを示しています。
これらのクラスタークレデンシャルを環境変数として安全に保存します。 digitalocean
プロジェクトディレクトリで、.env
ファイルを作成し、クラスターのクレデンシャルを次の形式で追加します。強調表示されたプレースホルダーのコンテンツを独自のクレデンシャルに置き換えてください。
DB_PASSWORD=YOUR_DB_PASSWORD
DB_PORT=PORT
DB_NAME=YOUR_DATABASE_NAME
DB_ADDR=HOST
DB_USER=USERNAME
接続の詳細が.env
ファイルに安全に保存されたら、次のステップはこれらの資格情報を取得し、データベースクラスターをプロジェクトに接続することです。
先に進む前に、Postgresデータベースに接続するときにGolangのネイティブSQLパッケージを操作するためのデータベースドライバーが必要になります。 go-pg は、ORM(オブジェクトリレーショナルマッピング)クエリをPostgresデータベースのSQLクエリに変換するためのGolangライブラリです。 godotenv は、.env
ファイルからアプリケーションに環境クレデンシャルをロードするためのGolangライブラリです。 最後に、 go.uuid は、データベースに挿入される各ユーザーのレコードのUUID(ユニバーサル一意識別子)を生成します。
これらをインストールするには、次のコマンドを実行します。
- go get github.com/go-pg/pg/v10 github.com/joho/godotenv github.com/satori/go.uuid
次に、graph
ディレクトリに移動し、db.go
ファイルを作成します。 ファイル内のコードを徐々にまとめて、管理対象データベースクラスターで作成されたPostgresデータベースに接続します。
まず、コードブロックの内容をdb.go
ファイルに追加します。 この関数(createSchema
)は、データベースへの接続が確立された直後に、Postgresデータベースにユーザーテーブルを作成します。
package graph
import (
"github.com/go-pg/pg/v10"
"github.com/go-pg/pg/v10/orm"
"digitalocean/graph/model"
)
func createSchema(db *pg.DB) error {
for _, models := range []interface{}{(*model.User)(nil)}{
if err := db.Model(models).CreateTable(&orm.CreateTableOptions{
IfNotExists: true,
}); err != nil {
panic(err)
}
}
return nil
}
go-pgからCreateTable
メソッドに渡されるIfNotExists
オプションを使用すると、createSchema
関数は、テーブルが挿入する場合にのみ、データベースに新しいテーブルを挿入します。存在しない。 このプロセスは、新しく作成されたデータベースをシードする単純化された形式として理解できます。 psql クライアントまたはGUIを使用してテーブルを手動で作成するのではなく、createSchema
関数がテーブルの作成を処理します。
次に、以下のコードブロックの内容をdb.go
ファイルに追加してPostgresデータベースへの接続を確立し、接続が正常に確立されたら上記のcreateSchema
関数を実行します。
import (
// ...
"fmt"
"os"
)
func Connect() *pg.DB {
DB_PASSWORD := os.Getenv("DB_PASSWORD")
DB_PORT := os.Getenv("DB_PORT")
DB_NAME := os.Getenv("DB_NAME")
DB_ADDR := os.Getenv("DB_ADDR")
DB_USER := os.Getenv("DB_USER")
connStr := fmt.Sprintf(
"postgresql://%v:%[email protected]%v:%v/%v?sslmode=require",
DB_USER, DB_PASSWORD, DB_ADDR, DB_PORT, DB_NAME )
opt, err := pg.ParseURL(connStr); if err != nil {
panic(err)
}
db := pg.Connect(opt)
if schemaErr := createSchema(db); schemaErr != nil {
panic(schemaErr)
}
if _, DBStatus := db.Exec("SELECT 1"); DBStatus != nil {
panic("PostgreSQL is down")
}
return db
}
実行されると、上記のコードブロックにエクスポートされたConnect
関数は、go-pgを使用してPostgresデータベースへの接続を確立します。 これは、次の操作によって実行されます。
-
最初に、ルート
.env
ファイルに保存したデータベースクレデンシャルが取得されます。 次に、取得した資格情報でフォーマットされた文字列を格納する変数が作成されます。 この変数は、データベースに接続するときに接続URIとして使用されます。 -
次に、作成された接続文字列が解析され、フォーマットされた資格情報が有効かどうかが確認されます。 有効な場合、接続文字列は、接続を確立するための引数として
connect
メソッドに渡されます。
エクスポートしたConnect
関数を使用するには、server.go
ファイルに関数を追加して、アプリケーションの起動時に実行されるようにする必要があります。 次に、接続をResolver
構造体内のDB
フィールドに格納できます。
アプリケーションの起動直後にgraph
パッケージから作成したConnect
関数を使用し、.env
ファイルからアプリケーションにクレデンシャルをロードするには、[X205X ]