0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C#のGraphQLサーバー構築 【HotChocolate 】

Last updated at Posted at 2024-02-20

GraphQL 使ってみたい~

けどまだ実務では使う機会がないので予習しておく :man_tone3:

  • C#でGraphQLサーバーを構築
  • ライブラリにはHotChocolateを採用 :chocolate_bar:
  • ワークショップのページがあるため、実装して体感する

実行環境

  • Windows10
  • .NET8
  • VSCode
  • .NET 8.0 Runtime (v8.0.2) - Windows x64

1. プロジェクト作成

まずターミナルでdotnetコマンドからプロジェクト作成

dotnet new sln -n ConferencePlanner
dotnet new web -n GraphQL
dotnet sln add GraphQL

Dataフォルダを用意して、Speaker.csクラスを作成

mkdir GraphQL/Data
touch Data/Speaker.cs
Data/Speaker.cs
using System.ComponentModel.DataAnnotations;

namespace ConferencePlanner.GraphQL.Data
{
    public class Speaker
    {
        public int Id { get; set; }

        [Required]
        [StringLength(200)]
        public string? Name { get; set; }

        [StringLength(4000)]
        public string? Bio { get; set; }

        [StringLength(1000)]
        public virtual string? WebSite { get; set; }
    }
}

パッケージを追加

dotnet add GraphQL package Microsoft.EntityFrameworkCore.Sqlite --version 8.0.2

追加に成功するとプロジェクトファイルに以下の項目が追加される

GraphQL.csproj
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.2" />

Entity FrameworkでDB接続するためApplicationDbContext.csクラスをDataフォルダに作成

touch Data/ApplicationDbContext.cs
Data/ApplicationDbContext.cs
using Microsoft.EntityFrameworkCore;

namespace ConferencePlanner.GraphQL.Data
{
    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }

        public DbSet<Speaker> Speakers { get; set; }
    }
}

エントリーポイントのProgram.csApplicationDbContextをサービスとして登録する

Program.cs
using ConferencePlanner.GraphQL.Data;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// サービスの登録
builder.Services.AddDbContext<ApplicationDbContext>(options =>
	options.UseSqlite("Data Source=conferences.db"));

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

ORMパッケージを追加

dotnet add GraphQL package Microsoft.EntityFrameworkCore.Tools --version 8.0.2
dotnet add GraphQL package Microsoft.EntityFrameworkCore.Design --version 8.0.2

dotnet-efのインストール
DBのマイグレーションの作成や適用、DBスキーマのスキャフォールディングなどをコマンドラインから実行できる

dotnet new tool-manifest
dotnet tool install dotnet-ef --version 8.0.2 --local
.config/dotnet-tools.json
{
  "version": 1,
  "isRoot": true,
  "tools": {
    "dotnet-ef": {
      "version": "8.0.2",
      "commands": [
        "dotnet-ef"
      ]
    }
  }
}

ビルド

dotnet build GraphQL

マイグレーション実行

dotnet ef migrations add Initial --project GraphQL
dotnet ef database update --project GraphQL

GraphQLパッケージを追加

dotnet add GraphQL package HotChocolate.AspNetCore --version 13.9.0

Query

リゾルバーとなるQuery.csクラスを追加
リソースを取得する(RESTでいうGET)

Query.cs
using System.Linq;
using HotChocolate;
using ConferencePlanner.GraphQL.Data;

namespace ConferencePlanner.GraphQL
{
    public class Query
    {
        public IQueryable<Speaker> GetSpeakers([Service] ApplicationDbContext context) =>
            context.Speakers;
    }
}

Program.csにサービスを追加
依存性注入にGraphQLスキーマを登録し、Queryタイプを登録している

Program.cs
using ConferencePlanner.GraphQL.Data;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// サービスの登録
builder.Services.AddDbContext<ApplicationDbContext>(options =>
	options.UseSqlite("Data Source=conferences.db"));

// 追加
builder.Services
	.AddGraphQLServer()
    .AddQueryType<Query>();

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

GraphQLミドルウェアを構成して、サーバーがGraphQLリクエストを実行する方法を認識できるようにする

Program.cs
using ConferencePlanner.GraphQL.Data;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// サービスの登録
builder.Services.AddDbContext<ApplicationDbContext>(options =>
	options.UseSqlite("Data Source=conferences.db"));

builder.Services
	.AddGraphQLServer()
    .AddQueryType<Query>();

var app = builder.Build();

// ルーティングのミドルウェアを追加
app.UseRouting();

// エンドポイントのミドルウェアを設定
app.UseEndpoints(endpoints =>
{
    endpoints.MapGraphQL();
});

app.MapGet("/", () => "Hello World!");

app.Run();

サーバーを起動

dotnet run --project GraphQL

サーバーの起動に成功すると以下のような画面になる

サーバーが起動した後、http://localhost:5042/graphqlにアクセスするとBanana Cake PopIDEが開く:banana:
image.png

Brose Schemaボタンをクリック後、Speakerフィールドをクリックしてスキーマの返り値の型を確認
image.png

Mutation

リソースを変更する(RESTでいうPOST,PUT,DELETE)
以下、ChatGPTによる解説

GraphQLにおけるMutationは、データを変更する操作を表します。
これにはデータの追加、更新、削除などが含まれます。
簡単に言うと、Mutationはデータベースやサーバーに何か変更を加えたいときに使います。

Mutationは大きく3つの部分から構成されています。

Mutationの定義:
これは、あなたが実行したい具体的な操作を定義する部分です。
例えば、addSpeakerという名前のMutationを作るとき、
これが実際に「話者を追加する」という操作を行う定義になります。

