使用Nuxt3 + Apollo Server创建用于学习的简便GraphQL服务器(和查询测试场地)

使用Nuxt3 + Apollo Server创建一个简单易用的学习用GraphQL服务器(以及用于发出查询的沙盒)[GraphQL学习支持]

请先给我教授代码。

这就是。

 

如果你克隆上述存储库并运行 npm install & npm run dev,你就可以启动一个作为模拟实现的GraphQL服务器和一个可以发出查询的沙盒。

前者在 http://localhost:3000/api/graphql 上启动,后者在 http://localhost:3000/ 上启动。

graphql_nuxt3_sandbox_04.gif

経緯可以用以下方式翻译成中文: 经过

    • GraphQLの学習をしたい時に、ちょっと手元でいじれるGraphQLサーバーがあったらいいなと思ったので、

 

    • git cloneでローカルに落とせてお手軽に試せるGraphQLサーバーを Nuxt3 + Apollo Server で作ることにしたよ

 

    当初Nuxt3ではなくNitroでやろうとしたから、その時の記録も残っているよ
スクリーンショット 2022-11-18 13.06.05.jpg

作者个人简介

我在Kenpal株式会社作为IT工程师,负责进行各种操作的faable01。

 

因为我喜欢制造东西,从大学时代起就和创作伙伴一起写小说并享受其中的乐趣,但当时我从来没有想过自己将来会成为IT技术人员。经过曲折的经历,直到我渐渐步入三十年纪的中期,才第一次接触到这个行业。

我的兴趣是用口语写技术文章。
我经常在个人网站上写技术文章,欢迎阅读(截至2023年4月,我经常写有关AWS CDK的爆速无服务器开发工具SST的文章)。

 

使用Nuxt3 + Apollo Server构建GraphQL服务器。

赶紧开始,使用Nuxt 3和Apollo Server来构建GraphQL服务器吧。

在适当的目录下创建一个otameshi_graphql/nuxt3目录,并在其中构建Nuxt3 + Apollo Server。

我们先创建一个Nuxt3的模板项目怎么样?

mkdir otameshi_graphql
cd otameshi_graphql
npx nuxi init nuxt3

nuxi init 是用于创建 Nuxt3 模板的命令吧。

一旦完成雏形,接下来进入nuxt3目录并安装所需的包,创建Server API Routes中的雏形GraphQL路由。

cd nuxt3
npm install --save-dev @apollo/server graphql @as-integrations/h3
mkdir server
mkdir server/api
echo 'import { ApolloServer } from "@apollo/server";
import { startServerAndCreateH3Handler } from "@as-integrations/h3";

const typeDefs = `#graphql
  type Query {
    hello: String
  }
`;
const resolvers = {
  Query: {
    hello: () => "world",
  },
};
const apollo = new ApolloServer({
  typeDefs,
  resolvers,
});

export default startServerAndCreateH3Handler(apollo);' > server/api/graphql.ts
npm run dev

当开发服务器启动后,可以试着使用curl命令从GraphQL服务器获取预期的正确响应。

curl -X POST -H "Content-Type: application/json" \
  --data '{"query":"{ hello }"}' http://localhost:3000/api/graphql

# ▼ 得られるレスポンス
# {"data":{"hello":"world"}}

没问题,我们确认了 GraphQL 服务器可以正常运行。

不过,如果现在还是这样的话,有点孤单,所以要不要试着让GraphQL服务器的内容更加丰富一些呢?

增加一个Schema,试着在Nuxt3上构建一个更符合实际需要的GraphQL服务器。

我想创建一个仅仅作为沙盒使用的GraphQL服务器,所以故意不连接到数据库。我们可以在Nuxt3服务器内创建一个能够添加和编辑用户信息的GraphQL服务器,将其仅仅作为一个普通对象来保存。

首先作为前期准备,先安装一个用于生成UUID的库。

npm install --save-dev uuid @types/uuid

然后,因为我想要制作一个查询发行界面,所以想提前引入 Tailwind CSS 为那个时候做准备。

