How to use koa.js + next in Firebase Functions - node.js

I want to use Cloud Functions for Firebase to deploy the React application for shopify app.
I am new to both Next and Koa.
Based on this repo the below code is how to host a simple react application in Firebase.
const path = require('path')
const functions = require('firebase-functions')
const next = require('next')
var dev = process.env.NODE_ENV !== 'production'
var app = next({
dev,
conf: { distDir: `${path.relative(process.cwd(), __dirname)}/next` }
})
var handle = app.getRequestHandler()
exports.next = functions.https.onRequest((req, res) => {
console.log('File: ' + req.originalUrl) // log the page.js file that is being requested
return app.prepare().then(() => handle(req, res))
})
Which works correctly, no issue.
Then based on this tutorial from shopify I need to integrate koa and other dependencies in server.js, which in my case I believe it should be placed inside the firebase function. so I get to this code
const path = require('path')
const isomorphicFetch = require('isomorphic-fetch');
const Koa = require('koa');
const functions = require('firebase-functions')
const next = require('next');
const ShopifyConfig = require('./shopify.js');
const { default: createShopifyAuth } = require('#shopify/koa-shopify-auth');
const dotenv = require('dotenv');
const { verifyRequest } = require('#shopify/koa-shopify-auth');
const session = require('koa-session');
dotenv.config();
const port = parseInt(process.env.PORT, 10) || 3000;
var dev = process.env.NODE_ENV !== 'production'
var app = next({
dev,
conf: { distDir: `${path.relative(process.cwd(), __dirname)}/next` }
})
var handle = app.getRequestHandler()
const server = new Koa();
server.use(session(server));
server.keys = [ShopifyConfig.secretKey];
server.use(
createShopifyAuth({
apiKey: ShopifyConfig.key,
secret: ShopifyConfig.secretKey,
scopes: [],
afterAuth(ctx) {
const { shop, accessToken } = ctx.session;
ctx.redirect('/');
},
}),
);
server.use(verifyRequest());
server.use(async (ctx) => {
await handle(ctx.req, ctx.res);
ctx.respond = false;
ctx.res.statusCode = 200;
});
exports.next = functions.https.onRequest((req, res) => {
console.log('File: ' + req.originalUrl) //
// This is old code
// return app.prepare().then(() => {
// handle(req, res);
// })
// I tried this #1
// server.callback(req, res);
})
// I tried this #2
// exports.next = functions.https.onRequest(server.callback);
// I tried this #3
// exports.next = functions.https.onRequest(server.callback());
// I tried this #4
exports.next = functions.https.onRequest((req, res) => {
console.log('File: ' + req.originalUrl)
return app.prepare().then(() => {
server.callback(req, res);
//handle(req, res);
})
})
My question is now based on Koa what code should be in functions.https.onRequest ? please note there is no code to listen to port since it doesn't make sense for firebase functions.
I tried #1, #2, #3, as well as this post
1 -> I get request timeout
2 -> I get request timeout
3 -> I get "Cannot access middleware of undefined"
4 -> I get request timeout

Thanks to kvindasAB
server.callback is not the callback by itself, rather a function that generates the callback from your configuration I assumed.
so code needs to be changed to
server.callback()(req, res);

const functions = require('firebase-functions');
const app = new Koa();
...
exports['http'] = functions.https.onRequest(app.callback());

Related

Node.js server return error 404 in production

hope you are good,
I am building my next.js weather app project using openweather API, I set up my proxy server, so the API key won't appear on the client side, it works perfect on my localhost, but when I deploy it on vercel or heroku, it returns error 404, it does the same when requesting directly on postman or browser.
PS: the environment variables are set in my vercel dashboard.
PS: api url example localhost:3000/api/weather?q=london
my server.js
const next = require('next');
const port = parseInt(process.env.PORT, 10) || 3000;
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
const needle = require('needle');
const url = require('url');
const cors = require('cors');
app.prepare().then(() => {
const API_BASE_URL = process.env.API_BASE_URL;
const API_KEY_NAME = process.env.API_KEY_NAME;
const API_KEY_VALUE = process.env.API_KEY_VALUE;
const server = express();
server.use(
cors({
origin: '*',
})
);
server.get(
'/api/:stat',
async (req, res) => {
try {
const params = new URLSearchParams({
...url.parse(req.url, true).slashes,
...url.parse(req.url, true).query,
[API_KEY_NAME]: API_KEY_VALUE,
});
const apiRes = await needle(
'get',
`${API_BASE_URL}/${req.params.stat}?${params}`
);
const data = apiRes.body;
res.status(200).json(data);
} catch (error) {
res.status(500).json({ error });
}
}
);
server.all('*', (req, res) => {
return handle(req, res);
});
server.listen(port, (err) => {
if (err) throw err;
console.log(`> Ready on http://localhost:${port}`);
});
});

