Can't connect to websocket server after pushing to vercel - node.js

When ever I run my server locally it works perfectly
But once I upload it to vercel I get errors like polling-xhr.js:202 GET https://giphy-chat-server.vercel.app/socket.io/?EIO=4&transport=polling&t=NQ03j3c&sid=H_PHDh9-4UKRVGTVAAAC 400
And WebSocket connection to 'wss://giphy-chat-server.vercel.app/socket.io/?EIO=4&transport=websocket&sid=k-Sex1ZKmrQQFoSKAAAA' failed: Error during WebSocket handshake: Unexpected response code: 400
I have tried so many solutions but none is working... I can't just figure out the problem. I would be glad if Its answered. Thank you
const express = require("express");
const app = express();
const http = require("http");
const path = require("path");
var server = http.createServer(app);
const io = require("socket.io")(server, {
cors: {
origin: "*",
credentials: true,
methods: ["GET", "POST"],
},
});
const { MONGODB_URI } = require("./config");
const port = process.env.PORT || 8000;
const Message = require("./message_model");
const mongoose = require("mongoose");
mongoose
.connect(MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
})
.then((result) => {
server.listen(port, () => {
console.log(`Listening on port ${port}...`);
});
})
.catch((err) => {
console.log(err);
});
app.use(express.static(path.join(__dirname, "..", "client", "build")));
const users = [];
io.on("connection", (socket) => {
users.push({ id: socket.id });
io.emit("users", { users: users });
Message.find()
.sort({ createdAt: -1 })
.limit(10)
.exec((err, messages) => {
if (err) return console.error(err);
socket.emit("init", messages);
});
socket.on("message", (msg) => {
const message = new Message({
content: msg.content,
name: msg.name,
});
message.save((err) => {
if (err) return console.error(err);
});
socket.broadcast.emit("push", msg);
});
socket.on("disconnect", (reason) => {
let index = -1;
for (let i = 0; i < users.length; i++) {
const user = users[i];
if (user.id === socket.id) {
index = i;
}
}
if (index !== -1) {
users.splice(index, 1);
}
io.emit("users", { users: users });
});
});
app.get("/", (req, res) => {
res.send("Giphy Chat Server is running successfully");
});
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header("Access-Control-Allow-Headers", "Content-Type");
res.header("Access-Control-Allow-Methods", "PUT, GET, POST, DELETE, OPTIONS");
next();
});

I think this will be due to Vercel's serverless functions having a maximum execution timeout, so they can't maintain a websocket connection. In order to use Websockets with Vercel you'll need to use a third party service to handle your websocket connections for you. Something like Ably or Pusher, or PubNub.
I just wrote up a demo of next + ably if it would be helpful - https://github.com/ably-labs/NextJS-chat-app

Related

NodeJS, Socket.io - The websocket getting disconnected immediately after getting connected

I have my backend in NodeJS and frontend in NextJS. As soon I try to connect with the server for a WebSocket connection, the server connects the socket and immediately disconnects it.
This is what I get on my server as soon as I reload my frontend page:
user connected ---- OKGG9r0qvvBs0EWeAAAB test#test.com socket disconnected--- OKGG9r0qvvBs0EWeAAAB test#test.com
This is my server code:
import express from "express";
import dotenv from "dotenv";
import http from "http";
import { Server, Socket } from "socket.io";
import userRoutes from "./routes/users";
import { DefaultEventsMap } from "socket.io/dist/typed-events";
dotenv.config();
const PORT = process.env.PORT || 5000;
const app = express();
const server = http.createServer(app);
const io = new Server(server, {
cors: {
origin: process.env.CLIENT_URL,
},
});
app.use(express.json());
app.get("/ping", (req, res) => res.status(200).send("Pong"));
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "http://localhost:3000");
res.header("Access-Control-Allow-Credentials", "true");
res.header("Access-Control-Allow-Methods", "POST, GET");
res.header(
"Access-Control-Allow-Headers",
"Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With"
);
next();
});
interface SType extends Socket {
email?: string;
}
io.use((socket: SType, next) => {
const userEmail = socket.handshake.auth.email;
if (!userEmail) {
return next(new Error("Invalid email"));
}
console.log("userEmail-----", userEmail);
socket.email = userEmail;
next();
});
io.on("connection", (socket) => {
const users = [];
for (let [id, socket] of io.of("/").sockets) {
users.push({
userID: id,
email: (socket as any).email,
});
socket.emit("users", users);
}
socket.broadcast.emit("user connected", {
userID: socket.id,
email: (socket as any).email,
});
console.log("user connected ---- ", socket.id, (socket as any).email);
socket.on("disconnect", () => {
console.log("socket disconnected---", socket.id, (socket as any).email);
(socket as any).email = "";
});
});
app.use("/users", userRoutes);
server.listen(PORT, () => console.log(`server running on port ${PORT}`));
this is code on client side
useEffect(() => {
if (userState) {
socket.auth = { email: userState.email };
}
}, [userState]);
useEffect(() => {
socket.on("users", (users: any) => {
console.log("users", users);
setUsers(users);
});
socket.on("user connected", (user: any) => {
setUsers((previousState: any) => [...previousState, user]);
});
}, [socket]);
I tried the solutions from stack overflow as well as socket.io documentation, but nothing seems to work. Does anyone know what is happening here?