npm install --save-dev @nuxtjs/tailwindcss

请按如下方式配置Nuxt3的设置文件。

// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  // Nuxt3上のTypeScriptの型チェックを厳密にする
  typescript: {
    strict: true,
  },
  // Tailwind CSS をモジュールとして導入
  modules: ["@nuxtjs/tailwindcss"],
});

前期准备就只有这些。

下面要做的是GraphQL服务器的构建工作。

首先,在一个单独的文件中定义模式。在Nuxt3应用程序的根目录下创建一个typeDefs.ts文件,并在其中编写模式。

请定义一个可以对具有用户名称和年龄的实体进行CRUD操作的查询和变更操作。

export const typeDefs = `#graphql
  type User {
    # uuid
    id: ID
    # フルネーム
    full_name: String
    # 年齢
    age: Int
  }
  input UserCreateInput {
    # フルネーム
    full_name: String
    # 年齢
    age: Int
  }
  input UserUpdateInput {
    # uuid
    id: ID
    # フルネーム
    full_name: String
    # 年齢
    age: Int
  }
  input UserDeleteInput {
    # uuid
    id: ID
  }
  input UserQueryInput {
    # uuidを直接指定できる引数
    id: ID
    # 「名前の一部分」を指定できる引数
    name_contains: String
    # 年齢の範囲を指定できる引数(最小値)
    age_gte: Int
    # 年齢の範囲を指定できる引数(最大値)
    age_lte: Int
  }
  type Query {
    # ユーザーを取得する
    users(input: UserQueryInput): [User]
  }
  type Mutation {
    # ユーザーを作成する
    createUser(input: UserCreateInput): User
    # ユーザーを更新する
    updateUser(input: UserUpdateInput): User
    # ユーザーを削除する
    deleteUser(input: UserDeleteInput): User
  }
`;

就是这样吧。

如果这样的话,我们可以使用这个 typeDefs 在 server/api/graphql.ts 中建立一个 GraphQL 服务器。

如果是实际项目级别的GraphQL服务器开发,我们会从模式中生成TypeScript的类型,但是这次只是一个小规模的学习项目,所以没有刻意去做那样的事情。

import { ApolloServer } from "@apollo/server";
import { startServerAndCreateH3Handler } from "@as-integrations/h3";
import { v4 as uuidv4 } from "uuid";
import { typeDefs } from "~~/typeDefs";

type User = {
  id: string;
  full_name: string;
  age: number;
};
type UserCreateInput = {
  full_name: string;
  age: number;
};
type UserUpdateInput = {
  id: string;
  full_name: string;
  age: number;
};
type UserDeleteInput = {
  id: string;
};
type UserQueryInput = {
  id?: string;
  name_contains?: string;
  age_gte?: number;
  age_lte?: number;
};

const users: User[] = [
  ...Array(10)
    .fill(null)
    .map((_, i) => ({
      // 動作確認用に1つだけuuidを固定させている
      id: i === 0 ? "660688c2-3b47-4de3-ad0e-af92a75731f9" : uuidv4(),
      full_name: `${
        ["John", "Mike", "Tom", "Bob"][
          // ランダムに選ぶ
          Math.floor(Math.random() * 4)
        ]
      } ${
        ["Smith", "Brown", "Johnson", "Williams"][
          // ランダムに選ぶ
          Math.floor(Math.random() * 4)
        ]
      }`,
      age: 20 + i,
    })),
];