Error starting a Next.js application in https

I'm attempting to start a Next.js application in https mode. I'm following the instructions here. When I try to open the page, I get the following error:
TypeError: (0 , react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxDEV) is not a function
here's my code:
const { createServer } = require("https");
const { parse } = require("url");
const {next} = require("next");
const fs = require("fs");
const dotenv = require('dotenv');
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
//read the .env file
dotenv.config();
const port = process.env.PORT;
const httpsOptions = {
key: fs.readFileSync("./certificates/privateKey.key"),
cert: fs.readFileSync("./certificates/certificate.crt"),
};
app.prepare().then(() => {
createServer(httpsOptions, (req, res) => {
const parsedUrl = parse(req.url, true);
handle(req, res, parsedUrl);
}).listen(port, (err) => {
if (err) throw err;
console.log("> Server started on https://localhost:" + port);
});
});
Here's the stack trace:
TypeError: (0 , react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxDEV) is not a function
at IBOUIApp (C:\Users\mgardner\workspace\qa-tool-backoffice\.next\server\pages\_app.js:53:92)
at d (C:\Users\mgardner\workspace\qa-tool-backoffice\node_modules\react-dom\cjs\react-dom-server.node.production.min.js:33:498)
at bb (C:\Users\mgardner\workspace\qa-tool-backoffice\node_modules\react-dom\cjs\react-dom-server.node.production.min.js:36:16)
at a.b.render (C:\Users\mgardner\workspace\qa-tool-backoffice\node_modules\react-dom\cjs\react-dom-server.node.production.min.js:42:43)
at a.b.read (C:\Users\mgardner\workspace\qa-tool-backoffice\node_modules\react-dom\cjs\react-dom-server.node.production.min.js:41:83)
at exports.renderToString (C:\Users\mgardner\workspace\qa-tool-backoffice\node_modules\react-dom\cjs\react-dom-server.node.production.min.js:52:138)
at Object.renderPage (C:\Users\mgardner\workspace\qa-tool-backoffice\node_modules\next\dist\next-server\server\render.js:54:854)
at Function.getInitialProps (C:\Users\mgardner\workspace\qa-tool-backoffice\.next\server\vendors-node_modules_next_dist_pages__document_js.js:627:19)
at loadGetInitialProps (C:\Users\mgardner\workspace\qa-tool-backoffice\node_modules\next\dist\next-server\lib\utils.js:5:101)
at renderToHTML (C:\Users\mgardner\workspace\qa-tool-backoffice\node_modules\next\dist\next-server\server\render.js:54:1145)
at async C:\Users\mgardner\workspace\qa-tool-backoffice\node_modules\next\dist\next-server\server\next-server.js:112:97
at async C:\Users\mgardner\workspace\qa-tool-backoffice\node_modules\next\dist\next-server\server\next-server.js:105:142
at async DevServer.renderToHTMLWithComponents (C:\Users\mgardner\workspace\qa-tool-backoffice\node_modules\next\dist\next-server\server\next-server.js:137:387)
at async DevServer.renderToHTML (C:\Users\mgardner\workspace\qa-tool-backoffice\node_modules\next\dist\next-server\server\next-server.js:138:610)
at async DevServer.renderToHTML (C:\Users\mgardner\workspace\qa-tool-backoffice\node_modules\next\dist\server\next-dev-server.js:36:578)

Nextjs and Express as middle ware. How do I set 'localhost:3000/newpage' and 'localhost:3000/newpage/' as the same routes

