واجهات GraphQL

Apollo Server مع Express

16 دقيقة الدرس 7 من 35

دمج Apollo Server مع Express

بينما يمكن لـ Apollo Server العمل بشكل مستقل، فإن دمجه مع Express.js يمنحك التحكم الكامل في خادم HTTP، والوسيطة (middleware)، والمسارات. هذا هو الإعداد الإنتاجي الأكثر شيوعًا.

الدمج الأساسي

قم بتثبيت الحزم المطلوبة:

npm install apollo-server-express express graphql npm install --save-dev @types/express

أنشئ إعدادًا أساسيًا لـ Express + Apollo Server:

const express = require('express'); const { ApolloServer } = require('apollo-server-express'); const typeDefs = ` type Query { hello: String users: [User] } type User { id: ID! name: String! email: String! } `; const resolvers = { Query: { hello: () => 'Hello from Apollo Server with Express!', users: () => [ { id: '1', name: 'Alice', email: 'alice@example.com' }, { id: '2', name: 'Bob', email: 'bob@example.com' } ] } }; async function startServer() { const app = express(); const server = new ApolloServer({ typeDefs, resolvers }); await server.start(); server.applyMiddleware({ app }); const PORT = process.env.PORT || 4000; app.listen(PORT, () => { console.log(`Server running at http://localhost:${PORT}${server.graphqlPath}`); }); } startServer();
مهم: مع Apollo Server 3+، يجب عليك استدعاء await server.start() قبل تطبيق الوسيطة باستخدام applyMiddleware().

وسيطة Express

إحدى الفوائد الرئيسية لاستخدام Express هي الوصول إلى نظامه البيئي الغني بالوسيطة:

const express = require('express'); const helmet = require('helmet'); const cors = require('cors'); const morgan = require('morgan'); const { ApolloServer } = require('apollo-server-express'); async function startServer() { const app = express(); // وسيطة الأمان app.use(helmet({ contentSecurityPolicy: process.env.NODE_ENV === 'production' ? undefined : false })); // تكوين CORS app.use(cors({ origin: ['http://localhost:3000', 'https://myapp.com'], credentials: true })); // وسيطة التسجيل app.use(morgan('combined')); // تحليل أجسام JSON app.use(express.json()); app.use(express.urlencoded({ extended: true })); // وسيطة مخصصة app.use((req, res, next) => { console.log(`${req.method} ${req.path}`); next(); }); const server = new ApolloServer({ typeDefs, resolvers }); await server.start(); server.applyMiddleware({ app, path: '/graphql' }); app.listen(4000); } startServer();
استخدام helmet() يضيف رؤوس أمان للحماية من الثغرات الشائعة. في التطوير، قد تحتاج إلى تعطيل CSP لكي يعمل GraphQL Playground.

إعداد السياق (Context)

السياق هو المكان الذي تضيف فيه البيانات المشتركة المتاحة لجميع المحللات. مع Express، يمكنك الوصول إلى كائن الطلب:

const server = new ApolloServer({ typeDefs, resolvers, context: ({ req, res }) => { // الحصول على رمز المصادقة من الرؤوس const token = req.headers.authorization || ''; // التحقق من الرمز والحصول على المستخدم const user = getUserFromToken(token); // إرجاع كائن السياق return { user, req, res, db: database, // اتصال قاعدة البيانات loaders: createLoaders() // DataLoaders }; } });
تعمل دالة السياق مرة واحدة لكل طلب، قبل تنفيذ أي محللات. هذا هو المكان المثالي للتعامل مع المصادقة وإعداد الموارد الخاصة بالطلب.

تكوين CORS

عندما يكون الواجهة الأمامية والخلفية على نطاقات مختلفة، تحتاج إلى CORS:

const cors = require('cors'); // CORS بسيط - السماح لجميع الأصول (التطوير فقط) app.use(cors()); // CORS الإنتاج - أصول محددة app.use(cors({ origin: function(origin, callback) { const allowedOrigins = [ 'http://localhost:3000', 'https://myapp.com', 'https://www.myapp.com' ]; if (!origin || allowedOrigins.includes(origin)) { callback(null, true); } else { callback(new Error('Not allowed by CORS')); } }, credentials: true, // السماح بملفات تعريف الارتباط methods: ['GET', 'POST', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'] })); // تطبيق وسيطة Apollo Server مع CORS server.applyMiddleware({ app, cors: false, // تعطيل CORS في Apollo (استخدم Express CORS بدلاً من ذلك) path: '/graphql' });
تحذير أمني: لا تستخدم أبدًا cors() بدون خيارات في الإنتاج. حدد دائمًا الأصول المسموح بها لمنع الوصول غير المصرح به.

تقديم الملفات الثابتة جنبًا إلى جنب مع GraphQL

يمكنك تقديم تطبيق الواجهة الأمامية وواجهة برمجة تطبيقات GraphQL من نفس خادم Express:

const express = require('express'); const path = require('path'); const { ApolloServer } = require('apollo-server-express'); async function startServer() { const app = express(); // تقديم ملفات ثابتة من بناء React app.use(express.static(path.join(__dirname, 'client/build'))); // مسارات API app.get('/api/health', (req, res) => { res.json({ status: 'ok', timestamp: Date.now() }); }); // نقطة نهاية GraphQL const server = new ApolloServer({ typeDefs, resolvers }); await server.start(); server.applyMiddleware({ app, path: '/graphql' }); // مسار شامل - تقديم تطبيق React app.get('*', (req, res) => { res.sendFile(path.join(__dirname, 'client/build/index.html')); }); const PORT = process.env.PORT || 4000; app.listen(PORT, () => { console.log(`Server: http://localhost:${PORT}`); console.log(`GraphQL: http://localhost:${PORT}/graphql`); }); } startServer();

معالجات المسار المخصصة

يمكنك مزج نقاط نهاية REST مع GraphQL على نفس الخادم:

// نقاط نهاية REST app.get('/api/users', async (req, res) => { const users = await db.user.findMany(); res.json(users); }); app.post('/api/upload', upload.single('file'), async (req, res) => { // معالجة تحميل الملف res.json({ url: req.file.path }); }); // Webhooks app.post('/webhook/stripe', async (req, res) => { // معالجة webhook من Stripe res.json({ received: true }); }); // نقطة نهاية GraphQL server.applyMiddleware({ app, path: '/graphql' });

تكوين خاص بالبيئة

const isDevelopment = process.env.NODE_ENV === 'development'; const server = new ApolloServer({ typeDefs, resolvers, context: ({ req }) => ({ req, db }), // ميزات التطوير playground: isDevelopment, introspection: isDevelopment, debug: isDevelopment, // ميزات الإنتاج formatError: (error) => { if (isDevelopment) { return error; } // إخفاء رسائل الخطأ الداخلية في الإنتاج return new Error('Internal server error'); } });
عطّل الاستبطان (introspection) وplayground في الإنتاج للأمان. استخدم متغيرات البيئة لتبديل الميزات بين التطوير والإنتاج.

إعداد إنتاجي كامل

require('dotenv').config(); const express = require('express'); const helmet = require('helmet'); const cors = require('cors'); const { ApolloServer } = require('apollo-server-express'); const { typeDefs, resolvers } = require('./schema'); const { createContext } = require('./context'); async function startServer() { const app = express(); app.use(helmet()); app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(','), credentials: true })); app.use(express.json({ limit: '10mb' })); const server = new ApolloServer({ typeDefs, resolvers, context: createContext, introspection: process.env.NODE_ENV !== 'production', playground: process.env.NODE_ENV !== 'production' }); await server.start(); server.applyMiddleware({ app, path: '/graphql', cors: false }); app.get('/health', (req, res) => res.json({ status: 'ok' })); const PORT = process.env.PORT || 4000; app.listen(PORT, () => { console.log(`🚀 Server ready at http://localhost:${PORT}${server.graphqlPath}`); }); } startServer().catch(console.error);
تمرين تطبيقي:
  1. أنشئ إعداد Express + Apollo Server مع تمكين CORS
  2. أضف وسيطة مخصصة تسجل وقت الطلب لجميع الطلبات
  3. قم بتكوين السياق لاستخراج مستخدم من رأس Authorization
  4. أضف نقطة نهاية REST في /api/status تُرجع صحة الخادم
  5. قدم ملف HTML ثابت في المسار الجذري جنبًا إلى جنب مع نقطة نهاية GraphQL