GraphQL интеграция

Fresh — это современный фреймворк для создания веб-приложений на базе Deno, ориентированный на высокую производительность, нативный SSR (Server-Side Rendering) и минимализм. Основная идея заключается в том, чтобы максимально использовать возможности ES-модулей и сервера без лишних слоёв абстракции, обеспечивая мгновенную отдачу страниц и минимальный размер бандлов.

Fresh построен на трёх ключевых принципах:

  1. Zero JavaScript by default — клиентский JavaScript загружается только при необходимости, что ускоряет время загрузки страниц.
  2. Islands Architecture — интерактивные компоненты (островки) внедряются в статически сгенерированный HTML, минимизируя работу JS на клиенте.
  3. Роутинг через файловую систему — структура папок и файлов напрямую соответствует маршрутам, что упрощает организацию кода.

Работа с данными через GraphQL

В современных приложениях Fresh часто интегрируют с GraphQL, позволяя запрашивать данные на сервере и передавать их в компоненты Islands. GraphQL обеспечивает более гибкое взаимодействие с API по сравнению с REST, позволяя выбирать только необходимые поля и уменьшать объём передаваемых данных.

Настройка GraphQL клиента

Для работы с GraphQL в Fresh можно использовать стандартные fetch вызовы, но чаще подключают легковесные клиенты, например graphql-request. Основная настройка включает указание URL GraphQL сервера и при необходимости заголовков авторизации:

import { GraphQLClient } FROM "https://cdn.skypack.dev/graphql-request";

const client = new GraphQLClient("https://api.example.com/graphql", {
  headers: {
    Authorization: `Bearer ${Deno.env.get("API_TOKEN")}`,
  },
});

export default client;

Формирование запросов

Запросы в GraphQL описываются в виде строковых литералов. Fresh позволяет выполнять их на стороне сервера при рендеринге страниц:

import client FROM "./client.ts";

const query = `
  query GetPosts($limit: Int!) {
    posts(LIMIT: $limit) {
      id
      title
      content
    }
  }
`;

const variables = { LIMIT: 10 };

const data = await client.request(query, variables);

Особенности работы в Fresh:

  • Запросы выполняются в route handler или islands компонентах при серверном рендеринге.
  • Полученные данные передаются напрямую в компонент, что исключает лишние этапы сериализации.

Использование GraphQL с Islands

В архитектуре Islands данные обычно подгружаются на сервере и передаются интерактивному компоненту через props. Такой подход позволяет:

  • Снизить количество клиентских запросов.
  • Сэкономить ресурсы браузера и ускорить загрузку.
  • Поддерживать полное SEO, так как HTML генерируется на сервере.

Пример Islands компонента с данными из GraphQL:

/** @jsx h */
import { h } from "preact";
import { useState } from "preact/hooks";

export default function PostsIsland({ posts }) {
  const [items, setItems] = useState(posts);

  return (
    <div>
      {items.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.content}</p>
        </article>
      ))}
    </div>
  );
}

На сервере:

import PostsIsland from "../islands/PostsIsland.tsx";
import client from "../graphql/client.ts";

const query = `
  query GetPosts {
    posts {
      id
      title
      content
    }
  }
`;

export default async function handler(req, ctx) {
  const posts = await client.request(query);
  return ctx.render({ posts });
}

Мутации и обработка данных

Помимо чтения данных, GraphQL поддерживает мутации для изменения состояния на сервере. В Fresh мутации выполняются аналогично запросам, но обычно через обработчики POST-запросов или внутри Islands для интерактивных форм:

const mutation = `
  mutation AddPost($title: String!, $content: String!) {
    addPost(title: $title, content: $content) {
      id
      title
      content
    }
  }
`;

const variables = { title: "Новая статья", content: "Содержимое статьи" };

const result = await client.request(mutation, variables);

Важно учитывать:

  • Серверный рендеринг может отправлять начальные данные через props, а последующие изменения обрабатывать клиентскими запросами.
  • Любые интерактивные формы с мутациями лучше изолировать в Islands, чтобы клиентский JS запускался только там, где это нужно.

Оптимизация запросов

Для больших приложений с Fresh и GraphQL актуальны следующие приёмы:

  • Выборочные поля — всегда запрашивать только необходимые данные.
  • Фрагменты — использовать GraphQL fragments для повторно используемых структур данных.
  • Кэширование на сервере — сохранять часто используемые данные в памяти или Redis, снижая количество запросов к внешнему API.
  • Серверный prefetching — подгружать данные до рендеринга страницы, чтобы интерактивные Islands сразу получали готовые props.

Интеграция с подписками

GraphQL поддерживает подписки для реального времени через WebSocket. В Fresh это реализуется через отдельный клиент на стороне Islands, так как подписки требуют постоянного соединения и не подходят для серверного рендеринга HTML напрямую. Стандартный подход:

  • Сервер отдает начальные данные через SSR.
  • Islands подключаются к подписке для получения обновлений в реальном времени.
import { useEffect, useState } from "preact/hooks";
import { createClient } from "graphql-ws";

const client = createClient({ url: "wss://api.example.com/graphql" });

export default function LivePostsIsland({ initialPosts }) {
  const [posts, setPosts] = useState(initialPosts);

  useEffect(() => {
    const dispose = client.subscribe(
      { query: "subscription { newPost { id title content } }" },
      {
        next: data => setPosts(prev => [...prev, data.newPost]),
        error: err => console.error(err),
        complete: () => console.log("Subscription complete")
      }
    );
    return () => dispose();
  }, []);

  return (
    <div>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.content}</p>
        </article>
      ))}
    </div>
  );
}

Использование подписок позволяет построить интерактивные и динамичные приложения без полной перезагрузки страницы.

Безопасность и авторизация

При интеграции GraphQL в Fresh важно соблюдать правила безопасности:

  • Авторизация на сервере — проверка токенов перед выполнением запросов к API.
  • Валидация входных данных — особенно для мутаций, чтобы избежать инъекций.
  • Сокрытие чувствительных данных — передавать клиенту только нужные поля.

Эти меры обеспечивают безопасное взаимодействие между клиентом и сервером, сохраняя принципы Zero JavaScript by default и Islands Architecture.