入力(Input):
Mutationがデータを変更するために必要な情報を提供するためのものです。
addSpeakerの場合、話者の名前や経歴など、話者を追加するために必要な情報が入力として必要になります。
慣例として、この入力の型はAddSpeakerInputのように、Mutationの名前にInputを付けた形で命名されます。

ペイロード(Payload):
これは、Mutationの実行後に返されるデータの型です。
操作の結果として何が起こったのか、または新しく追加された話者の詳細など、
クライアントが受け取りたい情報を含みます。
慣例に従い、この型はAddSpeakerPayloadのように、Mutationの名前にPayloadを付けた形で命名されます。

AddSpeakerInput.csを追加

AddSpeakerInput.cs
namespace ConferencePlanner.GraphQL
{
    public record AddSpeakerInput(
        string Name,
        string Bio,
        string WebSite);
}

AddSpeakerPayload.csを追加

AddSpeakerPayload.cs
using ConferencePlanner.GraphQL.Data;

namespace ConferencePlanner.GraphQL
{
    public class AddSpeakerPayload
    {
        public AddSpeakerPayload(Speaker speaker)
        {
            Speaker = speaker;
        }

        public Speaker Speaker { get; }
    }
}

mutation typeを定義したMutation.csを追加

using System.Threading.Tasks;
using ConferencePlanner.GraphQL.Data;
using HotChocolate;

namespace ConferencePlanner.GraphQL
{
    public class Mutation
    {
        public async Task<AddSpeakerPayload> AddSpeakerAsync(
            AddSpeakerInput input,
            [Service] ApplicationDbContext context)
        {
            var speaker = new Speaker
            {
                Name = input.Name,
                Bio = input.Bio,
                WebSite = input.WebSite
            };

            context.Speakers.Add(speaker);
            await context.SaveChangesAsync();

            return new AddSpeakerPayload(speaker);
        }
    }
}

Program.csMutationを追加

Program.cs
builder.Services
	.AddGraphQLServer()
    .AddQueryType<Query>()
	.AddMutationType<Mutation>(); // 追加

サーバーを起動

dotnet run --project GraphQL

サーバーが起動した後、http://localhost:5042/graphqlschema Definitionから追加したMutaionが確認できる
image.png

Operationに以下のmutationを記述してRunをクリックすると、

mutation AddSpeaker {
  addSpeaker(input: {
    name: "Speaker Name"
    bio: "Speaker Bio"
    webSite: "http://speaker.website" }) {
    speaker {
      id
    }
  }
}

レスポンスが返却される

{
  "data": {
    "addSpeaker": {
      "speaker": {
        "id": 1
      }
    }
  }
}

image.png

OperationGetSpeakerNamesのクエリを記述してRunをクリックすると、

query GetSpeakerNames {
  speakers {
    name
  }
}

mutationによって実行された結果を取得できる

{
  "data": {
    "speakers": [
      {
        "name": "Speaker Name"
      }
    ]
  }
}

image.png

実行時のGraphQLサーバーのログ
INSERTSELECTのクエリが走っていることが確認できた

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (11ms) [Parameters=[@p0='?' (Size = 11), @p1='?' (Size = 12), @p2='?' (Size = 22)], CommandType='Text', CommandTimeout='30']    
      INSERT INTO "Speakers" ("Bio", "Name", "WebSite")
      VALUES (@p0, @p1, @p2)
      RETURNING "Id";
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT "s"."Id", "s"."Bio", "s"."Name", "s"."WebSite"
      FROM "Speakers" AS "s"

ソースコードはGitHubに置きました

2. null許容・非許容のコントロール

  • GraphQLの型はデフォルトでnull許容
  • C#の型はデフォルトでnull許容ではない

nullを許容する場合、GraphQL.csproj<Nullable>enable</Nullable>とする

GraphQL.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="HotChocolate.AspNetCore" Version="13.9.0" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.2">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.2" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.2">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
  </ItemGroup>

</Project>

Entity Frameworkが実行時にSpeakersプロパティを初期化するため、コンパイラが不必要に警告を発するのを避けるために、null許容性に関するデフォルトの振る舞いをオーバーライドする (default==nullだが、!をつけてnullにならないという意)

ApplicationDBContext.cs
 public DbSet<Speaker> Speakers { get; set; } = default!;

Speaker.csクラスのプロパティにて、プロパティを設定する
[Required]アノテーションを付与するとNULL非許容となる
C#の場合、string(null非許容参照型)と string?(null許容参照型)を?の有無で設定する (※C#8.0以降)

Speaker.cs
using System.ComponentModel.DataAnnotations;

namespace ConferencePlanner.GraphQL.Data
{
    public class Speaker
    {
        public int Id { get; set; }

        [Required]
        [StringLength(200)]
        public string? Name { get; set; }

        [StringLength(4000)]
        public string? Bio { get; set; }

        [StringLength(1000)]
        public virtual string? WebSite { get; set; }
    }
}
AddSpeakerInput.cs
namespace ConferencePlanner.GraphQL
{
    public record AddSpeakerInput(
        string Name,
        string? Bio,
        string? WebSite);
}

GraphQLスキーマで確認すると、!サフィックスが付与されているものがnull非許容で定義されていることが確認できる

  • string null許容
  • string! null非許容
  • [Speaker!]! オブジェクトの配列がnull非許容かつ、配列の要素すべてがnull非許容

詳しくは公式ドキュメント Object types and fields を参照

続く

Reference

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?