NodeJS/Express Server on Heroku, NextJS Client on Vercel, SocketIO Problem - node.js

I’ve got a problem with my NextJS + NodeJS + SocketIO setup and i wrap my head around it since days.
In development mode on my mac machine everything is fine but in production there is the problem.
The NodeJS server is hosted on Heroku and the NextJS client is hosted on Vercel.
Server:
require('dotenv').config()
// Packages
const express = require('express')
const mongoose = require('mongoose')
const passport = require('passport')
const cookie = require('cookie')
const jwtDecode = require('jwt-decode')
const cors = require('cors')
// App
const app = express()
// Models
const User = require('./models/User')
// App Settings
app.use(cors())
app.use(express.urlencoded({ limit: '10mb', extended: true }))
app.use(express.json({ limit: '10mb', extended: true }))
app.use(passport.initialize())
// App Routes
app.use('/_admin', require('./routes/_admin'))
app.use('/auth', require('./routes/auth'))
app.use('/profile', require('./routes/profile'))
app.use('/posts', require('./routes/posts'))
app.use('/comments', require('./routes/comments'))
app.use('/search', require('./routes/search'))
app.use('/users', require('./routes/users'))
require('./utils/passport')(passport)
const db = process.env.MONGO_URI
const port = process.env.PORT || 5000
mongoose
.connect(db, {
useNewUrlParser: true,
useFindAndModify: false,
useCreateIndex: true,
useUnifiedTopology: true
})
.then(() => {
console.log('MongoDB Connected') // eslint-disable-line no-console
const server = app.listen(port, () => console.log(`Server running on port ${port}`)) // eslint-disable-line no-console
const io = require('socket.io')(server)
io.on('connection', async socket => {
const decodedUser =
socket.handshake.headers.cookie && cookie.parse(socket.handshake.headers.cookie).jwtToken
? jwtDecode(cookie.parse(socket.handshake.headers.cookie).jwtToken)
: null
if (decodedUser) {
console.log(`${socket.id} -> ${decodedUser.username} -> connected`) // eslint-disable-line no-console
const user = await User.findById(decodedUser.id)
if (!user.sockets.includes(socket.id)) {
user.sockets.push(socket.id)
user.dateOnline = Date.now()
user.isOnline = true
user.save()
}
socket.on('disconnect', async () => {
console.log(`${socket.id} -> ${decodedUser.username} -> disconnected`) // eslint-disable-line no-console
const user = await User.findById(decodedUser.id)
const index = user.sockets.indexOf(socket.id)
user.sockets.splice(index, 1)
if (user.sockets.length < 1) {
user.isOnline = false
user.dateOffline = Date.now()
}
user.save()
})
} else {
console.log(`${socket.id} -> GUEST -> connected`) // eslint-disable-line no-console
socket.on('disconnect', async () => {
console.log(`${socket.id} -> GUEST -> disconnected`) // eslint-disable-line no-console
})
}
})
})
.catch(err => console.log(err)) // eslint-disable-line no-console
Client React Context:
import React, { createContext, useContext, useEffect, useState } from 'react'
import PropTypes from 'prop-types'
import io from 'socket.io-client'
const SocketContext = createContext()
export function useSocket() {
return useContext(SocketContext)
}
export function SocketContextProvider({ children }) {
const [socket, setSocket] = useState(null)
useEffect(() => {
setSocket(io(process.env.NOIZE_APP_SERVER_URL))
}, [])
const defaultContext = { socket }
return <SocketContext.Provider value={defaultContext}>{children}</SocketContext.Provider>
}
SocketContextProvider.propTypes = {
children: PropTypes.node
}
The hole React app is wrapped is this context-provider and as i said, on my localhost everything works fine.
The problem on the Heroku server is, that it is not receiving the cookie with my bearer jwt token from client in the SocketIO handshake. I’m lost right now and hope for help/hints/and so on…
Thank you very much =)

I solved the problem!
cookies.set('jwtToken', jwtToken, {
path: '/',
domain: process.env.NODE_ENV === 'development' ? 'localhost' : 'example.com'
})
The cookie needs the domain attribute in this case because the server runs on api.example.com and the client on www.example.com.

Related

Socket.io server not runnning

