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?

SvelteKitとGraphQLで効率的なデータフェッチングを実現

Posted at

こんにちは!SvelteKitはREST APIとの統合が得意ですが、GraphQLを使うことで、さらに柔軟で効率的なデータフェッチングが可能です。この記事では、SvelteKitとurqlライブラリを使って、GraphQL APIからブログ記事を取得する方法を解説します。コード例を交えながら、シンプルで型安全なデータ取得を体験しましょう!


なぜGraphQLか?

GraphQLは、必要なデータだけをリクエストできるAPI仕様で、以下のような利点があります:

  • 柔軟性:クライアントが必要なフィールドだけを指定。
  • 型安全:スキーマベースで、TypeScriptとの相性が良い。
  • 単一エンドポイント:複数のRESTエンドポイントを管理する必要なし。

SvelteKitでGraphQLを使うと、urqlのような軽量ライブラリで簡単にデータフェッチングができます。ReactのApollo Clientに比べ、urqlは設定がシンプルで、SvelteKitの軽快な開発体験にマッチします。


プロジェクトのセットアップ

新しいSvelteKitプロジェクトを作成:

npm create svelte@latest graphql-blog
cd graphql-blog
npm install

urqlと関連パッケージをインストール:

npm install @urql/svelte graphql

Skeleton UIを追加:

npm install @skeletonlabs/skeleton @skeletonlabs/tw-plugin --save-dev

この記事では、GraphQL Hiveのような公開GraphQL APIを仮に使用しますが、実際には自分のバックエンド(例:Hasura)を推奨します。


GraphQLの統合

1. urqlクライアントの設定

src/lib/graphql-client.tsを作成:

import { createClient, defaultExchanges } from '@urql/svelte';

export const client = createClient({
  url: 'https://api.graphqlhive.com/graphql', // 仮のGraphQLエンドポイント
  exchanges: defaultExchanges
});
  • 解説
    • createClientでGraphQLエンドポイントを設定。
    • defaultExchangesでキャッシュやフェッチングを有効化。

2. ブログ記事の取得

ブログ記事を取得するクエリを定義。src/lib/queries.ts

import { gql } from '@urql/svelte';

export const GET_POSTS = gql`
  query GetPosts {
    posts {
      id
      title
      slug
      excerpt
      createdAt
    }
  }
`;

export const GET_POST = gql`
  query GetPost($slug: String!) {
    post(slug: $slug) {
      id
      title
      slug
      excerpt
      content
      createdAt
    }
  }
`;

3. ブログ一覧ページの実装

src/routes/+page.svelteを更新:

<script>
  import { operationStore, query } from '@urql/svelte';
  import { GET_POSTS } from '$lib/queries';
  import { Card, CardHeader, CardContent, CardFooter, Spinner } from '@skeletonlabs/skeleton';

  const posts = operationStore(GET_POSTS);
  query(posts);
</script>

<div class="container mx-auto p-8">
  <h1 class="h1 mb-8">ブログ記事一覧</h1>
  {#if $posts.fetching}
    <div class="flex justify-center">
      <Spinner />
    </div>
  {:else if $posts.error}
    <p class="text-error-500">エラー: {$posts.error.message}</p>
  {:else}
    <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
      {#each $posts.data.posts as post}
        <Card>
          <CardHeader>
            <h2 class="h3">{post.title}</h2>
            <p class="text-sm text-surface-500">{post.createdAt}</p>
          </CardHeader>
          <CardContent>
            <p>{post.excerpt}</p>
          </CardContent>
          <CardFooter>
            <a href="/blog/{post.slug}" class="btn variant-filled-primary">続きを読む</a>
          </CardFooter>
        </Card>
      {/each}
    </div>
  {/if}
</div>

4. 記事詳細ページの実装

src/routes/blog/[slug]/+page.svelteを作成:

<script>
  import { operationStore, query } from '@urql/svelte';
  import { GET_POST } from '$lib/queries';
  import { Spinner, Card } from '@skeletonlabs/skeleton';

  export let data;
  const post = operationStore(GET_POST, { slug: data.slug });
  query(post);
</script>

<svelte:head>
  {#if !$post.fetching && !$post.error}
    <title>{$post.data.post.title} | My Blog</title>
    <meta name="description" content={$post.data.post.excerpt} />
  {/if}
</svelte:head>

<div class="container mx-auto p-8">
  {#if $post.fetching}
    <div class="flex justify-center">
      <Spinner />
    </div>
  {:else if $post.error}
    <p class="text-error-500">エラー: {$post.error.message}</p>
  {:else}
    <Card class="p-6">
      <header class="mb-4">
        <h1 class="h2">{$post.data.post.title}</h1>
        <p class="text-sm text-surface-500">{$post.data.post.createdAt}</p>
      </header>
      <section class="prose">
        <p>{$post.data.post.content}</p>
      </section>
      <footer class="mt-6">
        <a href="/" class="btn variant-ghost-primary">一覧に戻る</a>
      </footer>
    </Card>
  {/if}
</div>

src/routes/blog/[slug]/+page.ts

export function load({ params }) {
  return {
    slug: params.slug
  };
}

5. 動作確認

プロジェクトを起動:

npm run dev

以下のURLを試す:

  • http://localhost:5173:GraphQLから取得した記事一覧。
  • http://localhost:5173/blog/your-slug:記事詳細ページ。

Skeleton UIのカードデザインで、記事がモダンに表示されます。ReactのApollo Clientでは、プロバイダーの設定やキャッシュ管理が複雑ですが、urqlは軽量で、SvelteKitのストアと自然に統合されます。


やってみよう!(チャレンジ)

GraphQLのミューテーションを使って、記事を投稿するフォームを追加してみましょう!以下のようなミューテーションを定義:

export const CREATE_POST = gql`
  mutation CreatePost($title: String!, $slug: String!, $excerpt: String!, $content: String!) {
    createPost(title: $title, slug: $slug, excerpt: $excerpt, content: $content) {
      id
      title
      slug
    }
  }
`;

src/routes/create/+page.svelteでフォームを実装し、投稿後に一覧ページにリダイレクトしてみてください。また、urqlの@urql/exchange-graphcacheを導入して、キャッシュを自動更新する機能を試してみましょう!


まとめ

この記事では、SvelteKitとurqlを使って、GraphQL APIからブログ記事を効率的に取得しました。Skeleton UIでモダンなUIを簡単に作り、型安全なデータフェッチングを実現しました。ReactのApollo Clientと比べ、urqlは設定が少なく、SvelteKitの軽快な開発体験を最大限に活かせます。

この記事が役に立ったと思ったら、LGTMストックしていただけると嬉しいです!GraphQLで挑戦したいことや質問があれば、コメントで教えてください。また会いましょう!

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?