Pełne bezpieczeństwo typów full-stack z Axolotl, Zeus i Vite

by

Pełne bezpieczeństwo typów full-stack z Axolotl, Zeus i Vite

Od lat tworzę API GraphQL i największą bolączką zawsze było utrzymanie spójności typów. Piszesz swój schemat, ręcznie tworzysz typy TypeScript dla resolverów, generujesz typy klienta i masz nadzieję, że nic się nie rozjedzie. Jedna literówka w typie zwracanym przez resolver i debugujesz błędy czasu wykonania.

Ten przykład pokazuje inne podejście — prawdziwe, kompleksowe bezpieczeństwo typów od schematu aż po komponenty React, uruchomione na jednym serwerze.

Konfiguracja

Przykład yoga-federated uruchamia wszystko na jednym porcie:

  • Backend: Express + GraphQL Yoga z typami generowanymi przez Axolotl
  • Frontend: React + Vite z klientem GraphQL generowanym przez Zeus
  • Oba: Ten sam serwer, to samo origin

http://localhost:4002/ -> Frontend React (SSR)

http://localhost:4002/graphql -> GraphQL Playground

W trybie deweloperskim Vite działa jako middleware Express dla HMR. W produkcji Express serwuje zbudowany frontend. Jedno wdrożenie, zero konfiguracji CORS.

Dyrektywa @resolver

Oto schemat:

type Todo {
  _id: String!
  content: String!
  done: Boolean
}

type AuthorizedUserMutation {
  createTodo(content: String!, secret: Secret): String! @resolver
  todoOps(_id: String!): TodoOps! @resolver
}

type Query {
  user: AuthorizedUserQuery @resolver
}

Dyrektywa @resolver oznacza, które pola wymagają implementacji. Pola bez niej są rozwiązywane automatycznie na podstawie obiektu nadrzędnego.

To właśnie sprawia, że Axolotl świetnie współpracuje z asystentami AI. Gdy Copilot lub Cursor widzi schemat, dokładnie wie, które pola wymagają resolverów, a które nie. Zero zgadywania.

Podpowiedz AI: „Implement all @resolver fields” i otrzymasz poprawny kod, ponieważ schemat jasno określa, co trzeba zaimplementować.

Uruchom axolotl build i otrzymujesz typowane modele.

Frontend z Zeus

Zeus generuje typowanego klienta GraphQL na podstawie schematu.

// frontend/src/api.ts
import { Chain } from '../../src/zeus/index';

export const createGqlClient = (token?: string) => {
  const headers: Record<string, string> = {
    'Content-Type': 'application/json',
  };
  if (token) {
    headers['token'] = token;
  }
  return Chain('/graphql', { headers });
};

Zapytania są type-safe:

// frontend/src/hooks/useTodos.ts
const fetchTodos = async () => {
  const data = await gql()('query')({
    user: {
      todos: { _id: true, content: true, done: true },
    },
  });

  // TypeScript wie, że data.user?.todos to Todo[]
  if (data.user?.todos) {
    setTodos(data.user.todos);
  }
};

Mutacje również:

const createTodo = async (content: string) => {
  await gql()('mutation')({
    user: {
      createTodo: [{ content }, true],
    },
  });
};

Składnia [{ content }, true] oznacza: przekaż te argumenty, zwróć to pole. TypeScript sprawdza, czy content zgadza się ze schematem.

Express + Vite na jednym serwerze

Cały stack działa na jednym serwerze Express:

// src/index.ts
async function startServer() {
  const app = express();

  // Podłącz GraphQL
  const { yoga } = adapter({ resolvers, directives });
  app.use('/graphql', yoga);

  // Tryb deweloperski: Vite middleware dla HMR
  if (!isProduction) {
    const vite = await createViteServer({
      server: { middlewareMode: true },
      appType: 'custom',
    });
    app.use(vite.middlewares);
  } else {
    // Produkcja: serwuj zbudowane zasoby
    app.use(sirv(resolve(__dirname, '../dist/client')));
  }

  // SSR dla wszystkich innych tras
  app.use('*all', async (req, res) => {
    const { html } = await render(req.originalUrl);
    res.send(html);
  });
}

Masz:

  • HMR w trybie deweloperskim
  • SSR dla SEO
  • Jeden artefakt wdrożeniowy
  • Bez konfiguracji proxy

Przepływ typów

Todo przechodzi przez system w następujący sposób:

  1. Schema definiuje createTodo(content: String!): String!
  2. Axolotl generuje args: { content: string } dla resolverów
  3. Resolver używa { content } z wsparciem TypeScript
  4. Zeus generuje typy klienta na podstawie tego samego schematu
  5. React hook wywołuje createTodo: [{ content }, true] z type checkingiem
  6. Response wraca z odpowiednimi typami

Zmień schemat, uruchom axolotl build, a TypeScript pokaże, co się zepsuło.

Wypróbuj

cd examples/yoga-federated
npm install
npm run dev

Otwórz http://localhost:4002:

Zarejestruj się i jesteś w środku, możesz tworzyć todo

GraphQL Playground pod adresem http://localhost:4002/graphql:

Co mi się w tym podoba

Po zastosowaniu tego wzorca parę rzeczy się wyróżnia:

  1. Zmiany w schemacie propagują się wszędzie – zmodyfikuj pole, uruchom build, TypeScript powie Ci co nie działa po stronie backendu i frontendu
  2. AI assistant rzeczywiście pomaga – dyrektywa @resolver pozwala Copilotowi zrozumieć, co zaimplementować, bez tłumaczenia architektury
  3. Jedno npm run dev – nie trzeba osobnego terminala dla frontendu, nie ma potrzeby proxy, zero problemów z CORS
  4. SSR po prostu działa – co przydaje się jeśli zależy Ci na SEO

Minusem jest sprzężenie – frontend i backend są wdrażane razem. Dla większości projektów to w porządku. Jeśli potrzebujesz niezależnego skalowania, wtedy rozdzielisz je później.

Linki


Zbudowane z Axolotl + GraphQL Yoga + Zeus przez Aexol Studio

Ready to start?

Let's talk about your project.

Free consultation and quote within 48h. No strings attached.

Contact us