i'm developing a chat app in express by socket.io and this is my code:
well the front end cannot connect to socket io but server is running and i can login
const express = require("express");
const { Server } = require("socket.io");
const helmet = require("helmet");
const cors = require("cors");
const authRouter = require("./routes/authRouter");
const { corsConfig } = require("./controllers/serverController");
const {
Authorization,
AddFriend,
Disconnect,
dm,
} = require("./controllers/socketController");
require("dotenv").config();
const app = express();
const server = require("http").createServer(app);
const io = new Server(server, {
cors: corsConfig,
});
app.use(helmet());
app.use(cors(corsConfig));
app.use(express.json());
//! Routes
app.use("/auth", authRouter);
app.get("/", (req, res) => res.send("Hi"));
io.use(Authorization);
io.on("connection", (socket) => {
console.log("socket")
socket.on("add_friend", (friendName, cb) => {
AddFriend(socket, friendName, cb);
});
socket.on("disconnect", Disconnect);
socket.on("dm", (message) => dm(socket, message));
});
server.listen(5050, () => {
console.log(app.get("env"));
});
but my server isnt running on localhost:5050 this is the error i got :
curl "http://localhost:5050/socket.io/?EIO=4&transport=polling" curl: (7) Failed to connect to localhost port 5050: Connection refused
ive tried to change config of socket server but none of those worked
i have a socketjs file which will create my config and a useEffect hook which inside that ive tried to connect to my server
frontendCode:
socket.js
import { io } from "socket.io-client";
const socket = (user) =>
new io("http://localhost:5050", {
autoConnect: false,
withCredentials: true,
auth: {
token: user.token,
},
});
export default socket;
useSocket.js:
socket.connect();
socket.on("friends", (FriendList) => {
setFriendList(FriendList);
});
socket.on("messages", (messages) => {
setMessages(messages);
});
socket.on("dm", (message) => {
setMessages((prev) => [message, ...prev]);
});
socket.on("connected", (status, username) => {
setFriendList((prev) => {
const friends = [...prev];
return friends.map((friend) => {
if (friend.username === username) {
friend.status = status;
}
return friend;
});
});
});
socket.on("connect_error", () => {
setUser({ loggedIn: false });
});
returned error from frontend:
Firefox can’t establish a connection to the server at ws://localhost:5050/socket.io/?EIO=4&transport=websocket&sid=1uKM4znamAHH8P6kAAKY.

400 error socket.io when trying to connect from client side

I am setting up a little chat feature with socket.io
for some reason when I try to connect from client I am getting this error
I am initiating my connection in a chatProvider component in a react app.
Here is the client side connection function
import React, { useState, useEffect} from 'react';
import io from "socket.io-client";
const ChatProvider = ({ children }) => {
useEffect(() => {
const socket = io('http://localhost:5000');
socket.on('messageFromServer', (dataFromServer) => {
console.log(dataFromServer);
socket.emit('messageToServer', {data: "this is from client"})
})
}, [])
return <ChatContext.Provider value={value}>{children}</ChatContext.Provider>;
};
ChatProvider.propTypes = { children: PropTypes.node.isRequired };
export default ChatProvider;
Here is my backend
const express = require('express');
const mongoose = require('mongoose');
const cookieSession = require('cookie-session');
const passport = require('passport');
const bodyParser = require('body-parser') // when a post request comes in to express, it doesnt automatically parse, you need to call body-parser
const keys = require('./config/keys');
const cors = require('cors');
const { setIO } = require('./helpers/socket-setup');
require("dotenv").config();
require('./models/GoogleUserModel'); // the user model must be placed before this services passport// this must be ran after requiring model bcuz this needs the model. ORDER
require('./models/UserModel');
require('./services/passport');
const app = express()
const server = require('http').createServer(app)
const corsOptions = {
origin:"http://localhost:3000",
credentials: true, //access-control-allow-credentials:true
optionSuccessStatus:200
}
app.use(cors(corsOptions))
mongoose.Promise = global.Promise;
mongoose.connect(keys.mongoURI, {
useNewUrlParser: true,
useCreateIndex: true,
useUnifiedTopology: true
})
mongoose.connection.on('error', () => {
throw new Error (`unable to connect to database: ${keys.mongoURI}`)
});
app.use(bodyParser.json({limit: '10mb'}))
app.use(express.urlencoded( { extended: true }))
app.use(
cookieSession({
maxAge: 30 * 24 * 60 * 60 * 1000,
keys: [keys.cookieKey]
})
)
app.use(passport.initialize());
app.use(passport.session());
require('./routes/webhookRoutes')(app)
require('./routes/userRoutes')(app);
let io = setIO(server)
io.on("connection", socket => {
socket.emit('messageFromServer', { data: 'this is from server' });
socket.on('messageToServer', ( dataFromClient )=> {
console.log(dataFromClient);
})
})
const PORT = process.env.PORT || 5000;
server.listen(PORT);
Here is a helper file socket-setup.js I made so I can use socket.io functions within other files, could this be the issue?
const socket = require("socket.io")
let _io;
const setIO = (server) => {
_io = socket(server, {
cors : {
origin:"http://localhost:3000",
credentials: true, //access-control-allow-credentials:true
optionSuccessStatus:200
}
})
return _io
}
const getIO = () => {
return _io
}
module.exports = {
getIO,
setIO
}