Heroku: json data displayed instead of UI

My app works locally but once deployed to Heroku, every page works except the home page where the json data is displayed instead of the UI. On this page, I am listing the posts from the db so the posts in the db are being displayed as json data.
I tried prepending post routes with '/post' to stop api returning html on '/' route but now I'm getting this problem. Whatever I change, it just keeps switching between these two issues - either json data is displayed or index.html is returned instead of data.
How can I fix this?
Thanks!
The route for that specific page (this is the first route):
<Route path="/posts/" exact component={Home} />
server.js
// imports
...
require("dotenv").config();
// import routes
const authRoutes = require("./routes/auth");
const userRoutes = require("./routes/user");
const postRoutes = require("./routes/posts");
// app
const app = express();
// connect db
const url = process.env.MONGODB_URI
mongoose.connect(url, {
useNewUrlParser: true,
useCreateIndex: true,
useUnifiedTopology: true,
useFindAndModify: false,
});
mongoose.connection
.once("open", function () {
console.log("DB Connected!");
})
.on("error", function (error) {
console.log("Error is: ", error);
});
// middlewares
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", '*');
res.header("Access-Control-Allow-Credentials", true);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
res.header("Access-Control-Allow-Headers", 'Origin,X-Requested-With,Content-Type,Accept,content-type,application/json');
next();
});
//middleware
...
// routes middleware
// app.use(express.static(path.join(__dirname, './client/build')))
if (process.env.NODE_ENV === "production") {
app.use(express.static("client/build"));
}
app.use(authRoutes);
app.use(userRoutes);
app.use('/post', postRoutes);
app.get("/*", function (req, res) {
res.sendFile(path.join(__dirname, "./client/build/index.html"));
});
const port = process.env.PORT || 80;
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
ListPosts.js
// imports
...
import { API } from "../config";
class ListPosts extends React.Component {
state = {
title: "",
body: "",
date: "",
posts: [],
};
componentDidMount = () => {
this.getPosts();
};
getPosts = () => {
axios
.get(`${API}/post/`)
.then((response) => {
const posts = response.data;
this.setState({ posts });
})
.catch((error) => {
console.log(error);
});
};
displayPosts = (posts) => {
if (!posts.length) return null;
return posts.map((post, index) => (
...
));
};
render() {
return <div>{this.displayPosts(this.state.posts)}</div>;
}
}
export default ListPosts;
list route/controller
router.get("/", list);
exports.list = (req, res) => {
const sort = { title: 1 };
Post.find()
.sort(sort)
.then((posts) => res.json(posts))
.catch((err) => res.status(400).json("Error: " + err));
};

React Hooks/Next.js and Multer: 401 Unauthorized, Error while uploading image to MongoDB Atlas

