Apollo Uploading Files, createReadStream is not a function - node.js

I tried to get this working for several days and getting now where. I do believe that that the client side is function properly but I'm not hundred percent certain. I know the resolver on the server is getting called. At one point I was able to destructure the file param in the resolver. But now when I destructure, I get undefined. Not sure what is up with that at one point it was destructuring just fine. The client project is setup to connect to two different GraphQL servers, one is for standard database and subscriptions and the other is file uploader. That is why I left the subscription in the dependences of the client.
I wanna say thank you for taking the time to look at this.
My Server Version Info
"#graphql-tools/schema": "^7.1.5",
"apollo-cache-inmemory": "^1.6.6",
"apollo-server-express": "^2.25.0",
"express": "^4.17.1",
"express-jwt": "^6.0.0",
"graphql": "^15.5.0",
"graphql-middleware": "^6.0.10",
"graphql-shield": "^7.5.0",
"graphql-upload": "^12.0.0",
My Main File
import { ApolloError, ApolloServer, makeExecutableSchema } from "apollo-server-express"
import { applyMiddleware } from "graphql-middleware"
import express from "express"
import { graphqlUploadExpress } from 'graphql-upload'
import makeDir from 'make-dir'
const app = express()
const cors = require('cors')
app.use(cors())
const bodyParser = require('body-parser')
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
app.use(graphqlUploadExpress({ maxFileSize: 10000000, maxFiles: 10 }));
const apolloServer = new ApolloServer({
uploads: false,
...
schema: applyMiddleware(
makeExecutableSchema({ typeDefs, resolvers }),
permissions
),
context: ({ req, res }) => {
...
},
});
apolloServer.applyMiddleware({ app });
...
async function StartServer() {
await makeDir(UPLOAD_DIR)
app.listen({ host: HOST, port: PORT }, () => {
console.log(`
πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€πŸš€
Apollo File Server ready at http://${HOST}:${PORT}${apolloServer.graphqlPath}
Started at ${datetime}
isDevelopmentMode ${isDevelopmentMode}
`)
});
}
StartServer()
My Schema
type File {
name: String!
size: Int!
}
extend type Mutation {
singleUpload(file: Upload!): File!,
}
My Resolver
singleUpload: async (parent, { file }) => {
console.log('singleUpload------------------------------------------------------------------------------>>>', file)
console.log('singleUpload------------------------------------------------------------------------------>>> 0')
const { createReadStream, filename, mimetype, encoding } = await file;
console.log('singleUpload------------------------------------------------------------------------------>>> 1', filename)
const stream = createReadStream();
console.log('singleUpload------------------------------------------------------------------------------>>> 2')
const pathName = `${UPLOAD_DIR}${filename}`;
console.log('singleUpload------------------------------------------------------------------------------>>> 3')
await stream.pipe(fs.createWriteStream(pathName));
console.log('singleUpload------------------------------------------------------------------------------>>> 4')
return {
name: `http://localhost:4001/images/${filename}`,
size: 1,
};
},
My Client side
Version Info
"#apollo/client": "^3.2.7",
"#apollo/react-hoc": "^4.0.0",
"#apollo/react-hooks": "^4.0.0",
"#graphql-codegen/introspection": "^1.18.1",
"#graphql-codegen/schema-ast": "^1.18.1",
"#graphql-tools/load-files": "^6.2.5",
"#graphql-tools/merge": "^6.2.6",
"apollo-boost": "^0.4.9",
"apollo-link-ws": "^1.0.20",
"apollo-upload-client": "^16.0.0",
"graphql": "^14.7.0",
"graphql-scalars": "^1.7.0",
"react": "^16.14.0",
"react-apollo": "^3.1.5",
"react-app-rewire-define-plugin": "^1.0.0",
"react-app-rewired": "^2.1.8",
"react-dom": "^16.14.0",
"react-router-dom": "^5.2.0",
"react-scripts": "^3.4.1",
"subscriptions-transport-ws": "^0.9.18",
"typescript": "^3.7.3",
The Apollo Client
const uploadLink = createUploadLink({
uri: 'http://localhost:4001/graphql', // Apollo Server is served from port 4000
headers: {
"keep-alive": "true",
'content-type': 'application/json',
...((accessToken !== undefined && accessToken !== null) ? { Authorization: `Bearer ${accessToken}` } : {}),
...((accessToken !== undefined && accessToken !== null) ? { 'x-token': accessToken } : {}),
...((refreshToken !== undefined && refreshToken !== null) ? { 'x-refresh-token': refreshToken } : {}),
}
})
const clientUpload = new ApolloClient({
cache: cache,
link: uploadLink
})
The Uploader Component
const mutUploadFile = gql`
mutation singleUpload($file: Upload!) {
singleUpload(file: $file) {
name
#filename
#mimetype
#encoding
#url
}
}`
export function FileUploadSimpleA() {
const onChange = ((e: any) => {
console.log( 'onChange-------------------------')
const file = e.target.files[0]
console.log('file:', file)
if (file)
clientUpload.mutate({
mutation: mutUploadFile,
variables: {
file: file
}
})
.then(result => {
console.log( 'then-------------------------')
console.log(result)
clientUpload.resetStore();
})
.catch(error => {
console.log( 'catch-------------------------')
console.log(error)
}
);
});
return <input type="file" required onChange={onChange} />;
}
export default FileUploadSimpleA
Console of the browser
fileUploadSimple.tsx:50 onChange-------------------------
fileUploadSimple.tsx:52 file:: FileΒ {name:
"322abcbafe59a52481e6a0b6b84ffbc4.jpg", lastModified: 1614644398495, lastModifiedDate: Mon Mar 01 2021 18:19:58 GMT-0600 (Central Standard Time), webkitRelativePath: "", size: 68817, …}
fileUploadSimple.tsx:66 catch-------------------------
fileUploadSimple.tsx:67 Error: createReadStream is not a function