Reciving empty file objects from frontend to backend React and NodeJS

Hi I'm trying to upload a file to a server send with axios. To send it I use react with Hooks and UseState, the thing is that when I do the console.log of the file in de frontend it shows all correctly but when I send it to backend I recive it empty.
Here is an example about what shows the frontend with console.log():
Here is the function I use to send the 3 files to backend and the differents things like react Hooks and that which I need:
const [weight, setWeight] = useState("");
const [frontPhoto, setFrontPhoto] = useState({});
const [sidePhoto, setSidePhoto] = useState({});
const [backPhoto, setBackPhoto] = useState({});
const JWT = new ClassJWT();
const axiosReq = axios.create();
const [uploadErrors, setUploadErrors] = useState([{}]);
const upload = async (e) => {
e.preventDefault();
await JWT.checkJWT();
console.log(frontPhoto);
axiosReq.post("http://localhost:3001/upload-progress", {
weight,
frontPhoto,
sidePhoto,
backPhoto,
token: JWT.getToken()
}).then((response) => {
console.log(response);
if (response.data.statusCode === '200') {
} else {
}
});
};
And then, in the backend de console.log() is like this:
{
weight: '70',
frontPhoto: {},
sidePhoto: {},
backPhoto: {},
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiaWF0IjoxNjI2NTk3Mjg1LCJleHAiOjE2MjY1OTgxODV9.njDz7BZX57NvAK399abQLhoelpTS4kStj4LBzjw5gR8'
}
Here is the router code I use to this upload:
routerProgress.post("/upload-progress", verifyJWT, async (req, res) => {
console.log(req.body);
}
And here is all the server configuration:
import express from 'express';
import sequelize from './db/db.js';
import cors from 'cors';
import fileUpload from 'express-fileupload';
// SERVER CONFIGURATION
const app = express();
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
console.log(`Listening at ${PORT}`);
sequelize.sync({ force: false })
.then(() => console.log('Database Connected!'))
.catch(err => console.log(err));
});
// BODYPARSER
app.use(express.json({limit: '50mb'}));
app.use(express.urlencoded({ limit: '50mb', extended: true, parameterLimit: 50000}));
app.use(cors({
origin: ["http://localhost:3000"],
methods: ["GET", "POST"],
credentials: true
}));
app.use(fileUpload({
createParentPath: true
}));
// ROUTES
import { routerAuthentication } from './routes/authentication.js';
import { routerProgress } from './routes/progress.js';
app.use(routerAuthentication);
app.use(routerProgress);
I don't know how to solve it, I tried many things but anything doesn't word. Please if anyone know what can I do to solve it, I'll be very grateful with him. Thanks!

socket.io problems. Not able to connect. front end says connection: false and backend doesn't log anything