const resolvers = {
  Query: {
    users: (_: unknown, { input }: { input?: UserQueryInput }) => {
      return users.filter((user) => {
        return (
          !input ||
          ((!input.id || user.id === input.id) &&
            (!input.name_contains ||
              user.full_name.includes(input.name_contains)) &&
            (!input.age_gte || user.age >= input.age_gte) &&
            (!input.age_lte || user.age <= input.age_lte))
        );
      });
    },
  },
  Mutation: {
    createUser: (_: unknown, { input }: { input: UserCreateInput }) => {
      const user = {
        id: uuidv4(),
        full_name: input.full_name,
        age: input.age,
      };
      users.push(user);
      return user;
    },
    updateUser: (_: unknown, { input }: { input: UserUpdateInput }) => {
      const user = users.find((user) => user.id === input.id);
      if (!user) throw new Error("User not found");
      user.full_name = input.full_name;
      user.age = input.age;
      return user;
    },
    deleteUser: (_: unknown, { input }: { input: UserDeleteInput }) => {
      const user = users.find((user) => user.id === input.id);
      if (!user) throw new Error("User not found");
      users.splice(users.indexOf(user), 1);
      return user;
    },
  },
};
const apollo = new ApolloServer({
  typeDefs,
  resolvers,
});

/**
 * ## 各クエリのcurl実行例
 *
 * ```
 * curl -X POST -H "Content-Type: application/json" -d '{"query":"{ users(input: {}) { id full_name age } }"}' http://localhost:3000/api/graphql
 * curl -X POST -H "Content-Type: application/json" -d '{"query":"{ users(input: { id: \"660688c2-3b47-4de3-ad0e-af92a75731f9\" }) { id full_name age } }"}' http://localhost:3000/api/graphql
 * curl -X POST -H "Content-Type: application/json" -d '{"query":"{ users(input: { name_contains: \"J\" }) { id full_name age } }"}' http://localhost:3000/api/graphql
 * curl -X POST -H "Content-Type: application/json" -d '{"query":"{ users(input: { age_gte: 25, age_lte: 30 }) { id full_name age } }"}' http://localhost:3000/api/graphql
 * curl -X POST -H "Content-Type: application/json" -d '{"query":"mutation { createUser(input: { full_name: \"John Smith\", age: 20 }) { id full_name age } }"}' http://localhost:3000/api/graphql
 * curl -X POST -H "Content-Type: application/json" -d '{"query":"mutation { updateUser(input: { id: \"660688c2-3b47-4de3-ad0e-af92a75731f9\", full_name: \"John Smith\", age: 20 }) { id full_name age } }"}' http://localhost:3000/api/graphql
 * curl -X POST -H "Content-Type: application/json" -d '{"query":"mutation { deleteUser(input: { id: \"660688c2-3b47-4de3-ad0e-af92a75731f9\" }) { id full_name age } }"}' http://localhost:3000/api/graphql
 * ```
 */
export default startServerAndCreateH3Handler(apollo);

我已经根据这个架构定义,实现了用户的增删改查操作的查询和变更。

这就是作为GraphQL服务器的实现的结束。

在源代码的注释中也提到了,如果想要使用curl命令进行确认,可以使用以下命令进行确认。

全ユーザ取得Query:
curl -X POST -H “Content-Type: application/json” -d ‘{“query”:”{ users(input: {}) { id full_name age } }”}’ http://localhost:3000/api/graphql

ID指定Query:
curl -X POST -H “Content-Type: application/json” -d ‘{“query”:”{ users(input: { id: \”660688c2-3b47-4de3-ad0e-af92a75731f9\” }) { id full_name age } }”}’ http://localhost:3000/api/graphql

名前の部分一致Query:
curl -X POST -H “Content-Type: application/json” -d ‘{“query”:”{ users(input: { name_contains: \”J\” }) { id full_name age } }”}’ http://localhost:3000/api/graphql

年齢の範囲指定Query:
curl -X POST -H “Content-Type: application/json” -d ‘{“query”:”{ users(input: { age_gte: 25, age_lte: 30 }) { id full_name age } }”}’ http://localhost:3000/api/graphql

ユーザ作成Mutation:
curl -X POST -H “Content-Type: application/json” -d ‘{“query”:”mutation { createUser(input: { full_name: \”John Smith\”, age: 20 }) { id full_name age } }”}’ http://localhost:3000/api/graphql

