واجهات GraphQL
GraphQL مع TypeScript
GraphQL مع TypeScript
أنشئ خوادم وعملاء GraphQL آمنة من حيث النوع باستخدام TypeScript للحصول على أمان النوع من النهاية إلى النهاية.
محللات آمنة من حيث النوع
حدد أنواع TypeScript للمحللات الخاصة بك:
// types/graphql.ts
export interface User {
id: string;
name: string;
email: string;
posts?: Post[];
}
export interface Post {
id: string;
title: string;
content: string;
authorId: string;
}
export interface Context {
db: Database;
user?: User;
}
export interface CreateUserInput {
name: string;
email: string;
}
// resolvers.ts
import { Resolvers } from './generated/graphql';
import { Context } from './types/graphql';
export const resolvers: Resolvers<Context> = {
Query: {
user: async (_, { id }, { db }) => {
const user = await db.user.findUnique({ where: { id } });
if (!user) throw new Error('لم يتم العثور على المستخدم');
return user;
},
users: async (_, __, { db }) => {
return db.user.findMany();
},
},
Mutation: {
createUser: async (_, { input }, { db }) => {
return db.user.create({
data: {
name: input.name,
email: input.email,
},
});
},
},
User: {
posts: async (parent, _, { db }) => {
return db.post.findMany({
where: { authorId: parent.id },
});
},
},
Post: {
author: async (parent, _, { db }) => {
const author = await db.user.findUnique({
where: { id: parent.authorId },
});
if (!author) throw new Error('لم يتم العثور على الكاتب');
return author;
},
},
};
الأنواع المُولَّدة
استخدم GraphQL Code Generator لتوليد الأنواع التلقائي:
# codegen.yml للخادم
schema: ./schema.graphql
generates:
src/generated/graphql.ts:
plugins:
- typescript
- typescript-resolvers
config:
contextType: ../types/graphql#Context
mappers:
User: ../types/graphql#User
Post: ../types/graphql#Post
// أنواع المحللات المُولَّدة
export type ResolversTypes = {
User: ResolverTypeWrapper<User>;
String: ResolverTypeWrapper<Scalars['String']>;
Post: ResolverTypeWrapper<Post>;
Query: ResolverTypeWrapper<{}>;
Mutation: ResolverTypeWrapper<{}>;
CreateUserInput: CreateUserInput;
};
export type UserResolvers<
ContextType = Context,
ParentType extends ResolversParentTypes['User'] = ResolversParentTypes['User']
> = {
id?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
email?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
posts?: Resolver<Array<ResolversTypes['Post']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
نصيحة: ربط أنواع GraphQL بنماذج قاعدة البيانات باستخدام تكوين
mappers لضمان اتساق النوع.
Nexus - نهج الكود أولاً
أنشئ مخطط GraphQL الخاص بك بالكود باستخدام Nexus:
import { objectType, extendType, stringArg, nonNull, inputObjectType } from 'nexus';
// تعريف نوع User
export const User = objectType({
name: 'User',
definition(t) {
t.nonNull.id('id');
t.nonNull.string('name');
t.nonNull.string('email');
t.nonNull.list.nonNull.field('posts', {
type: 'Post',
resolve: (parent, _, ctx) => {
return ctx.db.post.findMany({
where: { authorId: parent.id },
});
},
});
},
});
// تعريف نوع Post
export const Post = objectType({
name: 'Post',
definition(t) {
t.nonNull.id('id');
t.nonNull.string('title');
t.nonNull.string('content');
t.nonNull.field('author', {
type: 'User',
resolve: (parent, _, ctx) => {
return ctx.db.user.findUniqueOrThrow({
where: { id: parent.authorId },
});
},
});
},
});
// تعريف أنواع الإدخال
export const CreateUserInput = inputObjectType({
name: 'CreateUserInput',
definition(t) {
t.nonNull.string('name');
t.nonNull.string('email');
},
});
// تعريف الاستعلامات
export const UserQuery = extendType({
type: 'Query',
definition(t) {
t.field('user', {
type: 'User',
args: {
id: nonNull(stringArg()),
},
resolve: (_, { id }, ctx) => {
return ctx.db.user.findUnique({ where: { id } });
},
});
t.nonNull.list.nonNull.field('users', {
type: 'User',
resolve: (_, __, ctx) => {
return ctx.db.user.findMany();
},
});
},
});
// تعريف الطفرات
export const UserMutation = extendType({
type: 'Mutation',
definition(t) {
t.nonNull.field('createUser', {
type: 'User',
args: {
input: nonNull('CreateUserInput'),
},
resolve: (_, { input }, ctx) => {
return ctx.db.user.create({
data: {
name: input.name,
email: input.email,
},
});
},
});
},
});
// schema.ts
import { makeSchema } from 'nexus';
import * as types from './graphql';
import path from 'path';
export const schema = makeSchema({
types,
outputs: {
schema: path.join(__dirname, './generated/schema.graphql'),
typegen: path.join(__dirname, './generated/nexus-typegen.ts'),
},
contextType: {
module: path.join(__dirname, './context.ts'),
export: 'Context',
},
});
TypeGraphQL
استخدم الديكورات لتعريف المخطط مع TypeGraphQL:
import { ObjectType, Field, ID, Resolver, Query, Mutation, Arg, Ctx } from 'type-graphql';
@ObjectType()
class User {
@Field(() => ID)
id: string;
@Field()
name: string;
@Field()
email: string;
@Field(() => [Post])
posts: Post[];
}
@ObjectType()
class Post {
@Field(() => ID)
id: string;
@Field()
title: string;
@Field()
content: string;
@Field(() => User)
author: User;
}
@InputType()
class CreateUserInput {
@Field()
name: string;
@Field()
email: string;
}
@Resolver(User)
class UserResolver {
@Query(() => User, { nullable: true })
async user(@Arg('id') id: string, @Ctx() ctx: Context): Promise<User | null> {
return ctx.db.user.findUnique({ where: { id } });
}
@Query(() => [User])
async users(@Ctx() ctx: Context): Promise<User[]> {
return ctx.db.user.findMany();
}
@Mutation(() => User)
async createUser(
@Arg('input') input: CreateUserInput,
@Ctx() ctx: Context
): Promise<User> {
return ctx.db.user.create({
data: {
name: input.name,
email: input.email,
},
});
}
@FieldResolver(() => [Post])
async posts(@Root() user: User, @Ctx() ctx: Context): Promise<Post[]> {
return ctx.db.post.findMany({
where: { authorId: user.id },
});
}
}
// server.ts
import 'reflect-metadata';
import { ApolloServer } from 'apollo-server';
import { buildSchema } from 'type-graphql';
async function bootstrap() {
const schema = await buildSchema({
resolvers: [UserResolver, PostResolver],
});
const server = new ApolloServer({
schema,
context: ({ req }) => ({
db: prisma,
user: getUserFromToken(req.headers.authorization),
}),
});
const { url } = await server.listen(4000);
console.log(`الخادم جاهز على ${url}`);
}
bootstrap();
استعلامات العميل الآمنة من حيث النوع
استخدم الأنواع المُولَّدة في كود العميل الخاص بك:
import { useGetUserQuery, GetUserQuery, GetUserQueryVariables } from './generated/graphql';
function UserProfile({ userId }: { userId: string }) {
// خطاف استعلام مُكتَّب بالكامل
const { data, loading, error } = useGetUserQuery({
variables: { id: userId },
});
// TypeScript يعرف الشكل الدقيق للبيانات
const userName: string | undefined = data?.user?.name;
const userPosts: Array<{ id: string; title: string }> | undefined = data?.user?.posts;
return (
<div>
{userName && <h1>{userName}</h1>}
{userPosts?.map((post) => (
<div key={post.id}>{post.title}</div>
))}
</div>
);
}
أمان النوع من النهاية إلى النهاية
حقق أماناً كاملاً للنوع من قاعدة البيانات إلى واجهة المستخدم:
// 1. مخطط قاعدة البيانات (Prisma)
model User {
id String @id @default(uuid())
name String
email String @unique
posts Post[]
}
// 2. مخطط GraphQL (مُولَّد أو الكود أولاً)
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
// 3. محللات TypeScript (محققة من النوع)
const resolvers: Resolvers<Context> = {
Query: {
user: (_, { id }, { db }) => db.user.findUnique({ where: { id } }),
},
};
// 4. أنواع العميل المُولَّدة
const { data } = useGetUserQuery({ variables: { id: '123' } });
// 5. عرض واجهة مستخدم آمن من حيث النوع
const name: string | undefined = data?.user?.name;
تحذير: حافظ على تكوين codegen متزامناً مع تغييرات المخطط. قم دائماً بتشغيل codegen بعد تعديل ملفات GraphQL.
تمرين:
- قم بإعداد GraphQL Code Generator مع إضافات TypeScript و React Apollo
- أنشئ عمليات GraphQL (استعلامات وطفرات) في ملفات
.graphqlمنفصلة - ولّد أنواع TypeScript وخطافات React
- أنشئ مكون React باستخدام الخطافات المُولَّدة مع أمان النوع الكامل
- نفذ محللات من جانب الخادم مع أنواع المحللات المُولَّدة