I've tried numerous configurations and been pulling my hair out for a couple days now. However no matter what I do when I try to hit /users/uploadmulter I get an authorized error.
On mongoDB atlas I've tried creating a new collection.
Moving the route declaration in my users file (that way I know it's not an express config issue as other operations work i.e. make a user, get new password etc.)
Anyway, here is my React component:
import { useState, useEffect } from 'react';
import { Card, Icon, Image, Segment, Form } from 'semantic-ui-react';
import axios from 'axios';
function ImageUploader() {
var [defaultImage, setDefaultImage] = useState(
require('../../assets/images/placeholder.jpg')
);
var [userAvatar, setUserAvatar] = useState(defaultImage);
useEffect(() => {
setUserAvatar(userAvatar);
}, [userAvatar]);
function fileUploader(e) {
console.log('event fileUploader ', e);
var imageFormObj = new FormData();
console.log('e.target.files[0] ', e.target.files[0]);
imageFormObj.append('avatar', 'multer-image-' + Date.now());
imageFormObj.append('imageData', e.target.files[0]);
setUserAvatar(URL.createObjectURL(e.target.files[0]));
console.log('userAvatar ', userAvatar);
console.log('imageFormObj ', imageFormObj);
axios
.post('http://localhost:8016/users/uploadmulter', imageFormObj)
.then(data => {
if (data.data.success) {
alert('Image has been successfully uploaded using multer');
}
})
.catch(err => {
alert('Error while uploading image using multer');
});
}
return (
<>
<Segment>
<Card fluid>
<Image src={userAvatar} alt="upload-image" />
<Segment>
<Form encType="multipart/form-data">
<Form.Field>
<input
placeholder="Name of image"
className="process__upload-btn"
type="file"
name="avatar"
content="Edit your Avatar!"
onChange={e => fileUploader(e)}
/>
{/* <Button
content="Edit your Avatar!"
labelPosition="left"
icon="file"
onClick={e => fileUploader(e)}
/> */}
</Form.Field>
</Form>
</Segment>
<Card.Content>
<Card.Header>Charly</Card.Header>
<Card.Meta>
<span className="date">Joined in 2015</span>
</Card.Meta>
<Card.Description>Charly</Card.Description>
</Card.Content>
<Card.Content extra>
<a>
<Icon name="user" />
22 Friends
</a>
</Card.Content>
</Card>
</Segment>
</>
);
}
export default ImageUploader;
And this is my route which handles sending the data to MongoDB Atlas:
var router = require('express').Router();
var Image = require('../models/UserImagesSchema');
var multer = require('multer');
var storage = multer.diskStorage({
destination: function(req, file, cb) {
cb(null, './uploads/');
},
filename: function(req, file, cb) {
cb(null, Date.now() + file.originalname);
}
});
var upload = multer({
storage: storage,
limits: {
fileSize: 1024 * 1024 * 5
}
});
/*
stores image in uploads folder
using mulkter and creates a reference to the file
*/
router.post(upload.single('imageData'), (req, res, next) => {
console.log(req.body);
var newImage = new Image({
avatar: {
imageName: req.body.avatar,
imageData: req.file.path
}
});
newImage
.save()
.then(result => {
console.log(result);
res.status(200).json({
success: true,
document: result
});
})
.catch(err => next(err));
});
module.exports = router;
This is my Schema:
var mongoose = require('mongoose');
/* Image Schema for storing images in the mongodb database */
var UserImagesSchema = new mongoose.Schema({
avatar: {
_userId: { type: mongoose.Schema.Types.ObjectId, required: true, ref: 'User' },
imageName: { type: String, default: 'none', required: true },
imageData: {
data: Buffer,
contentType: String
}
}
});
module.exports = mongoose.model('UserImages', UserImagesSchema);
This is the app/server file for express:
var express = require('express');
require('dotenv').config();
var path = require('path');
var morgan = require('morgan');
var cookieParser = require('cookie-parser');
var cors = require('cors');
var nextJS = require('next');
var session = require('express-session');
var MongoStore = require('connect-mongo')(session);
var bodyParser = require('body-parser');
var auth = require('./lib/auth');
var HttpStatus = require('http-status-codes');
var compression = require('compression');
var helmet = require('helmet');
var PORT = process.env.PORT || 8016;
var { isBlockedPage, isInternalUrl } = require('next-server/dist/server/utils');
function NODE_ENVSetter(ENV) {
var environment,
environments = {
production: () => {
environment = process.env.MONGODB_URI;
console.log(`We are currently in the production environment: ${environment}`);
return environment;
},
test: () => {
environment = process.env.TEST_DB_DSN;
console.log(`We are currently in the test environment: ${environment}`);
return environment;
},
default: () => {
environment = process.env.DEVELOPMENT_DB_DSN;
console.log(`We are currently in the development environment: ${environment}`);
return environment;
}
};
(environments[ENV] || environments['default'])();
return environment;
}
var db = NODE_ENVSetter('development');
var mongoose = require('mongoose');
function errorHandler(err, req, res, next) {
// Set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// Log error
console.error(err.stack);
// Render the error page
res.status(err.status || 500);
// Default error message by HTTP code
res.render('error', {
title: HttpStatus.getStatusText(err.status),
message: HttpStatus.getStatusText(err.status)
});
}
function start() {
const dev = process.env.NODE_ENV !== 'production';
const app = nextJS({ dev });
const server = express();
// const proxy = createProxyMiddleware(options);
app
.prepare()
.then(() => {
mongoose.connect(db, { useNewUrlParser: true, useUnifiedTopology: true });
mongoose.Promise = global.Promise;
mongoose.connection
.on('connected', () => {
console.log(`Mongoose connection open on ${db}`);
})
.on('error', err => {
console.log(`Connection error: ${err.message}`);
});
})
.catch(err => {
console.error(err);
});
server.use(
session({
secret: 'very secret 12345',
resave: false,
saveUninitialized: false,
store: new MongoStore({ mongooseConnection: mongoose.connection })
})
);
server.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
);
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Methods', '*'); // enables all the methods to take place
return next();
});
server.set('view engine', 'html');
server.use(cors());
server.use(morgan('dev'));
server.use('/uploads', express.static('uploads'));
server.use(bodyParser.json({ limit: '50mb' }));
server.use(bodyParser.urlencoded({ limit: '50mb', extended: true }));
server.use(cookieParser());
server.use(express.static(path.join(__dirname + 'uploads')));
server.use(compression());
server.use(helmet());
server.use(auth.initialize);
server.use(auth.session);
server.use(auth.setUser);
// console.log('auth.setUser ', auth.setUser);
server.use('/users', require('./users'));
// Redirect all requests to main entrypoint pages/index.js
server.get('/*', async (req, res, next) => {
try {
// #NOTE code duplication from here
// https://github.com/zeit/next.js/blob/cc6fe5fdf92c9c618a739128fbd5192a6d397afa/packages/next-server/server/next-server.ts#L405
const pathName = req.originalUrl;
if (isInternalUrl(req.url)) {
return app.handleRequest(req, res, req.originalUrl);
}
if (isBlockedPage(pathName)) {
return app.render404(req, res, req.originalUrl);
}
// Provide react-router static router with a context object
// https://reacttraining.com/react-router/web/guides/server-rendering
req.locals = {};
req.locals.context = {};
const html = await app.renderToHTML(req, res, '/', {});
// Handle client redirects
const context = req.locals.context;
if (context.url) {
return res.redirect(context.url);
}
// Handle client response statuses
if (context.status) {
return res.status(context.status).send();
}
// Request was ended by the user
if (html === null) {
return;
}
app.sendHTML(req, res, html);
} catch (e) {
next(e);
}
});
// catch 404 and forward to error handler
server.use(function(req, res, next) {
next(createError(404));
});
// error handler
server.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.errorStatus = err.status;
res.locals.errorMessage = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
console.log('err.status ', err.status);
res.status(401).send(err.message);
});
if (process.env.NODE_ENV === 'production') {
server.use(express.static('.next/static'));
server.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, '.next/static', 'index.html'));
});
server.listen(PORT, err => {
if (err) throw err;
console.log(
`> Ready and listening on PORT:${PORT} in the ${process.env.NODE_ENV} environment`
);
});
} else {
server.listen(PORT, err => {
if (err) throw err;
console.log(`> Ready and listening on http://localhost:${PORT}`);
});
}
}
start();
If your files are actually less than 16 mb, please try using this Converter that changes the image of format jpeg / png to a format of saving to mongodb, and you can see this as an easy alternative for gridfs ,
please follow this github repo for more details, please try this method,
https://github.com/saran-surya/Mongo-Image-Converter