ユーザ更新Mutation:
curl -X POST -H “Content-Type: application/json” -d ‘{“query”:”mutation { updateUser(input: { id: \”660688c2-3b47-4de3-ad0e-af92a75731f9\”, full_name: \”John Smith\”, age: 20 }) { id full_name age } }”}’ http://localhost:3000/api/graphql

ユーザ削除Mutation:
curl -X POST -H “Content-Type: application/json” -d ‘{“query”:”mutation { deleteUser(input: { id: \”660688c2-3b47-4de3-ad0e-af92a75731f9\” }) { id full_name age } }”}’ http://localhost:3000/api/graphql

现在你可以使用 npm run dev 命令在本地主机上启动 GraphQL 服务器了,接下来,你可以从想要学习的编程语言或框架发送查询到 GraphQL 服务器,并尝试感受一下。

我原本打算以這種方式結束這篇文章,但既然有機會(也為了更容易學習),我打算在Nuxt3的前端中建立一個可以從瀏覽器自由向GraphQL服務器發送查詢的沙盒界面。

尝试从Nuxt3的前端创建一个沙盒,用于方便地测试向GraphQL服务器发送查询。

我的結論是,我們要創建一個能夠從瀏覽器上的文本區域輸入查詢並發送到服務器的畫面。

graphql_nuxt3_sandbox_04.gif

我会用草稿写。让我们直接在Nuxt3的根目录下的app.vue文件中一次性地编写。(暂且将优先确保在短时间内运行为目标,所以代码可能不会很整洁,请原谅)

<script setup lang="ts">
import { ChangeEvent } from "rollup";
import { typeDefs } from "~~/typeDefs";

const query = ref(`query {users { id full_name age }}`);
const result = ref("ここに結果が表示されるよ");
const execute = async () => {
  const res = await fetch("/api/graphql", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      query: query.value,
    }),
  });
  result.value = JSON.stringify(await res.json(), null, 2);
};
const schema = typeDefs;
const showSchema = ref(false);

const sampleQueries = {
  全ユーザ取得Query: `query {
  users {
    id
    full_name
    age
  }
}`,
  ID指定Query: `query {
  users(input: { id: "660688c2-3b47-4de3-ad0e-af92a75731f9" }) {
    id
    full_name
    age
  }
}`,
  名前の部分一致Query: `query {
  users(input: { name_contains: "J" }) {
    id
    full_name
    age
  }
}`,
  年齢の範囲指定Query: `query {
  users(input: { age_gte: 25, age_lte: 30 }) {
    id
    full_name
    age
  }
}`,
  ユーザ作成Mutation: `mutation {
  createUser(input: { full_name: "John Smith", age: 20 }) {
    id
    full_name
    age
  }
}`,
  ユーザ更新Mutation: `mutation {
  updateUser(
    input: {
      id: "660688c2-3b47-4de3-ad0e-af92a75731f9"
      full_name: "John Smith"
      age: 20
    }
  ) {
    id
    full_name
    age
  }
}`,
  ユーザ削除Mutation: `mutation {
  deleteUser(input: { id: "660688c2-3b47-4de3-ad0e-af92a75731f9" }) {
    id
    full_name
    age
  }
}`,
}
</script>

