واجهات GraphQL
Apollo Server مع Express
دمج 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);
تمرين تطبيقي:
- أنشئ إعداد Express + Apollo Server مع تمكين CORS
- أضف وسيطة مخصصة تسجل وقت الطلب لجميع الطلبات
- قم بتكوين السياق لاستخراج مستخدم من رأس Authorization
- أضف نقطة نهاية REST في
/api/statusتُرجع صحة الخادم - قدم ملف HTML ثابت في المسار الجذري جنبًا إلى جنب مع نقطة نهاية GraphQL