GraphQL API с TypeScript: пошаговый туториал
GraphQL — это язык запросов для API, который позволяет клиентам запрашивать только те данные, которые им нужны. В сочетании с TypeScript, статическая типизация помогает избежать ошибок на этапе разработки и делает код более предсказуемым. В этом туториале мы создадим полноценный GraphQL API с использованием Apollo Server и TypeScript.
Мы разберём: настройку проекта, определение схемы и типов, реализацию резолверов, мутации для изменения данных и интеграцию с базой данных (in-memory для простоты). К концу статьи вы сможете запустить собственный GraphQL сервер.
1. Подготовка проекта и установка зависимостей
Первым делом создадим новый проект Node.js и установим необходимые пакеты.
mkdir graphql-typescript-tutorial
cd graphql-typescript-tutorial
npm init -y
npm install apollo-server graphql
npm install -D typescript @types/node ts-node nodemon
Создадим файл tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"rootDir": "./src",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"]
}
Структура проекта будет такой:
src/index.ts— точка входаsrc/schema.ts— GraphQL схемаsrc/resolvers.ts— резолверыsrc/types.ts— TypeScript типы
2. Определение типов и схемы GraphQL
GraphQL схема описывает, какие данные доступны и как их запрашивать. Для примера создадим простую систему управления пользователями и постами.
Создадим src/types.ts:
export interface User {
id: number;
name: string;
email: string;
}
export interface Post {
id: number;
title: string;
content: string;
authorId: number;
}
export interface CreateUserInput {
name: string;
email: string;
}
export interface CreatePostInput {
title: string;
content: string;
authorId: number;
}
Теперь создадим src/schema.ts с определением типов GraphQL:
import { gql } from 'apollo-server';
export const typeDefs = gql`
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
}
input CreateUserInput {
name: String!
email: String!
}
input CreatePostInput {
title: String!
content: String!
authorId: ID!
}
type Query {
users: [User!]!
user(id: ID!): User
posts: [Post!]!
post(id: ID!): Post
}
type Mutation {
createUser(input: CreateUserInput!): User!
createPost(input: CreatePostInput!): Post!
}
`;
Обратите внимание: мы используем input типы для мутаций — это стандартная практика в GraphQL.
3. Реализация резолверов с TypeScript
Резолверы — это функции, которые возвращают данные для каждого поля схемы. Создадим src/resolvers.ts:
import { User, Post, CreateUserInput, CreatePostInput } from './types';
// In-memory база данных
let users: User[] = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' },
];
let posts: Post[] = [
{ id: 1, title: 'GraphQL Basics', content: '...', authorId: 1 },
{ id: 2, title: 'TypeScript Guide', content: '...', authorId: 2 },
];
let nextUserId = 3;
let nextPostId = 3;
export const resolvers = {
Query: {
users: (): User[] => users,
user: (_: unknown, args: { id: string }): User | undefined =>
users.find(u => u.id === Number(args.id)),
posts: (): Post[] => posts,
post: (_: unknown, args: { id: string }): Post | undefined =>
posts.find(p => p.id === Number(args.id)),
},
Mutation: {
createUser: (_: unknown, args: { input: CreateUserInput }): User => {
const newUser: User = {
id: nextUserId++,
name: args.input.name,
email: args.input.email,
};
users.push(newUser);
return newUser;
},
createPost: (_: unknown, args: { input: CreatePostInput }): Post => {
// Проверяем, существует ли автор
const author = users.find(u => u.id === Number(args.input.authorId));
if (!author) {
throw new Error('Author not found');
}
const newPost: Post = {
id: nextPostId++,
title: args.input.title,
content: args.input.content,
authorId: Number(args.input.authorId),
};
posts.push(newPost);
return newPost;
},
},
// Резолверы для связей между типами
User: {
posts: (parent: User): Post[] =>
posts.filter(p => p.authorId === parent.id),
},
Post: {
author: (parent: Post): User | undefined =>
users.find(u => u.id === parent.authorId),
},
};
Ключевой момент: TypeScript автоматически проверяет типы аргументов и возвращаемых значений. Если вы попытаетесь вернуть неверный тип, компилятор выдаст ошибку.
4. Запуск сервера и тестирование
Создадим src/index.ts:
import { ApolloServer } from 'apollo-server';
import { typeDefs } from './schema';
import { resolvers } from './resolvers';
const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
Добавим скрипты в package.json:
"scripts": {
"dev": "nodemon --exec ts-node src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
}
Запустите сервер:
npm run dev
Откройте http://localhost:4000 — вы увидите GraphQL Playground. Попробуйте выполнить запросы:
Пример запроса (Query):
query {
users {
id
name
email
posts {
title
}
}
}
Пример мутации:
m