i am new to socket.io and i can't get it to connect to react app. here is my app.js in node
const express = require('express');
const port = process.env.PORT || 4000;
const router = require('./routes/routes');
const cors = require('cors');
const app = express();
const bodyParser = require('body-parser');
const db = require('./db/db');
const server = require('http').createServer(app);
const io = require('socket.io')(server);
io.on('connection', () => {
console.log('connected');
});
app.use('*', cors());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
(router);
app.listen(port, () => {
console.log('listening on port ' + port);
db.sync({
// force: true,
logging: false,
});
});
and my front end code.
import React, { useState, useEffect, useRef } from 'react';
import { io } from 'socket.io-client';
import classes from './Chatroom.module.css';
const Chatroom = ({ user, getAllMessages, setGetAllMessages }) => {
const ENDPOINT = 'http://localhost:4000/getallmessages';
var socket = io(ENDPOINT);
const messagesEndRef = useRef(null);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
useEffect(() => {
socket.on('connect', () => {
socket.send('hello');
console.log('connected.');
});
console.log(socket);
}, []);
Whenever i look in the console on it shows connected: false and nothing is logging on the backend.
In order to fix the issue i had to add options to my io declaration as follows.
const server = require('http').createServer(app);
const options = {
cors: true,
origins: ['http://127.0.0.1:3000'],
};
const io = require('socket.io')(server, options);
127.0.0.1 being home and on client side my server is on 3000 so that's where that comes from. and on the client side you were right i had to remove "getallmessages" route so now it is as follows.
onst ENDPOINT = 'http://localhost:4000/';
var socket = io(ENDPOINT);
const messagesEndRef = useRef(null);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
useEffect(() => {
socket.on('connect', () => {
socket.send('hello');
console.log('connected.');
});
console.log(socket);
}, []);
socket.io is bound to the server object so you should listen to the server instead of the app.
Change app.listen to server.listen
Change endpoint by removing getallmessages if you are not using namespaces

Unable to link backend API routes - NextJS

I am making a shopify app and have created a Koa + NodeJS backend and NextJS running in frontend, in the same port, and also I have created a custom server.js file When running locally in my PC, its running all right, all the routes work as expected, and I am able to fetch data from Frontend (React) from the routes defined in my backend (Koa).
Now the problem is that, when I deploy my app to vercel it doesn't seem to be recognizing my Koa routes, and is throwing a 404 error in the console for all the requests I make to the backend routes. This is my first time working with NextJS, so I have really very little idea on what is wrong here, so I would like some support on this please.
Also when I deploy it, the shopify auth also doesn't seem to be working anymore like it does when its running in local development.
My Code:
server.js:
require('isomorphic-fetch');
const dotenv = require('dotenv');
dotenv.config();
const Koa = require('koa');
const next = require('next');
const { default: createShopifyAuth } = require('#shopify/koa-shopify-auth');
const { verifyRequest } = require('#shopify/koa-shopify-auth');
const session = require('koa-session');
const { default: graphQLProxy } = require('#shopify/koa-shopify-graphql-proxy');
const { ApiVersion } = require('#shopify/koa-shopify-graphql-proxy');
const Router = require('koa-router');
const { receiveWebhook, registerWebhook } = require('#shopify/koa-shopify-webhooks');
const getSubscriptionUrl = require('./server/getSubscriptionUrl');
const port = parseInt(process.env.PORT, 10) || 3000;
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
const {
SHOPIFY_API_SECRET_KEY,
SHOPIFY_API_KEY,
HOST,
} = process.env;
app.prepare().then(() => {
const server = new Koa();
const router = new Router();
server.use(session({ sameSite: 'none', secure: true }, server));
server.keys = [SHOPIFY_API_SECRET_KEY];
server.use(
createShopifyAuth({
apiKey: SHOPIFY_API_KEY,
secret: SHOPIFY_API_SECRET_KEY,
scopes: ['read_products', 'write_products'],
async afterAuth(ctx) {
const { shop, accessToken } = ctx.session;
ctx.cookies.set("shopOrigin", shop, {
httpOnly: false,
secure: true,
sameSite: 'none'
});
const registration = await registerWebhook({
address: `${HOST}/webhooks/products/create`,
topic: 'PRODUCTS_CREATE',
accessToken,
shop,
apiVersion: ApiVersion.October19
});
if (registration.success) {
console.log('Successfully registered webhook!');
} else {
console.log('Failed to register webhook', registration.result);
}
await getSubscriptionUrl(ctx, accessToken, shop);
}
})
);
router
.get('/api', ctx => {
ctx.res.statusCode = 200;
ctx.body = "API Route"
})
const webhook = receiveWebhook({ secret: SHOPIFY_API_SECRET_KEY });
router.post('/webhooks/products/create', webhook, (ctx) => {
console.log('received webhook: ', ctx.state.webhook);
});
server.use(graphQLProxy({ version: ApiVersion.April19 }));
router.get('*', verifyRequest(), async (ctx) => {
await handle(ctx.req, ctx.res);
ctx.respond = false;
ctx.res.statusCode = 200;
});
server.use(router.allowedMethods());
server.use(router.routes());
server.listen(port, () => {
console.log(`> Ready on http://localhost:${port}`);
});
});
I found out that Vercel isn't supporting custom servers anymore, if I right. from this https://github.com/vercel/next.js/issues/9397#issuecomment-556215227
So, I used Heroku to deploy my app instead, and now it is working all right.

Resources