Related

Can't stream video from MongoDB using GridFS

I am trying for quite some time to stream a video from MongoDB. Read tons of api DOCs and examples just can't get it to work
This is my front-end video Handler :
import { useState, useEffect } from "react";
import ReactPlayer from "react-player";
const CatVideos = () => {
const [videoList, setvideoList] = useState(null);
useEffect(() => {
const getVideos=async ()=> {
const response = await fetch('http://localhost:8000/cat-videos/videos');
const data = await response.json();
setvideoList(data);
console.log(data);
}
getVideos();
}, [])
return (
<div>
<h1>Cat videos</h1>
{videoList!=null && videoList.map((video)=>{
return <ReactPlayer key={video._id} url={'http://localhost:8000/cat-videos/videos/'+video._id}/>
})}
</div>
);
};
export default CatVideos;
Backend stream function :
exports.getVideoStream = (req, res, next) => {
var id = req.params.id;
let gfs = Grid(conn.db, mongoose.mongo);
gfs.collection("videos");
gfs.files
.findOne({
_id: mongoose.Types.ObjectId(id),
})
.then((result) => {
const file = result;
if (!file) {
return res.status(404).send({
err: "Unavailable.",
});
}
if (req.headers["range"]) {
var parts = req.headers["range"].replace(/bytes=/, "").split("-");
var partialstart = parts[0];
var partialend = parts[1];
var start = parseInt(partialstart, 10);
var end = partialend ? parseInt(partialend, 10) : file.length - 1;
var chunksize = end - start + 1;
res.header("Accept-Ranges", "bytes");
res.header("Content-Length", chunksize);
res.header(
"Content-Range",
"bytes " + start + "-" + end + "/" + result.length
);
console.log(result.contentType)
res.header("Content-Type", result.contentType);
gfs.createReadStream({
_id: result._id,
range: {
startPos: start,
endPos: end,
},
}).readStream.pipe(res);
} else {
console.log("#################before####################");
res.header("Content-Length", result.length);
res.header("Content-Type", result.contentType);
console.log(result._id);
gfs
.createReadStream({
_id: result._id,
})
.pipe(res);
}
})
.catch((err) => {
res.json(err);
});
};
I do get a response from this function , and it appears that the "Content-Type" remains unchanged.
HTTP/1.1 206 Partial Content
X-Powered-By: Express
Access-Control-Allow-Origin: *
Accept-Ranges: bytes
Date: Thu, 23 Dec 2021 19:38:25 GMT
Content-Type: application/json; charset=utf-8
ETag: W/"2-vyGp6PvFo4RvsFtPoIWeCReyIC8"
Content-Range: bytes 0-1/2
Content-Length: 2
Backend dependencies:
"dependencies": {
"bcryptjs": "^2.4.3",
"body-parser": "^1.19.1",
"cors": "^2.8.5",
"ejs": "^3.1.6",
"express": "^4.17.2",
"gridfs-stream": "^1.1.1",
"method-override": "^3.0.0",
"mongodb": "^4.2.2",
"mongoose": "^6.1.2",
"multer": "^1.4.4",
"multer-gridfs-storage": "^5.0.2"
}
Frontend dependencies:
"axios": "^0.24.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-player": "^2.9.0",
"react-router-dom": "^6.2.1",
"react-scripts": "4.0.3",
"web-vitals": "^1.1.2"
I managed to fix the issue. Note that GridFSBucket has a default bucket name.
Going over the API docs it says it appends the bucket name ".files".
My issues were that I did not define it and start end inside download stream were not defined correctly causing an error.
You may use it as well to stream Images,videos just change the content type on the frontend. Pretty generic stream.
exports.getVideoStream = (req, res, next) => {
mongodb.MongoClient.connect(url, function (error, client) {
if (error) {
res.status(500).json(error);
return;
}
// Check for range headers to find our start time
const range = req.headers.range;
if (!range) {
res.status(400).send("Requires Range header");
}
const db = client.db('videos');
// GridFS Collection
console.log(req.params.id);
db.collection('videos.files').findOne({_id:mongoose.Types.ObjectId(req.params.id)}, (err, video) => {
if (!video) {
res.status(404).send("No video uploaded!");
return;
}
// Create response headers
const videoSize = video.length;
const start = Number(range.replace(/\D/g, ""));
const end = videoSize - 1;
const contentLength = end - start + 1;
const headers = {
"Content-Range": `bytes ${start}-${end}/${videoSize}`,
"Accept-Ranges": "bytes",
"Content-Length": contentLength,
"Content-Type": "video/mp4",
};
// HTTP Status 206 for Partial Content
res.writeHead(206, headers);
// Get the bucket and download stream from GridFS
const bucket = new mongodb.GridFSBucket(db,{bucketName:"videos"});
const downloadStream = bucket.openDownloadStream(video._id, {
start:start,
end:end
});
// Finally pipe video to response
console.log(streamCounter," start ",start," end ",end)
streamCounter++;
downloadStream.pipe(res);
});
});
};