Post request from react app stalls after deploying to Heroku

I've made this app using the MERN stack and am facing problems while deploying to Heroku. I have a userContext.js file which handles authentication of users. It has a function which makes a post request to the server which in development is on localhost:80. This has been working fine in development and the request is successful. After deploying the same request stalls and fails. I don't understand how to get a response from the request. Any help is really appreciated. Thanks, Hatim
server.js
const express = require("express"),
http = require("http"),
app = express(),
server = http.createServer(app),
bodyParser = require("body-parser"),
mongoose = require("mongoose"),
passport = require("passport"),
cors = require("cors");
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cors());
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "DELETE, PUT, GET, POST");
res.header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept"
);
next();
});
//IMPORT MODELS
require("./models/products");
require("./models/user");
//MONGOOSE CONNECT
mongoose.Promise = global.Promise;
mongoose
.connect(
process.env.MONGODB_URI || `mongodb://localhost:27017/technicalKwt`,
{
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false
}
)
.then(() => console.log("MONGODB Connected"))
.catch(err => {
console.log(err);
});
//SET FOR PRODUCTION
if (process.env.NODE_ENV === "production") {
app.use(express.static("client/build"));
const path = require("path");
app.get("*", (req, res) => {
res.sendFile(path.resolve(__dirname, "client", "build", "index.html"));
});
}
// Passport middleware
app.use(passport.initialize());
// Passport config
require("./config/passport")(passport);
require("./api/products")(app);
require("./api/transaction")(app);
require("./api/users")(app);
require("./api/genPDF")(app);
const port = process.env.PORT || 80;
server.listen(port, () => {
console.log(`Listening on port ${port}`);
});
users.js
app.post("/login", (req, res) => {
console.log(req.body);
const email = req.body.email;
const password = req.body.password;
User.findOne({ email }).then(user => {
if (!user) {
return res.status(401).json({ message: "Invalid Credentials" });
}
// Check password
bcrypt.compare(password, user.password).then(isMatch => {
if (isMatch) {
// User matched
// Create JWT Payload
const payload = {
id: user.id,
name: user.name
};
// Sign token
jwt.sign(
payload,
keys.secretOrKey,
{
expiresIn: 31556926 // 1 year in seconds
},
(err, token) => {
res.json({
success: true,
token: "Bearer " + token,
payload
});
}
);
} else {
return res.status(401).json({ message: "Invalid Credentials" });
}
});
});
});
loginUser function in userContext.js
function loginUser(dispatch, login, password, history, setIsLoading, setError) {
setIsLoading(true);
if (!!login && !!password) {
axios
.post('http://localhost:80/login', {
email: login,
password,
})
.then(res => {
localStorage.setItem('id_token', res.data.payload.id);
localStorage.setItem('name', res.data.payload.name);
setIsLoading(false);
dispatch({ type: 'LOGIN_SUCCESS' });
history.push('/app/products');
})
.catch(err => {
setIsLoading(false);
dispatch({ type: 'LOGIN_FAILURE' });
});
} else {
dispatch({ type: 'LOGIN_FAILURE' });
setIsLoading(false);
}
}
I'm making some assumptions about your app here.
On Heroku I believe you cannot use './' anymore. In your server.js try using the built-in '__dirname':
require(__dirname + "/models/users")
Second issue: Your userContext.js is presumably on the client-side now, as in a person views this on their browser. The path to your routes is no longer localhost:80 it's your heroku domain (something.com) so your axios post request needs the new url. Try this:
axios
.post('http://' + document.location.hostname + '/login', {
email: login,
password,
})

