Repository navigation
Expand file tree
/
Copy pathserver.js
More file actions
160 lines (142 loc) · 4.55 KB
/
Copy pathserver.js
File metadata and controls
160 lines (142 loc) · 4.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
const express = require('express');
const { ApolloServer } = require('@apollo/server');
const { expressMiddleware } = require('@apollo/server/express4');
const { createServer } = require('http');
const { ApolloServerPluginDrainHttpServer } = require('@apollo/server/plugin/drainHttpServer');
const { makeExecutableSchema } = require('@graphql-tools/schema');
const { WebSocketServer } = require('ws');
const { useServer } = require('graphql-ws/lib/use/ws');
const { PubSub } = require('graphql-subscriptions');
const mongoose = require('mongoose');
const Redis = require('ioredis');
const winston = require('winston');
const rateLimit = require('express-rate-limit');
const { applyMiddleware } = require('graphql-middleware');
const typeDefs = require('./src/graphql/schema/typeDefs');
const resolvers = require('./src/graphql/schema/resolvers');
const { logger } = require('./src/config/logger');
const { connectDB } = require('./src/config/database');
const { authMiddleware } = require('./src/middleware/auth');
const { complexityLimit } = require('./src/middleware/complexityLimit');
const { createRateLimitRule } = require('./src/middleware/rateLimit');
const { formatError } = require('./src/utils/errorHandler');
require('dotenv').config();
const pubsub = new PubSub();
async function startServer() {
const app = express();
const httpServer = createServer(app);
// Connect to MongoDB
await connectDB();
// Create Schema
const schema = makeExecutableSchema({ typeDefs, resolvers });
// Updated WebSocket server configuration
const wsServer = new WebSocketServer({
server: httpServer,
path: '/graphql',
handleProtocols: (protocols, req) => {
// Accept graphql-transport-ws protocol
return protocols.includes('graphql-transport-ws')
? 'graphql-transport-ws'
: false;
},
});
const serverCleanup = useServer({
schema,
context: async (ctx, msg, args) => {
if (msg.type === 'connection_init') {
const token = msg.payload?.Authorization || ctx.connectionParams?.Authorization;
logger.info('WebSocket connection initialized');
return {
pubsub,
};
}
return {
pubsub,
};
},
onConnect: async (ctx) => {
logger.info('Client connected to WebSocket');
return true;
},
onDisconnect: async (ctx) => {
logger.info('Client disconnected from WebSocket');
},
onSubscribe: async (ctx, msg) => {
logger.info('Client subscribed to:', msg.payload.query);
},
onNext: async (ctx, msg, args, result) => {
logger.debug('Subscription event:', { msg, result });
},
onError: async (ctx, msg, errors) => {
logger.error('Subscription error:', errors);
},
// Add keepAlive configuration
keepAlive: 12000, // Send keep-alive every 12 seconds
}, wsServer);
// Create Apollo Server
const server = new ApolloServer({
schema,
plugins: [
ApolloServerPluginDrainHttpServer({ httpServer }),
{
async serverWillStart() {
return {
async drainServer() {
await serverCleanup.dispose();
},
};
},
},
],
// Enable batching
allowBatchedHttpRequests: true,
context: async ({ req }) => ({
user: req.user,
pubsub,
redis: new Redis(process.env.REDIS_URL),
logger,
}),
});
await server.start();
// Apply middleware
app.use(
'/graphql',
// rateLimit(createRateLimitRule()), // Comment this line
express.json(),
authMiddleware,
expressMiddleware(server, {
context: async ({ req }) => ({
user: req.user,
redis: new Redis(process.env.REDIS_URL),
logger,
}),
})
);
// Add WebSocket heartbeat
wsServer.on('connection', function connection(ws) {
ws.isAlive = true;
ws.on('pong', function heartbeat() {
ws.isAlive = true;
});
// Send ping to keep connection alive
const interval = setInterval(function ping() {
if (ws.isAlive === false) {
logger.info('Terminating inactive connection');
return ws.terminate();
}
ws.isAlive = false;
ws.ping();
}, 30000);
ws.on('close', function close() {
clearInterval(interval);
});
});
const PORT = process.env.PORT || 4000;
httpServer.listen(PORT, () => {
logger.info(`🚀 Server ready at http://localhost:${PORT}/graphql`);
logger.info(`🚀 Subscriptions ready at ws://localhost:${PORT}/graphql`);
});
}
startServer().catch((err) => {
logger.error('Failed to start server:', err);
});