I am new to express and next and was trying to set 'localhost:3000/newpage' and 'localhost:3000/newpage/' as the same route however as I add a '/' at the end it shows a 404 error.
I am using "next-routes" for dynamic routing and have created routes.js file that looks like this:
const nextRoutes = require("next-routes");
const routes = (module.exports = nextRoutes());
routes.add("index", "/");
routes.add("newpage", "/newpage/:slug"); //with body parser this doesnt work
and my server.js file looks like this:
const express = require("express");
const next = require("next");
const routes = require("./routes");
const dev = process.env.NODE_ENV !== "production";
const port = process.env.PORT || 3000;
const app = next({ dev });
const handle = app.getRequestHandler();
const bodyParser = require("body-parser");
const handler = routes.getRequestHandler(app);
app
.prepare()
.then(() => {
const server = express();
server.use(bodyParser.json()); //with this dynamic routes dont work
server.use (handler); //with this dynamic routes work but / url show 404
server.get("*", (req, res) => {
server.use(handler);
if (req.url.endsWith("/")) {
req.url = req.url.slice(0, -1); // works only when using body parser
}
return handle(req, res);
});
server.listen(port, (err) => {
if (err) throw err;
console.log("> Ready on http://localhost:3000");
});
})
.catch((ex) => {
console.error(ex.stack);
process.exit(1);
});
You can modify the url that you get before passing it to Next's handling.
const next = require('next');
const express = require('express');
const routes = require('./routes');
const port = process.env.PORT || 3000;
const dev = process.env.NODE_ENV !== 'production';
const app = next({dev});
const handle = app.getRequestHandler();
// const handler = routes.getRequestHandler(app); // redundant line
app.prepare().then(() => {
const server = express();
// server.use(handler); // <-- this line is redundant since you need only one handle!
server.get('*', (req, res) => {
if (req.url.endsWith('/')) {
req.url = req.url.slice(0, -1); // remove the last slash
}
return handle(req, res);
});
server.listen(port, (err) => {
if (err) throw err;
console.log('> Ready on http://localhost:3000');
});
});
Working example: https://codesandbox.io/s/express-nextjs-react-c47y8?file=/src/index.js
Navigate to /form or /form/
I had to install the body-parser package then used body-parser. I also changed the folder structure such that I didn't have to import the routes. The final code in server.js looks like this:
const express = require("express");
const next = require("next");
const dev = process.env.NODE_ENV !== "production";
const port = process.env.PORT || 3000;
const app = next({ dev });
const handle = app.getRequestHandler();
app
.prepare()
.then(() => {
const server = express();
server.get("*", (req, res) => {
if (req.url.endsWith("/")) {
req.url = req.url.slice(0, -1); // remove the last slash
}
return handle(req, res);
});
server.listen(port, (err) => {
if (err) throw err;
console.log("> Ready on http://localhost:3000");
});
})
.catch((ex) => {
console.error(ex.stack);
process.exit(1);
});

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.

Await for function before end()

edit: added a bit more code.
const express = require('express');
var bodyParser = require('body-parser');
const app = express();
var urlencodedParser = bodyParser.urlencoded({extended: false})
const {google} = require('googleapis');
const {PubSub} = require('#google-cloud/pubsub');
const iot = require('#google-cloud/iot');
const API_VERSION = 'v1';
const DISCOVERY_API = 'https://cloudiot.googleapis.com/$discovery/rest';
app.get('/', urlencodedParser, (req, res) => {
const projectId = req.query.proyecto;
const cloudRegion = req.query.region;
const registryId = req.query.registro;
const numSerie = req.query.numSerie;
const command = req.query.command;
const client = new iot.v1.DeviceManagerClient();
if (client === undefined) {
console.log('Did not instantiate client.');
} else {
console.log('Did instantiate client.');
sendCom();
}
async function sendCom() {
const formattedName = await client.devicePath(projectId, cloudRegion, registryId, numSerie)
const binaryData = Buffer.from(command);
const request = {
name: formattedName,
binaryData: binaryData,
};
return client.sendCommandToDevice(request).then(responses => res.status(200).send(JSON.stringify({
data: OK
}))).catch(err => res.status(404).send('Could not send command. Is the device connected?'));
}
});
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`App listening on port ${PORT}`);
console.log('Press Ctrl+C to quit.');
});
module.exports = app;
I have this function, that I call after the client initiate: sendCom();
async function sendCom() {
const formattedName = await client.devicePath(projectId, cloudRegion, registryId, deviceId)
const binaryData = Buffer.from(command);
const request = { name: formattedName, binaryData: binaryData, };
client.sendCommandToDevice(request)
.then(responses => {
res.status(200).send(JSON.stringify({ data: OK })).end();
})
.catch(err => {
res.status(404).send('Could not send command. Is the device connected?').end();
});
}
My problem is that sendCommandToDevice gets executed perfectly, however I get the catch error.
As I understand it, it's because in the .then ends the connection.
I've looked at this and thats's what I tried, however I'm not sure I understand what's going on.
You can not use send with end.
end() is used when you want to end the request and want to respond with no data.
send() is used to end the request and respond with some data.
You can found more about it here.

Resources