NodeJS, Express and JSON API Response Time

I have create a simple express api to handle JSON data on my webapp.
Even if the app is on the same server, the response times are always on 100ms range, the actual data fetching is under 5ms;
Here is the data fetching zone :
export function getData(params) {
return new Promise((resolve, reject) => {
if (!params.take || params.take > 30 && isBrowser) {
params.take = 30;
}
console.time("API QUERY Execute");
{Data Fetching Goes here}
console.timeEnd("API QUERY Execute");
});
}
That method takes under 5 ms to complete from console.time.
Here is the express route config
app.route('/api/data').get((req, res) => {
getData(req.query)
.then((data) => {
res.json(data);
}).catch((err) => {
res.status(400);
res.send(err.message);
});
});
The actual Express App :
export default (parameters) => {
const app = Express();
const server = require('http').Server(app);
let io;
let redisCaching;
if (process.env.ENABLE_API === 'true') {
const Mongoose = require('mongoose');
const Redis = require('socket.io-redis');
Mongoose.connect(config.server.mongodb.url, config.server.mongodb.options);
Mongoose.connection.on('connected', () => {
console.log('==> Connected ', config.server.mongodb.url);
});
io = require('socket.io')(server);
io.sockets.on('connection', (socket) => {
console.log('==> Socket Connect', socket.handshake.headers.referer);
});
io.adapter(Redis({ host: 'localhost', port: 6379 }));
redisCaching = require('../../redis/redisCaching').redisCaching;
redisCaching.configure(6379, config.server.hostname);
}
if (process.env.CORS === 'true') {
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
}
app.route('/api/data').get((req, res) => {
getArticles(req.query)
.then((data) => {
res.json(data);
}).catch((err) => {
res.status(400);
res.send(err.message);
});
});
app.listen(process.env.PORT, () => {
console.log(`App listening on port ${process.env.PORT}`);
});
};
When using tools like pingdom, it showcases 100MS

Resources