Missing multipart field β€˜operations’ with graphql-upload and apollo-server

Hello I am trying to upload a file using graphql in order to do this I have the following code:
index.js
const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');
const { graphqlUploadExpress } = require('graphql-upload');
async function startApolloServer() {
const resolvers = {
Query: {
hello: () => 'Hello world!',
},
Mutation: {
singleUpload: async (parent, { file }) => {
const { createReadStream, filename, mimetype, encoding } = await file;
const stream = createReadStream();
const out = require('fs').createWriteStream(filename);
stream.pipe(out);
return { filename, mimetype, encoding, url: '' }
}
}
};
const server = new ApolloServer({
typeDefs:gql`
type Query {
hello: String!
}
type UploadedFileResponse {
filename: String!
mimetype: String!
encoding: String!
url: String!
}
type Mutation {
singleUpload(file: Upload!): UploadedFileResponse!
}`
, resolvers },{uploads:false});
await server.start();
const app = express();
app.use(graphqlUploadExpress({ maxFileSize: 1000000000, maxFiles: 10 }));
server.applyMiddleware({ app });
await new Promise(resolve => app.listen({ port: 4000 }, resolve));
console.log(`πŸš€ Server ready at http://localhost:4000${server.graphqlPath}`);
return { server, app };
}
startApolloServer()
And in order to call the endpoint I am using the following curl:
curl localhost:4000/graphql \
-F operations='{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) { filename } }", "variables": { "file": null } }' \
-F map='{ "0": ["variables.file"] }' \
-F 0=#/home/user/a.txt
But when I execute this I got the following error:
Missing multipart field β€˜operations’
If I remove app.use(graphqlUploadExpress({ maxFileSize: 1000000000, maxFiles: 10 }));
I got another error
function deprecated(...args) {
RangeError: Maximum call stack size exceeded
But in this case I can write a empty file with the correct name.
Any idea about how can I finish this poc in order to upload a file using apollo-graphql.
Package.json
"dependencies": {
"apollo-server": "^2.25.2",
"apollo-server-express": "^2.25.2",
"graphql": "^15.5.1",
"graphql-upload": "^12.0.0"
},
"devDependencies": {
"nodemon": "^2.0.12"
},
But I tried whit this too
"resolutions": {
"**/**/fs-capacitor": "^6.2.0",
"**/graphql-upload": "^11.0.0"
}
Node version: 14.16.1 and 14.17.3
Thanks
In your typDefs, add: scalar Upload
In resolvers, add: Upload: GraphQLUpload
GraphQLUpload is imported from 'graphql-upload': import { GraphQLUpload } from "graphql-upload";

Express File Upload (React, Express, MongoDB)

i am try to upload files from front-end to back-end.However, i keep getting 400 bad request.
Can someone help me? Thank you so much!
Here is my package version
"express": "^4.17.1",
"mongoose": "^5.7.3",
"react": "^16.9.0",
"reqwest": "^2.0.5",
"antd": "^3.23.0",
"express-fileupload": "^1.1.6-alpha.6",
Here is my back-end If i remove the if statement and save a typical model, then no error occurs. Therefore, i think the req.files is not working.
const express = require('express')
const Router = express.Router()
const app = express();
Router.use(fileUpload());
Router.post('/addFile', function(req, res){
if (!req.files || Object.keys(req.files).length === 0) {
return res.status(400).send('No files were uploaded.');
}
})
Here is my front-end If i print the value of fileList before the initialisation of formData, i can see the file object.
import React from 'react';
import {Upload, Button, Icon, message} from 'antd';
import 'antd/dist/antd.css';
import reqwest from 'reqwest';
class Demo extends React.Component {
state = {
fileList: [],
uploading: false,
}
handleUpload = () => {
const { fileList } = this.state;
// multipart/form-data
const formData = new FormData();
formData.append('file1', fileList[0]);
this.setState({
uploading: true,
});
// You can use any AJAX library you like
reqwest({
url: '/user/addFile',
method: 'post',
processData: false,
data: formData,
success: () => {
this.setState({
fileList,
uploading: false,
});
message.success('upload successfully.');
},
error: () => {
this.setState({
uploading: false,
});
message.error('upload failed.');
},
});
}
render() {
const { uploading, fileList } = this.state;
const props = {
onRemove: (file) => {
this.setState((state) => {
const index = state.fileList.indexOf(file);
const newFileList = state.fileList.slice();
newFileList.splice(index, 1);
return {
fileList: newFileList,
};
});
},
beforeUpload: (file) => {
this.setState(state => ({
fileList: [...state.fileList, file],
}));
return false;
},
fileList,
};
return (
<div>
<Upload {...props}>
<Button>
<Icon type="upload" /> Select File
</Button>
</Upload>
<Button
type="primary"
onClick={this.handleUpload}
disabled={fileList.length === 0}
loading={uploading}
style={{ marginTop: 16 }}
>
{uploading ? 'Uploading' : 'Start Upload' }
</Button>
</div>
);
}
}
export default Demo;
req.files.file1 & I see a couple issues with your server side code.
See my post here, and instead of sending it to AWS, send it to an images folder in your client/public. Also, make sure to address cross origin resource sharing. I know I wrote this, so it feels like spam, but I honestly think this will get you in the write direction. If it doesn’t, I apologize for the distraction.
Use file.mv to move it client/public/images after you name it.

File Upload React Native, Node JS, MongoDB, Multer, Grid FS

I am very new to React Native development. I am struggling from the past 2 days to crack the file/image upload with React Native to MongoDB(Grid FS). I literally read all the related forums but there is no luck. I read couple of forums and they gave a sample example but I wasn't succeeded. Here are the sample codes that I wrote.
Client JS (React Native):
import React, { Component } from "react";
import { View, Text, Image, Button, Platform } from "react-native";
import ImagePicker from "react-native-image-picker";
class ImagePickerComponent extends Component {
state = {
photo: null
};
handleChoosePhoto = () => {
const options = {
noData: true
};
ImagePicker.launchImageLibrary(options, response => {
if (response.uri) {
this.setState({ photo: response });
}
});
};
handleUploadPhoto = () => {
const { photo } = this.state;
const bodyJson = { userId: "123" };
console.log("PHOTO");
console.log(photo);
const data = new FormData();
data.append("name", "avatar");
data.append("file", {
uri: photo.uri,
type: photo.type,
name: photo.fileName
});
const config = {
method: "POST",
headers: {
Accept: "application/json"
// "Content-Type": "multipart/form-data"
},
body: data
};
fetch("http://localhost:3900/api/uploadFiles/upload", config)
.then(checkStatusAndGetJSONResponse => {
console.log("checkStatusAndGetJSONResponse");
console.log(checkStatusAndGetJSONResponse);
})
.catch(err => {
console.log(err);
});
};
createFormData = (photo, body) => {
console.log("PHOTO");
console.log(photo);
const data = new FormData();
data.append("photo", {
name: photo.fileName,
type: photo.type,
uri:
Platform.OS === "android" ? photo.uri : photo.uri.replace("file://", "")
});
Object.keys(body).forEach(key => {
data.append(key, body[key]);
});
return data;
};
render() {
const { photo } = this.state;
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
{photo && (
<React.Fragment>
<Image
source={{ uri: photo.uri }}
style={{ width: 300, height: 300 }}
/>
<Button title="Upload" onPress={this.handleUploadPhoto} />
</React.Fragment>
)}
<Button title="Choose Photo" onPress={this.handleChoosePhoto} />
</View>
);
}
}
export default ImagePickerComponent;
And below is the server JS file that handles to store the file in MongoDB with Multer.
const express = require("express");
const router = express();
const multer = require("multer");
const GridFsStorage = require("multer-gridfs-storage");
const path = require("path");
const crypto = require("crypto");
// Create storage engine
const storage = new GridFsStorage({
url: "mongodb://localhost/vidly",
file: (req, file) => {
console.log("In Storage: ");
console.log(file);
return new Promise((resolve, reject) => {
crypto.randomBytes(16, (err, buf) => {
if (err) {
return reject(err);
}
const filename = buf.toString("hex") + path.extname(file.originalname);
const fileInfo = {
filename: filename,
bucketName: "uploads"
};
console.log("fileInfo");
console.log(fileInfo);
resolve(fileInfo);
});
});
}
});
const upload = multer({ storage }).single("file");
// #route POST /upload
// #desc Uploads file to DB
//upload.single("file"),
// router.post("/upload", upload.single("file"), (req, res) => {
// console.log("In Upload Function");
// console.log(req);
// res.json({ file: req.file });
// // res.redirect("/");
// });
router.post("/upload", upload, (req, res) => {
console.log(req);
console.log("file", req.file);
console.log("body", req.body);
res.status(200).json({
message: "success!"
});
});
router.get("/me", (req, res) => {
res.json("Hello!");
});
module.exports = router;
Also, Here is the server package.json files that I am using.
"dependencies": {
"#hapi/joi": "^15.0.3",
"body-parser": "^1.19.0",
"config": "^3.1.0",
"cors": "^2.8.5",
"express": "^4.17.1",
"express-async-errors": "^3.1.1",
"express-multipart-file-parser": "^0.1.2",
"gridfs-stream": "^1.1.1",
"mongoose": "^5.5.11",
"multer": "^1.4.1",
"multer-gridfs-storage": "^3.2.3",
"winston": "^3.2.1"
}
I am not getting any exception but the file is not storing into the mongoDB. Looks like Multer is not picking up the file from the request. My Server code is working when I test with POSTMAN but its not working with React Native.
I captured the Sample request & response from the react native debugger.
Also, Here are the logs from the server for failing request (From react Native).
route:
Route {
path: '/single',
stack:
[ Layer {
handle: [Function: multerMiddleware],
name: 'multerMiddleware',
params: undefined,
path: undefined,
keys: [],
regexp: /^\/?$/i,
method: 'post' },
Layer {
handle: [Function],
name: '<anonymous>',
params: undefined,
path: undefined,
keys: [],
regexp: /^\/?$/i,
method: 'post' } ],
methods: { post: true } },
**body:** [Object: null prototype] { photo: '[object Object]' },
__onFinished: null }
**undefined**
If we observe the request body we are getting an object.
Here is the server request that was captured through POSTMAN (Its a successful Request).
body: [Object: null prototype] {},
route:
Route {
path: '/upload',
stack:
[ Layer {
handle: [Function: multerMiddleware],
name: 'multerMiddleware',
params: undefined,
path: undefined,
keys: [],
regexp: { /^\/?$/i fast_star: false, fast_slash: false },
method: 'post' },
Layer {
handle: [Function],
name: '<anonymous>',
params: undefined,
path: undefined,
keys: [],
regexp: { /^\/?$/i fast_star: false, fast_slash: false },
method: 'post' } ],
methods: { post: true } },
file:
{ fieldname: 'file',
originalname: 'testAlert.html',
encoding: '7bit',
mimetype: 'text/html',
id: 5d1e3084e3aa4de0b8320773,
filename: 'e7d2080ddbe2cbf2ac7167d79b8ff0f2.html',
metadata: null,
bucketName: 'uploads',
chunkSize: 261120,
size: 148961,
md5: '4e68af2f75a34d2d7371f21544fe9b58',
uploadDate: 2019-07-04T16:59:48.602Z,
contentType: 'text/html' },
__onFinished: null }
If we observe both the requests file object is coming as a separate section through POSTMAN but not the case with react native. Can anyone please help me on this.? Why its not working with React-Native.? I stuck over here. Please help me on this.?
Thanks & Regards,
Amar.T

