著者は、 Diversity in Tech Fund を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。

序章

多くのアプリケーションにとって、望ましい機能の1つは、プロファイル画像をアップロードするユーザーの機能です。 ただし、この機能の構築は、ファイルのアップロードのサポートが組み込まれていないGraphQLを初めて使用する開発者にとっては難しい場合があります。

このチュートリアルでは、バックエンドアプリケーションから直接サードパーティのストレージサービスに画像をアップロードする方法を学習します。 GoバックエンドアプリケーションからS3互換のAWSGO SDKを使用するGraphQLAPIを構築し、高度にスケーラブルなオブジェクトストレージサービスである DigitalOceanSpacesに画像をアップロードします。 GoバックエンドアプリケーションはGraphQLAPIを公開し、DigitalOceanの ManagedDatabasesサービスによって提供されるPotsgreSQLデータベースにユーザーデータを保存します。

このチュートリアルの終わりまでに、Golangを使用してGraphQL APIを構築し、マルチパートHTTPリクエストからメディアファイルを受信し、 DigitalOceanSpaces内のバケットにファイルをアップロードできるようになります。

前提条件

このチュートリアルに従うには、次のものが必要です。

ステップ1— GolangGraphQLAPIのブートストラップ

このステップでは、Gqlgenライブラリーを使用してGraphQLAPIをブートストラップします。 Gqlgenは、GraphQLAPIを構築するためのGoライブラリです。 Gqglenが提供する2つの重要な機能は、スキーマファーストアプローチとコード生成です。 スキーマファーストのアプローチでは、最初にGraphQL Schema Definition Language (SDL)を使用してAPIのデータモデルを定義します。 次に、定義されたスキーマからAPIの定型コードを生成します。 コード生成機能を使用すると、APIのクエリリゾルバーとミューテーションリゾルバーが自動的に生成されるため、これらを手動で作成する必要はありません。

開始するには、以下のコマンドを実行してgqlgenをインストールします。

  1. go install github.com/99designs/[email protected]

次に、digitaloceanという名前のプロジェクトディレクトリを作成して、このプロジェクトのファイルを保存します。

  1. mkdir digitalocean

digitaloceanプロジェクトディレクトリに移動します。

  1. cd digitalocean

プロジェクトディレクトリから、次のコマンドを実行して、digitaloceanプロジェクト内のモジュールを管理するgo.modファイルを作成します。

 go mod init digitalocean

次に、nanoまたはお気に入りのテキストエディタを使用して、プロジェクトディレクトリ内にtools.goという名前のファイルを作成します。

  1. nano tools.go

プロジェクトのツールとしてtools.goファイルに次の行を追加します。

// +build tools

 package tools

 import _ "github.com/99designs/gqlgen" 

次に、 tidy コマンドを実行して、tools.goファイル内に導入されたgqlgen依存関係をインストールします。

  1. go mod tidy

最後に、インストールされたGqlgenライブラリを使用して、GraphQLAPIに必要な定型ファイルを生成します。

  1. 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でデータを挿入します。

schema.graphqls

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のGETHTTP動詞と同様にデータをフェッチするために使用されます。 API。

上記のコードブロックのスキーマは、GraphQL Schema Definition Language を使用して、CreateUserタイプを含むMutationを定義しました。これは、NewUser入力をパラメータを返し、単一のuserを返します。 また、uploadProfileImageタイプも含まれています。このタイプは、ProfileImageを受け入れ、アップロード操作の成功のステータスを示すブール値を返します。

注: Gqlgenは、Uploadスカラー型を自動的に定義し、ファイルのプロパティを定義します。 これを使用するには、上記のコードブロックで行われたように、スキーマファイルの先頭で宣言するだけで済みます。

この時点で、アプリケーションのデータモデルの構造を定義しました。 次のステップは、Gqlgenのコード生成機能を使用して、スキーマのクエリとミューテーションリゾルバー関数を生成することです。

ステップ3—アプリケーションリゾルバーの生成

このステップでは、Gqlgenのコード生成機能を使用して、前のステップで作成したスキーマに基づいてGraphQLリゾルバーを自動的に生成します。 resolver は、GraphQLフィールドの値を解決または返す関数です。 この値は、オブジェクト、または文字列、数値、さらにはブール値などのスカラー型である可能性があります。

Gqlgenパッケージは、スキーマファーストアプローチに基づいています。 Gqlgenの時間節約機能は、schema.graphqlsファイルで定義されたスキーマに基づいてアプリケーションのリゾルバーを生成する機能です。 この機能を使用すると、リゾルバーのボイラープレートコードを手動で記述する必要がありません。つまり、定義されたリゾルバーの実装に集中できます。

コード生成機能を使用するには、プロジェクトディレクトリで以下のコマンドを実行して、GraphQLAPIモデルファイルとリゾルバーを生成します。

  1. 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はメソッドをファイルの一番下に移動しました。

schema.resolvers.go

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タイプから生成されたメソッドを調べると、実行時にpanicnot 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を選択します。 他のすべての設定はデフォルト値のままにし、下部のボタンを使用してこのクラスターを作成します。

Digitalocean database cluster

データベースクラスターの作成プロセスは、完了するまでに数分かかります。

クラスタを作成したら、データベースクラスタページのはじめにの手順に従って、使用するクラスタを設定します。

はじめにガイドの2番目のステップで、続行をクリックします。これは後で実行しますテキストで続行します。 デフォルトでは、データベースクラスターはすべての接続に対して開かれています。

注:本番環境に対応したシナリオでは、2番目のステップの信頼できるソースの追加入力フィールドには、アプリケーションを実行しているDigitalOceanドロップレットのIPアドレスなどの信頼できるIPアドレスのみを含める必要があります。 開発中に、開発マシンのIPアドレスを信頼できるソースの追加入力フィールドに追加することもできます。

これらのインバウンドソースを許可するボタンをクリックして保存し、次の手順に進みます。

次のステップで、クラスターの接続の詳細が表示されます。 アクションドロップダウンをクリックし、接続の詳細オプションを選択して、クラスターの資格情報を見つけることもできます。

Digitalocean database cluster credentials

このスクリーンショットでは、右側の灰色のボックスは、作成されたデモクラスターの接続クレデンシャルを示しています。

これらのクラスタークレデンシャルを環境変数として安全に保存します。 digitaloceanプロジェクトディレクトリで、.envファイルを作成し、クラスターのクレデンシャルを次の形式で追加します。強調表示されたプレースホルダーのコンテンツを独自のクレデンシャルに置き換えてください。

.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(ユニバーサル一意識別子)を生成します。

これらをインストールするには、次のコマンドを実行します。

  1. 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データベースにユーザーテーブルを作成します。

db.go
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関数を実行します。

db.go

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 ]