GraphQL Nexus から Pothos GraphQL に乗り換える
このスクラップについて
GraphQL Nexus を実務で使っているが最近開発があまり活発ではないことに懸念を抱いている。
気になって調べていると下記の記事を見つけて Pothos のことを知った。
なんかすごく良さそうなので手を動かして試してみて良かったら GraphQL Nexus から乗り換えたい。
仕事に追われているが
明日納期の仕事があって調べている時間も惜しいくらいなのだが好奇心には勝てない。
たまには自分の喜びのために時間を使おう。
Pothos 公式 Web サイト
ワークスペース作成
mkdir hello-pothos
cd hello-pothos
npm init -y
npm install --save @pothos/core graphql-yoga
npm install --save-dev @types/node ts-node
touch tsconfig.json hello-world.ts
TypeScript 設定
Pothos is designed to be as type-safe as possible, to ensure everything works correctly, make sure that your tsconfig.json has strict mode set to true:
全てが正しく動くことを確かにするために tsconfig.json のコンパイラオプションの strict モードが有効になっていることを確認してくださいとのこと。
{
"compilerOptions": {
"strict": true
}
}
まずは上記の内容だけでどこまでできるかを試してみよう。
コーディング
import SchemaBuilder from "@pothos/core";
import { createYoga } from "graphql-yoga";
import { createServer } from "http";
async function main() {
const builder = new SchemaBuilder({});
builder.queryType({
fields: (t) => ({
hello: t.string({
args: {
name: t.arg.string(),
},
resolve: (parent, { name }) => `hello, ${name || "World"}`,
}),
}),
});
const schema = builder.toSchema();
const yoga = createYoga({
schema: builder.toSchema(),
});
const server = createServer(yoga);
server.listen(3000);
}
main().catch((err) => console.error(err));
動作確認
npx ts-node hello-world.ts
http://localhost:3000/ にアクセスする。
上記のページが表示された。
クエリを発行するにはどうすれば良いのだろう?
Visit GraphiQL を押す
http://localhost:3000/graphql へ移動してクエリが発行できそうなページが表示された。
クエリ作成には Explorer が便利
左側にある上から 3 つ目のアイコンをクリックすると Explorer なるものが起動してクエリを作成するのに便利。
クエリ実行
▶︎ ボタンを押すかエディタで Command + R を押すとクエリが実行される。
期待通りの結果が表示された。
今日はここまで
ほんの触りだけど楽しかった。
引き続き Guides のページを読みながら色々と試してみたい。
今日はここから
今日は Objects を極める。
Giraffe クラス作成
touch giraffe.ts
export class Giraffe {
name: string;
birthday: Date;
heightsInMeters: number;
constructor(name: string, birthday: Date, heightsInMeters: number) {
this.name = name;
this.birthday = birthday;
this.heightsInMeters = heightsInMeters;
}
}
Using classes is completely optional, but it's a good place to start, since it makes it easy to show all the different ways that you can tie the shape of your data to a new object type.
Pothos を使うのにクラスが必須という訳ではない。
Object Type 定義
コード例は下記の通り。
const builder = new SchemaBuilder({});
builder.objectType(Giraffe, {
name: 'Giraffe',
description: 'Long necks, cool patterns, taller than you.',
fields: (t) => ({}),
});
ファイル作成
touch objects.ts
コーディング
Schema ドキュメントを見るためにサーバーを起動する。
import SchemaBuilder from "@pothos/core";
import { Giraffe } from "./giraffe";
import { createYoga } from "graphql-yoga";
import { createServer } from "http";
async function main() {
const builder = new SchemaBuilder({});
builder.objectType(Giraffe, {
name: "Giraffe",
description: "Long necks, cool patterns, taller than you.",
fields: (t) => ({}),
});
const schema = builder.toSchema();
const yoga = createYoga({ schema });
const server = createServer(yoga);
server.listen(3000);
}
main().catch((err) => console.error(err));
この時点で実行する。
npx ts-node objects.ts
エラーメッセージ
http://localhost:3000/graphql にアクセスするとコンソールにエラーメッセージが出力された。
主な内容な下記の 2 点だった。
- ERR Error: Query root type must be provided.
- Type Giraffe must define one or more fields.
Query Type 定義
適当な Query Type を追加した。
builder.queryType({
fields: (t) => ({
ok: t.boolean({
resolve: () => true,
}),
}),
});
エラーが下記だけになった。
Error: Type Giraffe must define one or more fields.
フィールド追加
builder.objectType(Giraffe, {
name: "Giraffe",
description: "Long necks, cool patterns, taller than you.",
fields: (t) => ({
name: t.exposeString("name", {}),
}),
});
フィールドを追加したらエラーが消えた。
Yoga GraphiQL ではスキーマを見れない?
スキーマを確認する方法
npm install -g get-graphql-schema
get-graphql-schema http://localhost:3000/graphql
"""Exposes a URL that specifies the behavior of this scalar."""
directive @specifiedBy(
"""The URL that specifies the behavior of this scalar."""
url: String!
) on SCALAR
"""Long necks, cool patterns, taller than you."""
type Giraffe {
name: String!
}
type Query {
ok: Boolean!
}
公式ドキュメントにあった
npm install --save graphql
schema.graphql に出力するようにコードを書き換える。
import SchemaBuilder from "@pothos/core";
import { Giraffe } from "./giraffe";
import { createYoga } from "graphql-yoga";
import { createServer } from "http";
import { lexicographicSortSchema, printSchema } from "graphql";
import { writeFile } from "fs/promises";
import { join } from "path";
async function main() {
const builder = new SchemaBuilder({});
builder.queryType({
fields: (t) => ({
ok: t.boolean({
resolve: () => true,
}),
}),
});
builder.objectType(Giraffe, {
name: "Giraffe",
description: "Long necks, cool patterns, taller than you.",
fields: (t) => ({
name: t.exposeString("name", {}),
}),
});
const schema = builder.toSchema();
const schemaAsString = printSchema(lexicographicSortSchema(schema));
await writeFile(join(process.cwd(), "schema.graphql"), schemaAsString);
const yoga = createYoga({ schema });
const server = createServer(yoga);
server.listen(3000);
}
main().catch((err) => console.error(err));
フィールド追加
builder.objectType(Giraffe, {
name: "Giraffe",
description: "Long necks, cool patterns, taller than you.",
fields: (t) => ({
name: t.exposeString("name", {}),
age: t.int({
resolve: (parent) => {
const ageDifMs = Date.now() - parent.birthday.getTime();
const ageDate = new Date(ageDifMs);
return Math.abs(ageDate.getUTCFullYear() - 1970);
},
}),
height: t.float({
resolve: (parent) => parent.heightsInMeters,
}),
}),
});
この時点のスキーマ
サーバーを再起動すると schema.graphql が更新される。
"""Long necks, cool patterns, taller than you."""
type Giraffe {
age: Int!
height: Float!
name: String!
}
type Query {
ok: Boolean!
}
Nexus のように nonNull を指定しなくてもデフォルトで nonNull になる。
Query 追加
builder.queryType({
fields: (t) => ({
giraffe: t.field({
type: Giraffe,
resolve: () =>
new Giraffe("James", new Date(Date.UTC(2012, 11, 12)), 5.2),
}),
}),
});
resolve() の戻り値は Giraffe である必要がある。
GraphQL の type Giraffe
と同じである必要はない = name, age, height が無くても良い。
GraphiQL で実行
身長 5.2 m のキリンの James の年齢は 10 才のようだ。
今日はここまで
Pothos ではオブジェクトタイプを定義する方法は 3 つあるようだ。
今回やったのはそのうちクラスを使う方法だった。
次回は残り 2 つの方法を試してみたい。
やっぱりキリの良い所まで終わらせよう
SchemaTypes を使う方法を試してみる。
コーディング
import SchemaBuilder from "@pothos/core";
import { Giraffe } from "./giraffe";
import { createYoga } from "graphql-yoga";
import { createServer } from "http";
import { lexicographicSortSchema, printSchema } from "graphql";
import { writeFile } from "fs/promises";
import { join } from "path";
async function main() {
const builder = new SchemaBuilder<{ Objects: { Giraffe: Giraffe } }>({}); // この行を変更しました。
builder.queryType({
fields: (t) => ({
giraffe: t.field({
type: "Giraffe", // この行を変更しました。
resolve: () =>
new Giraffe("James", new Date(Date.UTC(2012, 11, 12)), 5.2),
}),
}),
});
builder.objectType("Giraffe", { // この行を変更しました。
// name: "Giraffe", // この行をコメントアウトしました。
description: "Long necks, cool patterns, taller than you.",
fields: (t) => ({
name: t.exposeString("name", {}),
age: t.int({
resolve: (parent) => {
const ageDifMs = Date.now() - parent.birthday.getTime();
const ageDate = new Date(ageDifMs);
return Math.abs(ageDate.getUTCFullYear() - 1970);
},
}),
height: t.float({
resolve: (parent) => parent.heightsInMeters,
}),
}),
});
const schema = builder.toSchema();
const schemaAsString = printSchema(lexicographicSortSchema(schema));
await writeFile(join(process.cwd(), "schema.graphql"), schemaAsString);
const yoga = createYoga({ schema });
const server = createServer(yoga);
server.listen(3000);
}
main().catch((err) => console.error(err));
VSCode コード補完
ソースコードだけでは伝えきれないが "Giraffe"
と入力する時にコード補完が効くので安心できる。
仮にタイプミスがある場合はエラーメッセージを表示してくれる。
SchemaTypes の使い所
This is ideal when you want to list out all the types for your schema in one place, or you have interfaces/types that define your data rather than classes, and means you won't have to import anything when referencing the object type in other parts of the schema.
一つの場所にスキーマに関するすべてのタイプをリストアップしたい時に最適のようだ。
またクラスではなくインタフェースや型の時にも適している。
メリットは後から文字列で参照できるのでインポートが不要な点。
ObjectRef を使う方法
ObjectRefs are useful when you don't want to define all the types in a single place (SchemaTypes) and your data is not represented as classes. Regardless of how you define your object types, builder.objectType returns an ObjectRef that can be used as a type parameter in other parts of the schema.
ObjectRef は SchemaTypes とは異なり、一つの場所にスキーマに関するすべてのタイプをリストアップしたくない時に便利のようだ。
一方で SchemaTypes と同様にデータがクラスとして表現されていない時に適している。
build.objectTypes() の戻り値の型は ObjectRef であり、他の Object Type を定義する時などに使うことができる。
import SchemaBuilder from "@pothos/core";
import { createYoga } from "graphql-yoga";
import { createServer } from "http";
import { lexicographicSortSchema, printSchema } from "graphql";
import { writeFile } from "fs/promises";
import { join } from "path";
type GiraffeShape = {
name: string;
birthday: Date;
heightsInMeters: number;
};
async function main() {
const builder = new SchemaBuilder({});
const Giraffe = builder.objectRef<GiraffeShape>("Giraffe");
builder.queryType({
fields: (t) => ({
giraffe: t.field({
type: Giraffe,
resolve: () => ({
name: "James",
birthday: new Date(Date.UTC(2012, 11, 12)),
heightsInMeters: 5.2,
}),
}),
}),
});
builder.objectType(Giraffe, {
description: "Long necks, cool patterns, taller than you.",
fields: (t) => ({
name: t.exposeString("name", {}),
age: t.int({
resolve: (parent) => {
const ageDifMs = Date.now() - parent.birthday.getTime();
const ageDate = new Date(ageDifMs);
return Math.abs(ageDate.getUTCFullYear() - 1970);
},
}),
height: t.float({
resolve: (parent) => parent.heightsInMeters,
}),
}),
});
const schema = builder.toSchema();
const schemaAsString = printSchema(lexicographicSortSchema(schema));
await writeFile(join(process.cwd(), "schema.graphql"), schemaAsString);
const yoga = createYoga({ schema });
const server = createServer(yoga);
server.listen(3000);
}
main().catch((err) => console.error(err));
Object Ref の implement() メソッド
Object Ref とその実装をまとめて書くこともできる。
const Giraffe = builder.objectRef<GiraffeShape>("Giraffe").implement({
description: "Long necks, cool patterns, taller than you.",
fields: (t) => ({
name: t.exposeString("name", {}),
age: t.int({
resolve: (parent) => {
const ageDifMs = Date.now() - parent.birthday.getTime();
const ageDate = new Date(ageDifMs);
return Math.abs(ageDate.getUTCFullYear() - 1970);
},
}),
height: t.float({
resolve: (parent) => parent.heightsInMeters,
}),
}),
});
どの書き方を使う?
ケースバイケースだがクラスがある場合はクラスを使うのが一番楽な気がする。
クラスがなくてインタフェースや型がある場合は ObjectRef を使うと良いかも。
SchemaTypes は使い所がピンと来ないがスキーマがあまり大きくない場合は便利に使えるかも知れない。
今日はここまで(2 回目)
とりあえず Object Types のページを終わらせることができてよかった。
Pothos では Nexus とは異なって Object Types のそれぞれにクラス/インタフェース/型が必要になる。
その点 Nexus はスキーマから型を自動生成してくれるのである意味で便利だった。
アプリ開発の初期段階で API のモックアップを作っている時などはどうすれば良いのだろう?
ドキュメントを眺めていた所、モック用のプラグインを見つけたので使えるかも知れない。
次回は ShcemaBuilder について理解を深めよう。
今日はここから
今日は SchemaBuilder についての理解を深めていく。
Backing models
色々と大切なことが説明されているが重要な点は下記に集約されている気がする。
To put it simply, backing models are the types that describe the data as it flows through your application, which may be substantially different than the types described in your GraphQL schema.
Backing models はアプリケーション内で受け渡されるデータの型であり、GraphQL スキーマの型とは異なる。
Fields
ShcemaBuilder のページが一瞬で終わってしまった。
Pothos のドキュメントは 1 つのページが短いのでテンポ良く読めて楽しい。
次はフィールドについての理解を深めていく。
ファイル作成
touch fields.ts
コーディング
import SchemaBuilder from "@pothos/core";
import { createYoga } from "graphql-yoga";
import { createServer } from "http";
async function main() {
const builder = new SchemaBuilder({});
builder.queryType({
fields: (t) => ({
name: t.field({
description: "Name field",
type: "String",
resolve: () => "Gina",
}),
}),
});
const schema = builder.toSchema();
const yoga = createYoga({ schema });
const server = createServer(yoga);
server.listen(3000, () => {
console.info("Access to http://localhost:3000/graphql");
});
}
main().catch((err) => console.error(err));
動作確認
npx ts-node fields.ts
FieldBuilder
t
が FieldBuider のようだ。
厳密にいうと QueryFieldBuilder のようだ。
便利なメソッド
FieldBuilder の field() メソッドは万能だが一般的なフィールド向けに便利なメソッドが用意されている。
builder.queryType({
fields: (t) => ({
id: t.id({ resolve: () => '123' }),
int: t.int({ resolve: () => 123 }),
float: t.float({ resolve: () => 1.23 }),
boolean: t.boolean({ resolve: () => false }),
string: t.string({ resolve: () => 'abc' }),
idList: t.idList({ resolve: () => ['123'] }),
intList: t.intList({ resolve: () => [123] }),
floatList: t.floatList({ resolve: () => [1.23] }),
booleanList: t.booleanList({ resolve: () => [false] }),
stringList: t.stringList({ resolve: () => ['abc'] }),
}),
});
書き換えてみる。
1 行だけ短くなった。
builder.queryType({
fields: (t) => ({
// name: t.field({
// description: "Name field",
// type: "String",
// resolve: () => "Gina",
// }),
name: t.string({
description: "Name field",
resolve: () => "Gina",
}),
}),
});
Object や Interfaces
文字列や整数などスカラーの場合は便利なメソッドが用意されているが、Object / Interfaces の場合は field() メソッドを使う必要がある。
コーディング
import SchemaBuilder from "@pothos/core";
import { createYoga } from "graphql-yoga";
import { createServer } from "http";
async function main() {
const builder = new SchemaBuilder<{
Objects: {
Giraffe: { name: string };
};
}>({});
builder.queryType({
fields: (t) => ({
giraffe: t.field({
description: "A giraffe",
type: "Giraffe",
resolve: () => ({
name: "Gina",
}),
}),
}),
});
builder.objectType("Giraffe", {
fields: (t) => ({
name: t.exposeString("name"),
}),
});
const schema = builder.toSchema();
const yoga = createYoga({ schema });
const server = createServer(yoga);
server.listen(3000, () => {
console.info("Access to http://localhost:3000/graphql");
});
}
main().catch((err) => console.error(err));
動作確認
npx ts-node fields.ts
Ref を使う
SchemaTypes の代わりに Ref を使うこともできる
const LengthUnit = builder.enumType('LengthUnit', {
values: { Feet: {}, Meters: {} },
});
builder.objectType('Giraffe', {
fields: (t) => ({
preferredNeckLengthUnit: t.field({
type: LengthUnit,
resolve: () => 'Feet',
}),
}),
});
builder.queryType({
fields: (t) => ({
giraffe: t.field({
type: 'Giraffe',
resolve: () => ({ name: 'Gina' }),
}),
}),
});
コーディング
import SchemaBuilder from "@pothos/core";
import { createYoga } from "graphql-yoga";
import { createServer } from "http";
async function main() {
const builder = new SchemaBuilder<{
Objects: {
Giraffe: { name: string };
};
}>({});
const LengthUnit = builder.enumType("LengthUnit", {
values: {
Feet: {},
Meters: {},
},
});
builder.objectType("Giraffe", {
fields: (t) => ({
preferredNeckLengthUnit: t.field({
type: LengthUnit,
resolve: () => "Feet" as const,
}),
}),
});
builder.queryType({
fields: (t) => ({
giraffe: t.field({
description: "A giraffe",
type: "Giraffe",
resolve: () => ({
name: "Gina",
}),
}),
}),
});
const schema = builder.toSchema();
const yoga = createYoga({ schema });
const server = createServer(yoga);
server.listen(3000, () => {
console.info("Access to http://localhost:3000/graphql");
});
}
main().catch((err) => console.error(err));
動作確認
npx ts-node fields.ts
リストフィールド
リストを使うには型(クラス、文字列、Ref)を []
で囲む。
builder.queryType({
fields: t => ({
giraffes: t.field({
description: 'multiple giraffes'
type: ['Giraffe'],
resolve: () => [{ name: 'Gina' }, { name: 'James' }],
}),
giraffeNames: t.field({
type: ['String'],
resolve: () => ['Gina', 'James'],
})
}),
});
コーディング
import SchemaBuilder from "@pothos/core";
import { createYoga } from "graphql-yoga";
import { createServer } from "http";
async function main() {
const builder = new SchemaBuilder<{
Objects: {
Giraffe: { name: string };
};
}>({});
const LengthUnit = builder.enumType("LengthUnit", {
values: {
Feet: {},
Meters: {},
},
});
builder.objectType("Giraffe", {
fields: (t) => ({
preferredNeckLengthUnit: t.field({
type: LengthUnit,
resolve: () => "Feet" as const,
}),
}),
});
builder.queryType({
fields: (t) => ({
giraffes: t.field({
description: "mutiple giraffes",
type: ["Giraffe"],
resolve: () => [{ name: "Gina" }, { name: "James" }],
}),
giraffeNames: t.field({
type: ["String"],
resolve: () => ["Gina", "James"],
}),
}),
});
const schema = builder.toSchema();
const yoga = createYoga({ schema });
const server = createServer(yoga);
server.listen(3000, () => {
console.info("Access to http://localhost:3000/graphql");
});
}
main().catch((err) => console.error(err));
動作確認
npx ts-node fields.ts
Nullable
Pothos ではデフォルトで non-nullable になっており、nullable にするには明示的に指定する必要がある。
builder.queryType({
fields: (t) => ({
nullableField: t.field({
type: 'String',
nullable: true,
resolve: () => null,
}),
nullableString: t.string({
nullable: true,
resolve: () => null,
}),
nullableList: t.field({
type: ['String'],
nullable: true,
resolve: () => null,
}),
spareseList: t.field({
type: ['String'],
nullable: {
list: false,
items: true,
},
resolve: () => [null],
}),
}),
});
SchemaBuilder の設定によってデフォルトを nullable にすることもできるそうだ。
コーディング
import SchemaBuilder from "@pothos/core";
import { writeFile } from "fs/promises";
import { lexicographicSortSchema, printSchema } from "graphql";
import { createYoga } from "graphql-yoga";
import { createServer } from "http";
import { join } from "path";
async function main() {
const builder = new SchemaBuilder({});
builder.queryType({
fields: (t) => ({
nullableField: t.field({
type: "String",
nullable: true,
resolve: () => null,
}),
nullableString: t.string({
nullable: true,
resolve: () => null,
}),
nullableList: t.field({
type: ["String"],
nullable: true,
resolve: () => null,
}),
spareseList: t.field({
type: ["String"],
nullable: {
list: false,
items: true,
},
resolve: () => [null],
}),
}),
});
const schema = builder.toSchema();
const schemaString = printSchema(lexicographicSortSchema(schema));
await writeFile(join(process.cwd(), "schema.graphql"), schemaString);
const yoga = createYoga({ schema });
const server = createServer(yoga);
server.listen(3000, () => {
console.info("Access to http://localhost:3000/graphql");
});
}
main().catch((err) => console.error(err));
動作確認
npx ts-node fields.ts
GraphQL スキーマ
type Query {
nullableField: String
nullableList: [String!]
nullableString: String
spareseList: [String]!
}
Exposing fields
多くのフィールドは backing model のフィールドをそのまま返すだけなので、それを便利にするためのメソッドが用意されている。
- exposeString
- exposeInt
- exposeFloat
- exposeBoolean
- exposeID
- exposeStringList
- exposeIntList
- exposeFloatList
- exposeBooleanList
- exposeIDList
使い方は下記の通り。
const builder = new SchemaBuilder<{
Objects: { Giraffe: { name: string } };
}>({});
builder.objectType('Giraffe', {
fields: (t) => ({
name: t.exposeString('name', {}),
}),
});
実際に使ってみる
import SchemaBuilder from "@pothos/core";
import { createYoga } from "graphql-yoga";
import { createServer } from "http";
async function main() {
const builder = new SchemaBuilder<{
Objects: {
Giraffe: { name: string };
};
}>({});
builder.objectType("Giraffe", {
fields: (t) => ({
name: t.exposeString("name"),
}),
});
builder.queryType({
fields: (t) => ({
giraffe: t.field({
type: "Giraffe",
resolve: () => ({ name: "Gino" }),
}),
}),
});
const schema = builder.toSchema();
const yoga = createYoga({ schema });
const server = createServer(yoga);
server.listen(3000, () => {
console.info("Access to http://localhost:3000/graphql");
});
}
main().catch((err) => console.error(err));
動作確認
npx ts-node fields.ts
引数
args オプションを使うことで引数を指定できる。
builder.queryType({
fields: (t) => ({
giraffeByName: t.field({
type: 'Giraffe',
args: {
name: t.arg.string({ required: true }),
},
resolve: (root, args) => {
if (args.name !== 'Gina') {
throw new NotFoundError(`Unknown Giraffe ${name}`);
}
return { name: 'Gina' };
},
}),
}),
});
実際に使ってみる
import SchemaBuilder from "@pothos/core";
import { writeFile } from "fs/promises";
import { lexicographicSortSchema, printSchema } from "graphql";
import { createYoga } from "graphql-yoga";
import { createServer } from "http";
import { join } from "path";
async function main() {
const builder = new SchemaBuilder<{
Objects: {
Giraffe: { name: string };
};
}>({});
builder.objectType("Giraffe", {
fields: (t) => ({
name: t.exposeString("name"),
}),
});
builder.queryType({
fields: (t) => ({
giraffe: t.field({
type: "Giraffe",
args: {
name: t.arg.string({ required: true }),
},
resolve: (root, args) => {
if (args.name !== "Gina") {
throw new TypeError(`Unkonwn Giraffe ${args.name}`);
}
return { name: "Gina" };
},
}),
}),
});
const schema = builder.toSchema();
const schemaString = printSchema(lexicographicSortSchema(schema));
await writeFile(join(process.cwd(), "schema.graphql"), schemaString);
const yoga = createYoga({ schema });
const server = createServer(yoga);
server.listen(3000, () => {
console.info("Access to http://localhost:3000/graphql");
});
}
main().catch((err) => console.error(err));
動作確認
npx ts-node fields.ts
自動生成されたGraphQL スキーマ
type Giraffe {
name: String!
}
type Query {
giraffe(name: String!): Giraffe!
}
フィールド追加
queryType() や objectType() の代わりに queryFields() や objectFields() を使うことでフィールドを追加できる。
builder.queryFields((t) => ({
giraffe: t.field({
type: Giraffe,
resolve: () => new Giraffe('James', new Date(Date.UTC(2012, 11, 12)), 5.2),
}),
}));
builder.objectField(Giraffe, 'ageInDogYears', (t) =>
t.int({
resolve: (parent) => parent.age * 7,
}),
);
実際に使ってみる
import SchemaBuilder from "@pothos/core";
import { writeFile } from "fs/promises";
import { lexicographicSortSchema, printSchema } from "graphql";
import { createYoga } from "graphql-yoga";
import { createServer } from "http";
import { join } from "path";
async function main() {
const builder = new SchemaBuilder<{
Objects: {
Giraffe: { name: string };
};
}>({});
builder.objectType("Giraffe", {});
builder.objectFields("Giraffe", (t) => ({
name: t.exposeString("name"),
}));
builder.queryType();
builder.queryFields((t) => ({
giraffe: t.field({
type: "Giraffe",
resolve: (root, args) => ({ name: "Gina" }),
}),
}));
const schema = builder.toSchema();
const schemaString = printSchema(lexicographicSortSchema(schema));
await writeFile(join(process.cwd(), "schema.graphql"), schemaString);
const yoga = createYoga({ schema });
const server = createServer(yoga);
server.listen(3000, () => {
console.info("Access to http://localhost:3000/graphql");
});
}
main().catch((err) => console.error(err));
objectFields() や queryFields() だけでは使えないのは少し残念。
動作確認
npx ts-node fields.ts
自動生成された GraphQL スキーマ
type Giraffe {
name: String!
}
type Query {
giraffe: Giraffe!
}
ネストされたリスト
const Query = builder.queryType({
fields: (t) => ({
example: t.field({
type: t.listRef(
t.listRef('String'),
// items are non-nullable by default, this can be overridden
// by passing `nullable: true`
{ nullable: true },
),
resolve: (parent, args) => {
return [['a', 'b'], ['c', 'd'], null];
},
}),
}),
});
listRef() メソッドを使うことで実現できる。
実際に使ってみる
import SchemaBuilder from "@pothos/core";
import { writeFile } from "fs/promises";
import { lexicographicSortSchema, printSchema } from "graphql";
import { createYoga } from "graphql-yoga";
import { createServer } from "http";
import { join } from "path";
async function main() {
const builder = new SchemaBuilder({});
builder.queryType({
fields: (t) => ({
example: t.field({
type: t.listRef(t.listRef("String"), { nullable: true }),
resolve: () => [["a", "b"], ["c", "d"], null],
}),
}),
});
const schema = builder.toSchema();
const schemaString = printSchema(lexicographicSortSchema(schema));
await writeFile(join(process.cwd(), "schema.graphql"), schemaString);
const yoga = createYoga({ schema });
const server = createServer(yoga);
server.listen(3000, () => {
console.info("Access to http://localhost:3000/graphql");
});
}
main().catch((err) => console.error(err));
動作確認
npx ts-node fields.ts
自動生成された GraphQL スキーマ
type Query {
example: [[String!]]!
}
今日はここまで
Fields について手を動かして理解を深めることができた。
次回は Args ページを読んで学んでいこうと思う。
やっぱりもう少し
引数についても理解を深めていこう。
引数の定義方法
FieldBuilder の arg() メソッドを使う。
builder.queryType({
fields: (t) => ({
string: t.string({
args: {
string: t.arg({
type: "String",
description: "String arg",
required: true,
}),
},
resolve: (parent, args) => args.string,
}),
}),
});
実際に試してみる
toush args.ts
import SchemaBuilder from "@pothos/core";
import { createYoga } from "graphql-yoga";
import { createServer } from "http";
async function main() {
const builder = new SchemaBuilder({});
builder.queryType({
fields: (t) => ({
string: t.string({
args: {
string: t.arg({
type: "String",
description: "String arg",
required: true,
}),
},
resolve: (parent, args) => args.string,
}),
}),
});
const schema = builder.toSchema();
const yoga = createYoga({ schema });
const server = createServer(yoga);
server.listen(3000, () => {
console.info("http://localhost:3000/graphql");
});
}
main().catch((err) => console.error(err));
動作確認
npx ts-node args.ts
便利なメソッド
スカラー用に便利なメソッドが用意されていてタイピングを節約できる。
const Query = builder.queryType({
fields: (t) => ({
withArgs: t.stringList({
args: {
id: t.arg.id(),
int: t.arg.int(),
float: t.arg.float(),
boolean: t.arg.boolean(),
string: t.arg.string(),
idList: t.arg.idList(),
intList: t.arg.intList(),
floatList: t.arg.floatList(),
booleanList: t.arg.booleanList(),
stringList: t.arg.stringList(),
},
resolve: (root, args) => Object.keys(args),
}),
}),
});
実際に試してみる
import SchemaBuilder from "@pothos/core";
import { createYoga } from "graphql-yoga";
import { createServer } from "http";
async function main() {
const builder = new SchemaBuilder({});
builder.queryType({
fields: (t) => ({
withArgs: t.stringList({
args: {
id: t.arg.id(),
int: t.arg.int(),
float: t.arg.float(),
boolean: t.arg.boolean(),
string: t.arg.string(),
},
resolve: (parent, args) => Object.keys(args),
}),
}),
});
const schema = builder.toSchema();
const yoga = createYoga({ schema });
const server = createServer(yoga);
server.listen(3000, () => {
console.info("http://localhost:3000/graphql");
});
}
main().catch((err) => console.error(err));
動作確認
npx ts-node args.ts
スキーマが気になる
表示させてみたら下記の通りだった。
type Query {
withArgs(
boolean: Boolean
float: Float
id: ID
int: Int
string: String
): [String!]!
}
スカラー以外の引数
FieldBuilder の arg() メソッドを使う。
const LengthUnit = builder.enumType('LengthUnit', {
values: { Feet: {}, Meters: {} },
});
const Giraffe = builder.objectType('Giraffe', {
fields: t => ({
height: t.float({
args: {
unit: t.arg({
type: LengthUnit,
}),
},
resolve: (parent, args) =>
args.unit === 'Feet' ? parent.heightInMeters * 3.281 : parent.heightInMeters,
}),
}),
}));
実際に使ってみる
import SchemaBuilder from "@pothos/core";
import { writeFile } from "fs/promises";
import { lexicographicSortSchema, printSchema } from "graphql";
import { createYoga } from "graphql-yoga";
import { createServer } from "http";
import { join } from "path";
async function main() {
const builder = new SchemaBuilder({});
const LengthUnit = builder.enumType("LengthUnit", {
values: { Feet: {}, Meters: {} },
});
builder.queryType({
fields: (t) => ({
height: t.float({
args: {
unit: t.arg({
type: LengthUnit,
}),
},
resolve: (parent, args) => (args.unit === "Feet" ? 3.281 : 1),
}),
}),
});
const schema = builder.toSchema();
const schemaString = printSchema(lexicographicSortSchema(schema));
await writeFile(join(process.cwd(), "schema.graphql"), schemaString);
const yoga = createYoga({ schema });
const server = createServer(yoga);
server.listen(3000, () => {
console.info("http://localhost:3000/graphql");
});
}
main().catch((err) => console.error(err));
動作確認
npx ts-node args.ts
GraphQL スキーマ
enum LengthUnit {
Feet
Meters
}
type Query {
height(unit: LengthUnit): Float!
}
必須の引数
引数はデフォルトではオプショナルになる。
つまり null か undefined でも OK になる。
必須にするには required オプションを true
に設定する。
const Query = builder.queryType({
fields: (t) => ({
nullableArgs: t.stringList({
args: {
optional: t.arg.string(),
required: t.arg.string({ required: true }),
requiredList: t.arg.stringList({ required: true }),
sparseList: t.stringList({
required: {
list: true,
items: false,
},
}),
},
resolve: (parent, args) => Object.keys(args),
}),
}),
});
リストの場合はリスト自体はオプショナルだがリストの要素は必須になるようだ。
引数をデフォルトで必須にすることも SchemaBuilder のオプションで設定できる。
実際に試してみる
import SchemaBuilder from "@pothos/core";
import { writeFile } from "fs/promises";
import { lexicographicSortSchema, printSchema } from "graphql";
import { createYoga } from "graphql-yoga";
import { createServer } from "http";
import { join } from "path";
async function main() {
const builder = new SchemaBuilder({});
const LengthUnit = builder.enumType("LengthUnit", {
values: { Feet: {}, Meters: {} },
});
builder.queryType({
fields: (t) => ({
nullableArgs: t.stringList({
args: {
optional: t.arg.string(),
required: t.arg.string({ required: true }),
requiredList: t.arg.stringList({ required: true }),
sparseList: t.arg.stringList({
required: {
list: true,
items: false,
},
}),
},
resolve: (parent, args) => Object.keys(args),
}),
}),
});
const schema = builder.toSchema();
const schemaString = printSchema(lexicographicSortSchema(schema));
await writeFile(join(process.cwd(), "schema.graphql"), schemaString);
const yoga = createYoga({ schema });
const server = createServer(yoga);
server.listen(3000, () => {
console.info("http://localhost:3000/graphql");
});
}
main().catch((err) => console.error(err));
動作確認
npx ts-node args.ts
リスト引数は必ずしもリストで無くても良い
下記のクエリが普通に成功する、知らなかった。
query MyQuery {
nullableArgs(required: "2", requiredList: "3", sparseList: "4", optional: "1")
}
リスト
リストを使うには type を []
で囲む。
const Query = builder.queryType({
fields: t => ({
giraffeNameChecker: t.booleanList({
{
args: {
names: t.arg.stringList({
required: true,
})
moreNames: t.arg({
type: ['String'],
required: true
})
},
},
resolve: (parent, args) => {
return [...args.names, ...args.moreNames].filter(name => ['Gina', 'James'].includes(name)),
}
})
}),
});
ネストしたリスト
FieldBuilder の arg.listRef() メソッドを使う。
const Query = builder.queryType({
fields: t => ({
example: t.boolean({
{
args: {
listOfListOfStrings: t.arg({
type: t.arg.listRef(t.arg.listRef('String')),
}),
listOfListOfNullableStrings: t.arg({
type: t.arg.listRef(
// By default listRef creates a list of Non-null items
// This can be overridden by passing in required: false
t.arg.listRef('String', { required: false }),
{ required: true }),
})
},
},
resolve: (parent, args) => {
return true
}
})
}),
});
飽きてきたのでプラグインを試す
ガイドを一通り読んでおくことは重要だと認識しつつも単調なので飽きてきた。
ここらへんでプラグインを試してみて新鮮さを取り戻そう。
モックプラグイン
API 設計段階などで便利そう。
プラグインのインストール
npm install @pothos/plugin-mocks
プラグインの使い方
SchemaBuilder を作成する時にプラグインを指定する。
import MocksPlugin from '@pothos/plugin-mocks';
const builder = new SchemaBuilder({
plugins: [MocksPlugin],
});
Query Type などの resolve() ではエラーを投げるようにしておき、toSchema() を呼び出す時に mocks
オプションを指定する。
builder.queryType({
fields: (t) => ({
someField: t.string({
resolve: () => {
throw new Error('Not implemented');
},
}),
}),
});
builder.toSchema({
mocks: {
Query: {
someField: (parent, args, context, info) => 'Mock result!',
},
},
});
実際に使ってみる
touch mocks.ts
import SchemaBuilder from "@pothos/core";
import MocksPlugin from "@pothos/plugin-mocks";
import { createYoga } from "graphql-yoga";
import { createServer } from "http";
async function main() {
const builder = new SchemaBuilder({
plugins: [MocksPlugin],
});
builder.queryType({
fields: (t) => ({
someField: t.string({
resolve: () => {
throw new Error("Not implemented");
},
}),
}),
});
const schema = builder.toSchema({
mocks: {
Query: {
someField: () => "Mock result!",
},
},
});
const yoga = createYoga({ schema });
const server = createServer(yoga);
server.listen(3000, () => console.info("http://localhost:3000/graphql"));
}
main().catch((err) => console.error(err));
動作確認
npx ts-node mocks.ts
モックプラグインの所感
正直あまりコード補完などが効かないので微妙。
resolve() にダミーデータを書くのとあまり変わらない気がする。
resolve() の方がコード補完が効くので使いやすそう。
今日はここまで(2回目)
次回はコンテキストの理解を深めていこう。