<template>
  <div class="container mx-auto max-w-5xl">
    <div class="m-4 border-2 bg-emerald-100 p-4 grid gap-y-4">
      <h1 class="text-2xl font-bold text-gray-800">
        GraphQL Sandbox
      </h1>
      <h2 class="text-xl font-bold text-gray-800 border-l-8 border-l-emerald-400 pl-2">
        使い方
      </h2>
      <p>
        下のテキストエリアにGraphQLのクエリを書いて、実行ボタンを押すと、結果が表示されます。
      </p>
      <p>
        なおGraphQLサーバーのエンドポイントは Nuxt3 Server API Routes の
        <code class="text-sm font-bold text-gray-800">
          /api/graphql
        </code>
        です
      </p>
      <h2 class="text-xl font-bold text-gray-800 border-l-8 border-l-emerald-400 pl-2">
        クエリ
      </h2>
      <button
        class="text-sm font-bold text-gray-800 border-2 border-gray-800 rounded-md px-2 py-1 max-w-md bg-white hover:bg-gray-800 hover:text-white"
        @click="showSchema = true">
        スキーマを見る?
      </button>
      <teleport to="body">
        <div v-if="showSchema" @click="(event) => {
          // 子要素以外の場所をクリックしたら showSchema = false; にする
          if (event.target === event.currentTarget) {
            showSchema = false;
          }
        }" class="fixed inset-0 bg-black bg-opacity-50 flex justify-center items-center h-screen w-screen">
          <!-- 閉じるボタンをバツで表現する -->
          <button class="absolute top-0 right-0 m-6 text-5xl font-bold text-white hover:text-gray-200"
            @click="showSchema = false">
            ×
          </button>
          <div class="bg-white rounded-md p-4 w-4/5 h-4/5 overflow-auto">
            <pre class="text-sm font-mono text-gray-800">{{ schema }}</pre>
          </div>
        </div>
      </teleport>
      <!-- selectでsampleQueriesの中から一つ選び、queryに設定する -->
      <select
        class="text-sm font-bold text-gray-800 border-2 border-gray-800 rounded-md px-2 py-1 max-w-md hover:bg-gray-800 hover:text-white"
        @change="(e) => {
          query = (e.target as HTMLSelectElement | null)?.value ?? '';
        }">
        <option disabled selected>
          サンプルクエリを選択してください
        </option>
        <option v-for="(value, key) in sampleQueries" :value="value">{{ key }}</option>
      </select>
      <textarea v-model="query" rows="10" cols="100"
        class="border-2 border-gray-300 bg-gray-100 bg-opacity-70 rounded-md p-2 w-full">
      </textarea>
      <div>
        <button
          class="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-600 focus:ring-opacity-50"
          @click="execute">
          実行
        </button>
      </div>
      <!-- 下記にコードの実行結果を表示する -->
      <h2 class="text-xl font-bold text-gray-800 border-l-8 border-l-emerald-400 pl-2">
        実行結果
      </h2>
      <div class="border-2 border-gray-300 bg-gray-100 bg-opacity-70 rounded-md p-2 w-full overflow-x-auto">
        <!-- スクロール可能なpreタグ -->
        <pre class="overflow-x-auto">{{ result }}</pre>
      </div>
    </div>
  </div>
</template>

大概是这样吧。

如果你能写到这里,就运行 npm run dev 启动开发服务器,然后访问 http://localhost:3000/。

这样做的话,就会显示出刚才在GIF动画中展示的那种画面,然后你就可以从文本区域自由地发出查询了。

只需提供一种中文翻译:
在学习GraphQL的过程中,你可以通过亲自触碰和修改来进一步掌握它。

总之/概括来说

如果要构建一个基于Nuxt3 + Apollo Server的GraphQL服务器模板,只需要下面这条命令就可以了。

npx nuxi init [プロジェクト名]
cd [プロジェクト名]
npm install --save-dev @apollo/server graphql @as-integrations/h3
mkdir server
mkdir server/api
echo 'import { ApolloServer } from "@apollo/server";
import { startServerAndCreateH3Handler } from "@as-integrations/h3";

const typeDefs = `#graphql
  type Query {
    hello: String
  }
`;
const resolvers = {
  Query: {
    hello: () => "world",
  },
};
const apollo = new ApolloServer({
  typeDefs,
  resolvers,
});

export default startServerAndCreateH3Handler(apollo);' > server/api/graphql.ts
npm run dev

进一步从这里开始添加各种细节,

    • モックとして動作するGraphQLサーバと

 

    クエリを発行する砂場の画面

如果要创建,请使用类似下面仓库的代码进行操作。

 

想学习GraphQL的人,可以试着动手操作一下。

如果你只想学习发出查询的那一方的实现,而不需要学习服务器端的训练,我认为模拟服务器和沙箱会很有帮助。如果愿意的话,可以试试看。

广告
将在 10 秒后关闭
bannerAds