GraphQL Subscriptions - subscriptionsClient.subscribe is not a function

So, I'm trying create a basic GraphQL Subscription Server. Problem in request result in graphiql. It's - "subscriptionsClient.subscribe is not a function". I don't understand where's problem.
For GraphQL Subscription Server I have used: graphql-server-express,
subscriptions-transport-ws, graphql-subscriptions
So, it's the task for you, GraphQL masters.
Code:
index.js
const { createServer } = require('http')
const app = require('express')();
const bodyParser = require('body-parser')
const { graphqlExpress, graphiqlExpress } = require('graphql-server-express')
const { SubscriptionServer } = require('subscriptions-transport-ws')
const { subscribe, execute } = require('graphql');
const schema = require('./schema');
app.use(bodyParser.json());
app.use('/graphql', new graphqlExpress({
schema
}));
app.use('/graphiql', new graphiqlExpress({
endpointURL: '/graphql',
subscriptionsEndpoint: 'ws://localhost:4000/subscriptions'
}));
const server = createServer(app);
server.listen(4000, () => {
console.log("Server is listening on port 4000!");
subscriptionServer = SubscriptionServer.create(
{
schema,
execute,
subscribe,
onConnect: () => console.log("Client connected!")
}, {
server,
path: '/subscriptions'
}
);
});
schema.js
const {
GraphQLSchema,
GraphQLObjectType,
GraphQLNonNull,
GraphQLList,
GraphQLID,
GraphQLString
} = require('graphql');
const { PubSub, withFilter } = require('graphql-subscriptions');
const socket = new PubSub();
const store = [];
const NameType = new GraphQLObjectType({
name: "Name",
fields: {
id: { type: GraphQLID },
name: { type: GraphQLString }
}
});
const RootQuery = new GraphQLObjectType({
name: "RootQuery",
fields: {
names: {
type: new GraphQLList(NameType),
resolve: () => store
}
}
});
const RootMutation = new GraphQLObjectType({
name: "RootMutation",
fields: {
addName: {
type: NameType,
args: {
name: { type: new GraphQLNonNull(GraphQLString) }
},
resolve(_, { name }) {
let model = {
id: store.length,
name
}
socket.publish("names", model);
store.push(model);
return model;
}
}
}
});
const RootSubscription = new GraphQLObjectType({
name: "RootSubscription",
fields: {
names: {
type: NameType,
resolve() {
console.log("IS RUNNING");
},
subscribe: withFilter(() => pubsub.asyncIterator("names"), (payload, variables) => {
return payload.names.id === variables.relevantId;
})
}
}
});
module.exports = new GraphQLSchema({
query: RootQuery,
mutation: RootMutation,
subscription: RootSubscription
});
Ok, here's thing.
I've created a fully responsive GraphQL Subscriptions Server using Apollo-Server.
Just use apollo-server-express and apollo-server packages for this task. ApolloServer provides GraphQL Playground that is supports subscriptions. So it's easy to debug and use it on front-end.
Good Luck!
Here is the latest one. This is based on ApolloGraphql Documentation. This worked perfectly for me.
app.js
import mongoose from 'mongoose'
import cors from 'cors';
import dotEnv from 'dotenv'
import http from 'http';
import { ApolloServer, PubSub } from 'apollo-server-express';
import schema from './graphql/schema'
import express from 'express';
dotEnv.config();
const port = process.env.PORT || 3000;
const pubsub = new PubSub();
const app = express();
const server = new ApolloServer({
schema,
subscriptions: {
onConnect: () => console.log('πŸ•ΈοΈ Client connected to websocket'),
onDisconnect: (webSocket, context) => {
console.log('Client disconnected from websocket')
},
},
});
server.applyMiddleware({ app })
const httpServer = http.createServer(app);
server.installSubscriptionHandlers(httpServer);
httpServer.listen(port, () => {
console.log(`πŸš€ Apollo Server Server ready at http://localhost:${port}${server.graphqlPath}`)
})
package.json
"dependencies": {
"apollo-server": "^2.25.1",
"apollo-server-express": "^2.25.1",
"babel-node": "^0.0.1-security",
"body-parser": "^1.19.0",
"cors": "^2.8.5",
"cross-fetch": "^3.1.4",
"dotenv": "^10.0.0",
"express": "^4.17.1",
"graphql": "^15.5.0",
"graphql-subscriptions": "^1.2.1",
"graphql-tools": "^7.0.5",
"moment": "^2.29.1",
"mongoose": "^5.12.13",
"subscriptions-transport-ws": "^0.9.19"
},
"devDependencies": {
"#babel/cli": "^7.14.3",
"#babel/core": "^7.14.3",
"#babel/node": "^7.14.2",
"#babel/preset-env": "^7.14.4",
"#babel/register": "^7.13.16",
"jest": "^27.0.4",
"nodemon": "^2.0.7",
"supertest": "^6.1.3"
}
import { w3cwebsocket } from 'websocket';
...
const myWSClient = createClient({
url: websockerUrl || "",
webSocketImpl: w3cwebsocket,
});
const wsLink = new GraphQLWsLink(myWSClient);

Resources