I am trying to get a subscription up and running with ApolloServer (v 2.2.2). I had a setup that all-of-a-sudden just stopped working. When I try to connect to the subscription in graphiql/PlaygroundI get the error:
{
"error": "Could not connect to websocket endpoint ws://localhost:4000/graphql. Please check if the endpoint url is correct."
}
As I have rest-endpoints in my app I need to have express but I can't get the minimal example from below running:
import http from 'http';
import { ApolloServer, PubSub } from 'apollo-server-express';
import express from 'express';
const pubsub = new PubSub();
// The DB
const messages = [];
const typeDefs = `
type Query {
messages: [String!]!
}
type Mutation {
addMessage(message: String!): [String!]!
}
type Subscription {
newMessage: String!
}
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
`;
const resolvers = {
Query: {
messages() {
return messages;
}
},
Mutation: {
addMessage(root, { message }) {
let entry = JSON.stringify({ id: messages.length, message: message });
messages.push(entry);
pubsub.publish('newMessage', { entry: entry });
return messages;
},
},
Subscription: {
newMessage: {
resolve: (message) => {
return message.entry;
},
subscribe: () => pubsub.asyncIterator('newMessage'),
},
},
};
const app = express();
const PORT = 4000;
const server = new ApolloServer({
typeDefs,
resolvers,
subscriptions: {
onConnect: () => console.log('Connected to websocket'),
}
});
server.applyMiddleware({ app })
const httpServer = http.createServer(app);
server.installSubscriptionHandlers(httpServer);
httpServer.listen(PORT, () => {
console.log(`🚀 Server ready at http://localhost:${PORT}${server.graphqlPath}`)
console.log(`🚀 Subscriptions ready at ws://localhost:${PORT}${server.subscriptionsPath}`)
})
The other endpoints work fine but it is unable to create the WebSocket. As far as I understand it I shouldn't have to use a different server or port (see https://www.ably.io/concepts/websockets). I've tinkered with SubsciptionServer but this should be handled by installSubscriptionHandlers (here's the code).
The it turns out that Firefox has issues with websockets (see this bug report that has been re-appeared even after the supposed fix).
In Firefox it works directly after starting a novel browser but after some hot reloading it stops working. The following helps out with starting out fresh but not with the reloading issue:
const wsLink = new WebSocketLink({
uri: SUBSCRIPTION_URI,
options: {
reconnect: true,
timeout: 20000,
lazy: true,
},
});
window.addEventListener('beforeunload', () => {
// #ts-ignore - the function is private in typescript
wsLink.subscriptionClient.close();
});
I think the bug is related to this SO-question: "websocket was interrupted while page is loading" on Firefox for Socket.io
If you want to test different solutions I've created an example repo: https://github.com/gforge/subscription_example that works both by itself and with a Docker container.
A lot of time has passed and now I faced with the same problem and I found a solution.
import { createServer } from 'http';
const app = express();
const server = new ApolloServer({});
server.applyMiddleware({ app });
const httpServer = createServer(app);
server.installSubscriptionHandlers(httpServer);
server.listen()
Works for me
Related
I am testing a nodejs express API with a PG db wrapped with Prisma ORM
I configured a testing singleton instance as described by Prisma docs
Since the API is implemented in CommonJS and not TS, I had to make some changes as described in this beautiful page.
Here is a synthesis of what I did, will try to make it short, so it's easier to read
orgs.js (A GET route served by the mock server later on ...)
const Router = require('express-promise-router')
const router = new Router()
const PrismaPool = require('../db/PrismaPool');
module.exports = router
router.get('/assessments', async (req, res) => {
try{
const prisma = PrismaPool.getInstance();
const data = await prisma.org.findUnique({
select:{
assessments:true,
},
where: {
id: res.locals.orgId,
},
})
res.send(data)
}
catch(err){
handleError(err, "[GET]/orgs/assessments", 400, req, res)
}
})
PrismaPool.js (A wrapper to access the unique prisma client instance)
const prisma = require('./PrismaClientInstance').default
class PrismaPool {
constructor() {
throw new Error('Use PrismaPool.getInstance()');
}
static getInstance() {
return prisma
}
}
module.exports = PrismaPool;
PrismaClientInstance.js The unique instance of PrismaClient class. This is the tricky part stitching between the CommonJS world and the TS world.
'use strict';
exports.__esModule = true;
const { PrismaClient } = require('#prisma/client')
const prisma = new PrismaClient()
exports['default'] = prisma;
All this configuration works GREAT at runtime, now, when wrapping it with JEST in unit tests, things go south quickly ...
mock_server.js (a simplified server to expose the orgs API above)
const http = require('http');
const express = require('express');
var orgsRouter = require('../orgs');
const app = express();
app.use('/orgs', orgsRouter);
const port = 3011
app.set('port', port);
const server = http.createServer(app);
function onError(error) {
// herror handling
}
function onListening() {
// some debug messages
}
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
module.exports = server
PrismaSingletonForTesting.ts (A jest deep mock of the PrismaClient instance)
import { PrismaClient } from '#prisma/client'
import { mockDeep, mockReset, DeepMockProxy } from 'jest-mock-extended'
import prisma from './PrismaClientInstance'
jest.mock('./PrismaClientInstance', () => ({
__esModule: true,
default: mockDeep<PrismaClient>()
}))
beforeEach(() => {
mockReset(prismaMock)
})
export const prismaMock = prisma as unknown as DeepMockProxy<PrismaClient>
orgs.test.js (The tests of the orgs API)
const Request = require("request")
const { prismaMock } = require('../../db/PrismaSingletonForTesting')
const TEST_ORGID = 1
describe('egrest server', () => {
let server
beforeAll(() => {
server = require('./test_server')
})
afterAll(() => {
server.close()
})
describe('assessments', () => {
let data = {}
beforeAll(() => {
const testorg = {
id: TEST_ORGID,
name: 'jestest',
admin:33,
avail_tests: 1234
}
prismaMock.org.findUnique.mockResolvedValue(testorg)
})
it(`read remaining assessments for org ${TEST_ORGID}`, (done) => {
Request.get("http://localhost:3011/orgs/assessments", (error, response, body) => {
data.status = response.statusCode
data.body = body
data.error = error
console.dir(body)
done()
})
})
})
})
I also configured .jest.config with the required line setupFilesAfterEnv: ['./db/PrismaSingletonForTesting.ts']
When I run this test, I get data=undefined in orgs.js, even-though I mocked prisma.org.findUnique by doing prismaMock.org.findUnique.mockResolvedValue(testorg) as described by prisma docs.
Any help would be appreciated.
I'm trying to set up an apollo server and an error shows up.
import { createConnection } from "typeorm";
import { Post } from "./entity/Post";
const express = require("express");
import { buildSchema } from "type-graphql";
import { ApolloServer } from "apollo-server-express";
import { PostResolver } from "./Resolvers/post";
createConnection()
.then(async (connection) => {
console.log("Inserting a new user into the database...");
const post = new Post();
post.firstName = "Timber";
post.lastName = "Saw";
post.age = 25;
await connection.manager.save(post);
console.log("Saved a new user with id: " + post.id);
console.log("Loading users from the database...");
const posts = await connection.manager.find(Post);
console.log("Loaded users: ", posts);
const app = express();
const PORT = process.env.PORT || 4000;
app.listen(PORT, () => console.log(`Listening on PORT: ${PORT}`));
const apolloServer = new ApolloServer({
schema: await buildSchema({
resolvers: [PostResolver],
}),
});
await apolloServer.start();
apolloServer.applyMiddleware({ app });
})
.catch((error) => console.log(error));
[ERROR] 16:49:21 ⨯ Unable to compile TypeScript:
src/index.ts:27:43 - error TS2345: Argument of type '{ schema: GraphQLSchema; }' is not assignable to parameter of type 'Config'.
Type '{ schema: GraphQLSchema; }' is missing the following properties from type 'Config': formatError, debug, rootValue, validationRules, and 6 more.
I've tried installing dependencies again, changed TypeScript config, restarting the whole project but nothing seems to fix the problem. Any clue?
Whenever I try to run the function refreshStock() in an endpoint in one of the API endpoints /api/seller/deactivate it gives me this error:
Error: listen EADDRINUSE: address already in use :::3000
at Server.setupListenHandle [as _listen2] (net.js:1318:16)
at listenInCluster (net.js:1366:12)
at Server.listen (net.js:1452:7)
at C:\Users\***\Documents\GitHub\***\***\.next\server\pages\api\seller\deactivate.js:191:10
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command
It looks like it's trying to restart the server, but it happens after it compiles, is there something I'm doing wrong, I've followed a couple of tutorials on medium, and they give this same type of code, just not ES Modules. I want to use ES Modules because it is what my database functions are written in.
Server.js:
import express from 'express';
import { createServer } from 'http';
import next from 'next';
import models from './server/models';
import { genStock } from './server/lib/functions';
import { Server } from 'socket.io';
const port = parseInt(process.env.PORT || '3000', 10);
const dev = process.env.NODE_ENV !== 'production';
const nextApp = next({ dev });
const nextHandler = nextApp.getRequestHandler();
const app = express();
const server = createServer(app);
const io = new Server(server);
const Users = models.users;
io.use(async (socket, next) => {
const err = new Error('Unauthorized');
err.data = { message: 'Unauthorized, please try again later.' };
try {
if (!socket.handshake.auth.token) return next(err);
let user = await Users.findOne({
where: {
socket_token: socket.handshake.auth.token,
},
});
if (!user) {
console.log('unauthenticated socket');
socket.disconnect();
next(err);
}
await Users.update(
{ socket_id: socket.id },
{
where: {
socket_token: socket.handshake.auth.token,
},
},
);
next();
} catch (e) {
console.log(e);
next(e);
}
});
io.on('connection', async (socket) => {
// Works fine
const stock = await genStock();
socket.emit('updateStock', stock);
});
// Fails with address already in use :::3000
export async function refreshStock() {
const stock = await genStock();
io.emit('updateStock', stock);
}
nextApp.prepare().then(async () => {
app.all('*', (req, res) => nextHandler(req, res));
server.listen(port, () => {
console.log(`> Ready on http://localhost:${port}`);
});
});
This is meant to refresh the stock after a seller deactivates their account and sends all users the new stock.
/api/seller/deactivate
....
await refreshStock();
....
I figured it out, I just split up the WebSocket server and the next.js one. I have whitelisted local IPs that may appear to only allow server-to-server communication. Although I don't think this is full-proof as there is most likely a better way to have this type of communication but for now it works.
/**
* This server cannot be imported in /api folders, it won't work.
* Although it can import other functions
* */
import express from 'express';
import { createServer } from 'http';
import session from 'express-session';
import { Server } from 'socket.io';
import { genStock } from './server/lib/stockFunctions';
import { sessionStore } from './server/lib/session';
import passport from './server/lib/passport';
import models from './server/models';
const authorizedIPs = ['::1', '127.0.0.1', '::ffff:127.0.0.1'];
const Users = models.users;
const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, {
cors: {
origin: `http://localhost:3000`,
methods: ['GET', 'POST'],
credentials: true,
},
});
const wrap = (middleware) => (socket, next) => middleware(socket.request, {}, next);
io.use(
wrap(
session({
secret: "---",
resave: false,
saveUninitialized: true,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
path: '/',
sameSite: 'lax',
},
store: sessionStore,
}),
),
);
io.use(wrap(passport.initialize()));
io.use(wrap(passport.session()));
io.use(async (socket, next) => {
const err = new Error('Unauthorized');
err.data = { message: 'Unauthorized, please try again later.' };
try {
const user = socket.request.user;
if (!user) return next(err);
await Users.update(
{ socket_id: socket.id },
{
where: {
id: user.id,
},
},
);
next();
} catch (e) {
console.log(e);
next(e);
}
});
io.on('connection', async (socket) => {
const stock = await genStock();
socket.emit('updateStock', stock);
});
app.post('/refresh-stock', async function (req, res) {
const ip = req.ip;
if (!authorizedIPs.includes(ip)) {
console.log(ip);
return res.status(401).json({ success: false });
}
const newStock = await genStock();
io.emit('updateStock', newStock);
return res.status(200).json({ success: true });
});
httpServer.listen(3001);
console.log(`> Websockets ready on http://localhost:3001`);
I just want to get live data from Mysql DB on the UI Reactjs. So that the user need not to refresh it always. After looking over some posts end up creating a socket.io connection so that the client can speak to the server. This is what I tried to get into:
server.js
const express = require("express");
const http = require("http");
const socketIo = require("socket.io");
var assert = require('assert');
const port = process.env.PORT || 4001;
const index = require("./routes/index");
const app = express();
app.use(index);
const server = http.createServer(app);
const io = socketIo(server);
const mysql = require('mysql');
var startDate ;
var endDate ;
var loopVariable = 1;
io.on("connection", (socket) => {
console.log("New client connected");
const con = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'root',
database: 'localstatus',
debug: false,
});
console.log('Connection established ',(loopVariable++));
socket.on("FromUI", (data) => {
startDate = data.startDate;
endDate = data.endDate;
});
var initial_result;
setInterval(() => {
con.query('SELECT * FROM table where start_time BETWEEN ? and ?', [ startDate, endDate ],(err,rows) =>
{
if(err) {
console.log ('error', err.message, err.stack)
}else {
}
if(JSON.stringify(rows) === JSON.stringify(initial_result)){
}else{
if(Changed(initial_result, rows)) {
var result = [];
for (var row in rows) {
var results = [];
results.push({
Id: rows[row].id,
status: rows[row].t_status,
});
result.push({ returnValue:"true",
object: {Id: rows[row].id,
status: rows[row].t_status,
}});
}
socket.emit('FromAPI', result);
}
initial_result = rows;
}
})
function Changed(pre, now) {
if (pre != now)
{
return true
}else{
return false
}}
}, 1000);
socket.on('disconnect', function() {
socket.disconnect();
loopVariable--;
});
});
server.listen(port, () => console.log(`Listening on port ${port}`));
client.js
import React, { useState, useEffect, Component } from "react";
import socketIOClient from "socket.io-client";
import TableUsingReactTable from "./TableUsingReactTable.js";
const ENDPOINT = "http://localhost:4001";
export default function App(){
const [response, setResponse] = useState([]);
useEffect(() => {
const socket = socketIOClient(ENDPOINT);
try{
socket.on("FromAPI", data => {
setResponse(data);
});
}catch (error) {
console.log(error);
}
return () => {
socket.on("disconnect")
socket.disconnect();
};
}, []);
console.log(response)
return (<TableUsingReactTable response={response}></TableUsingReactTable>)
}
I think the socket gets disconnected when the tab gets closed, but what happens when is tab is not in use? And how to disable it when not in use? Even when all the tabs are closed then also the RAM increases. How to reduce the RAM when sockets get closed? And how does socket.io behave when at the same time many hit the URL? Moreover, sometimes I did face the issue as:
code: 'ER_CON_COUNT_ERROR',
errno: 1040,
sqlMessage: 'Too many connections',
sqlState: undefined,
fatal: true
How to handle this case too? I m new to this and not understanding how to proceed further. Can someone help me with this? Thanks a lot.
Firstly, you don't actually need Socket.IO for this use case. Server-Sent Events/EventSource API are fine for this, as you're only sending data in one direction. This gives you the benefit of not needing to load Socket.IO libraries.
Now, the real problem is that you're creating a separate MySQL connection for each individual client. Rather than calling mysql.createConnection() every time a new client connects, you can connect to your database once. (There are situations where this isn't appropriate, but since you're just doing some basic SELECT queries, this is fine.)
I have built my portfolio webpage with next.js now I need to test it. to test the express server I use supertest. But the problem is I need to refactor express to use it. Because supertest need to access to app() before listening.
I started the way how I used to implement in node.js app. Put the express code in app.js and call it in index.js.
const express = require("express");
const server = express();
const authService = require("./services/auth");
const bodyParser = require("body-parser");
//put all the middlewares here
module.exports = server;
and then in index.js
const server = require("express")();
// const { parse } = require("url");
const next = require("next");
const routes = require("../routes");
const path = require("path");
require("./mongodb");
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
// const handle = app.getRequestHandler(); //this is built in next route handler
const handle = routes.getRequestHandler(app);
app
.prepare()
.then(() => {
const server = require("./app");
//I required this outside too but it did not solve the issue
server.listen(3000, (err) => {
if (err) throw err;
console.log("> Ready on http://localhost:3000");
});
})
.catch((ex) => {
console.error(ex.stack);
process.exit(1);
});
with this set up, express is listening, I am able connect to mongodb, during the start up there is no issue.
When i request to localhost:3000, there is no response from localhost, it is spinning till timeout
Create a test client:
// test-client.ts
import { createServer, RequestListener } from "http";
import { NextApiHandler } from "next";
import { apiResolver } from "next/dist/next-server/server/api-utils";
import request from "supertest";
export const testClient = (handler: NextApiHandler) => {
const listener: RequestListener = (req, res) => {
return apiResolver(
req,
res,
undefined,
handler,
{
previewModeEncryptionKey: "",
previewModeId: "",
previewModeSigningKey: "",
},
false
);
};
return request(createServer(listener));
};
Test your APIs with:
// user.test.ts
import viewerApiHandler from "../api/user";
import { testClient } from "../utils/test-client";
const request = testClient(viewerApiHandler);
describe("/user", () => {
it("should return current user", async () => {
const res = await request.get("/user");
expect(res.status).toBe(200);
expect(res.body).toStrictEqual({ name: "Jane Doe" });
});
});
For those who want to add query parameters, here's the answer:
import { createServer, RequestListener } from 'http'
import { NextApiHandler } from 'next'
import { apiResolver } from 'next/dist/server/api-utils/node'
import request from 'supertest'
export const handlerRequester = (handler: NextApiHandler) => {
const listener: RequestListener = (req, res) => {
let query = {}
let queryUrl = req.url.split('?')[1]
if (queryUrl) {
queryUrl
.split('&')
.map((p) => [p.split('=')[0], p.split('=')[1]])
.forEach((k) => {
query[k[0]] = k[1]
})
}
return apiResolver(
req,
res,
query,
handler,
{
previewModeEncryptionKey: '',
previewModeId: '',
previewModeSigningKey: '',
},
false
)
}
const server = createServer(listener)
return [request(server), server]
}
I've just released a new npm package which handle this case here:
https://www.npmjs.com/package/nextjs-http-supertest
Feel free to test it and give me feedback !