Pełne bezpieczeństwo typów full-stack z Axolotl, Zeus i Vite
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:
- Schema definiuje
createTodo(content: String!): String! - Axolotl generuje
args: { content: string }dla resolverów - Resolver używa
{ content }z wsparciem TypeScript - Zeus generuje typy klienta na podstawie tego samego schematu
- React hook wywołuje
createTodo: [{ content }, true]z type checkingiem - 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:
- Zmiany w schemacie propagują się wszędzie – zmodyfikuj pole, uruchom build, TypeScript powie Ci co nie działa po stronie backendu i frontendu
- AI assistant rzeczywiście pomaga – dyrektywa
@resolverpozwala Copilotowi zrozumieć, co zaimplementować, bez tłumaczenia architektury - Jedno
npm run dev– nie trzeba osobnego terminala dla frontendu, nie ma potrzeby proxy, zero problemów z CORS - 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