How can I separate out Express routes from server.js? - node.js

I have been closely trying to follow this Vue SSR guide and in that example the author has placed his Express routes within a file called server.js. Simply for organisation purposes, I wish to keep my Express routes in a router.express.js file within my src/router folder instead of in the root file of server.js.
So this is a cutdown version of my router.express.js file:
const vueServerRenderer = require('vue-server-renderer');
const setupDevServer = require('../../build/setup-dev-server'); //webpack dev config
const express = require('express');
const app = express();
const router = express.Router();
const createRenderer = (serverBundle) =>
vueServerRenderer.createBundleRenderer(serverBundle, {
runInNewContext: false,
template: fs.readFileSync(path.resolve(__dirname, '../index.html'), 'utf-8')
});
let renderer;
if (process.env.NODE_ENV === 'development') {
setupDevServer(app, (serverBundle) => {
renderer = createRenderer(serverBundle);
});
} else {
renderer = createRenderer(require('../../dist/vue-ssr-server-bundle.json'));
}
router.get('/', async function (req, res) {
const context = {
url: req.params['0'] || '/'
};
let html;
try {
html = await renderer.renderToString(context);
} catch (error) {
if (error.code === 404) {
return res.status(404).send('404 | Page Not Found');
}
return res.status(500).send('500 | Internal Server Error');
}
res.end(html);
});
module.exports = router;
The problem is that I also need the vue-server-renderer code to be in the server.js file. I would then make the app require the router.express.js file so the Express routes work like this:
const vueServerRenderer = require('vue-server-renderer');
const setupDevServer = require('../../build/setup-dev-server'); //webpack dev config
const app = express();
const createRenderer = (serverBundle) =>
vueServerRenderer.createBundleRenderer(serverBundle, {
runInNewContext: false,
template: fs.readFileSync(path.resolve(__dirname, '../index.html'), 'utf-8')
});
let renderer;
if (process.env.NODE_ENV === 'development') {
setupDevServer(app, (serverBundle) => {
renderer = createRenderer(serverBundle);
});
} else {
renderer = createRenderer(require('../../dist/vue-ssr-server-bundle.json'));
}
app.use(require('./router/express.router.js'));
Whenever I do this, I get a Webpack error stating that
WebpackOptionsValidationError: Invalid configuration object. Webpack
has been initialised using a configuration object that does not match
the API schema.
- configuration.entry'app' should be a string.
If I remove the vue-server-renderer code from server.js then it runs fine. But the reason for having that code within server.js is so that the development environment works properly. Basically if the code is not in server.js then I cannot use hot-reloading or anything.
If I get rid of router.express.js and put all that code in server.js including the routes, then it all works perfectly including my development environment.
Why can I not (or rather how can I) keep my Express routes in a separate file and still have the vue-server-renderer stuff work?
Update: setup-dev-server.js file:
const setupDevServer = (app, onServerBundleReady) => {
const webpack = require('webpack');
const MFS = require('memory-fs');
const path = require('path');
const clientConfig = require('./webpack.client.config');
const serverConfig = require('./webpack.ssr.config');
// additional client entry for hot reload
clientConfig.entry.app = ['webpack-hot-middleware/client', clientConfig.entry.app];
const clientCompiler = webpack(clientConfig);
// setup dev middleware
app.use(require('webpack-dev-middleware')(clientCompiler, {
publicPath: clientConfig.output.publicPath,
serverSideRender: true,
logLevel: 'silent',
}));
// setup hot middleware
app.use(require('webpack-hot-middleware')(clientCompiler));
// watch src files and rebuild SSR bundle
global.console.log('Building SSR bundle...');
const serverCompiler = webpack(serverConfig);
const mfs = new MFS();
serverCompiler.outputFileSystem = mfs;
serverCompiler.watch({}, (error, stats) => {
if (error) throw error;
global.console.log(
`${stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false,
})}\n\n`
);
if (stats.hasErrors()) {
console.error(stats.compilation.errors);
throw new Error(stats.compilation.errors);
}
// read bundle generated by vue-ssr-webpack-plugin
const bundle = JSON.parse(
mfs.readFileSync(path.join(clientConfig.output.path, 'vue-ssr-server-bundle.json'), 'utf-8')
);
onServerBundleReady(bundle);
});
};
module.exports = setupDevServer;

1) Inside of your 'router.express.js' file write: module.exports = router; at the bottom of the file.
2) Inside of your 'server.js' file write: const router = require('./router/express.router.js');
at the top of the file.
3) And now where you used to have app.use(require('./router/express.router.js'));
replace that with app.use(router);
4) At the top of 'server.js' write const vueSsrBundle = require('../../dist/vue-ssr-server-bundle.json')
5) Lastly replace renderer = createRenderer(require('../../dist/vue-ssr-server-bundle.json')
With renderer = createRenderer(vueSsrBundle)
EDIT the issue you are facing is on this line And probably related to the file 'app.js' or 'client-entry-js'

Related

request entity too large error when using oas3-tools package,openapi 3.0 in node.js

index.js file
'use strict';
var path = require('path');
var http = require('http');
var cors = require('cors');
var oas3Tools = require('oas3-tools');
require("dotenv").config({ path: ".env" });
console.log(`### Running on ~~~ ${process.env.Instance} ~~~ ENV ###`);
var serverPort = 8080;
function validate(request, scopes, schema) {
// security stuff here
return true;
}
// swaggerRouter configuration
var options = {
routing: {
controllers: path.join(__dirname, './controllers')
},
logging: {
format: 'combined',
errorLimit: 400
},
};
var expressAppConfig = oas3Tools.expressAppConfig(
path.join(__dirname, "./api/openapi.yaml"),
options
);
expressAppConfig.addValidator();
var app = expressAppConfig.getApp();
app.use(cors());
var json2xls = require('json2xls');
app.use(json2xls.middleware);
// Initialize the Swagger middleware
http.createServer(app).listen(serverPort, function () {
});
// Connect to database
var mongo = require("./utils/db");
----------------------------------------EOF----------------------------------------------
PFB the version of the tools which we've used.
"express": "^4.17.1",
"oas3-tools": "2.1.3"
I've tried body-parser and app.use(express.json({limit:'25mb'})).
But nothing resolved the request entity too large error.
It would be great if anyone suggest me any different solution.
This works for me:
app._router.stack = app._router.stack.filter((i) => i.name !== 'jsonParser');
app.use(bodyParser.json({ limit: 5 * 1024 * 1024 }));
You can see all the middleware in app._router.stack which is populated by expressAppConfig function (express.app.config.js).
I found out when you use bodyParser.json(), it will add another middleware layer to that stack - but from my observation, express will use the first one. That's why I removed all jsonParser before setting the limit.

node + Swagger behind nginx proxy_pass

I'm trying to get node and swagger to work with nginx dynamically
server_name ~^backend(?<PORTSERVER>[^.]+)\.domain\.com$;
location /swagger
{
proxy_pass http://127.0.0.1:$PORTSERVER/swagger/;
}
location /api
{
proxy_pass http://127.0.0.1:$PORTSERVER/api;
}
this is an example of virtual host the PORTSERVER variable is taking from gitlab-ci it takes id number of merge request + 2000
when i put the port directly in place of $PORTSEVER every thing is working swagger and api
any advice is appreciated thank you
this is index.js file
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
require("reflect-metadata");
const typeorm_1 = require("typeorm");
const express = require("express");
// var router = express.Router();
const fileUpload = require("express-fileupload");
const bodyParser = require("body-parser");
const routes_1 = require("./routes");
const cors = require("cors");
const typeorm_pagination_1 = require("typeorm-pagination");
const swaggerUi = require('swagger-ui-express');
const swaggerDocument = require('../../swagger.json');
var path = require('path');
typeorm_1.createConnection()
.then((connection) => __awaiter(void 0, void 0, void 0, function* () {
// create express app
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded());
app.use(fileUpload());
app.use(express.static(path.join(__dirname, '..', 'public')));
console.log(path.join(__dirname, '..', 'public'));
// register express routes from defined application routes
routes_1.Routes.forEach((route) => {
app[route.method]('/api' + route.route, (req, res, next) => {
const result = new route.controller()[route.action](req, res, next);
res.header('Access-Control-Allow-Origin', '*');
if (result instanceof Promise) {
result.then((result) => (result !== null && result !== undefined ? res.send(result) : undefined));
}
else if (result !== null && result !== undefined) {
res.json(result);
}
});
});
// setup express app here
// ...
app.use('/swagger', swaggerUi.serve, swaggerUi.setup(swaggerDocument));
app.use(express.json());
app.use(cors());
app.use(typeorm_pagination_1.pagination); // Register the pagination middleware
// start express server
// app.listen(process.env.SERVER_Port);
app.listen(process.env.PORTSERVER);
console.log('Express server has started on port ' + process.env.PORTSERVER);
}))
.catch((error) => console.log(error));
//# sourceMappingURL=index.js.map
The key is his sentence is "when i put the port directly in place of $PORTSEVER every thing is working swagger and api"
Based on the description you gave, I think that gitlabci is miss generating the port number, or miss understanding the syntax.. Both gitlabci and nginx uses $VAR syntax.. Can be a miss interpretation of the 1st line regex too..
Also, I think you need to check the content of process.env.PORTSERVERa used in the js file.. It can have different port than nginx..
For this, I would approach the issue by preventing the the job from restarting nginx to not cause down time for other vhosts.. Deploy a broken config then from the server I run nginx -t and/or diff -u a working config and a broken one..
The 1st source of truth would be nginx -t and nginx logs.. If, ever, nginx manage the starts, the HTTP code it's returning can reveal more paths to pursuit.
One thing you forgot to share is the content of your gitlabci YML.. That can help identify the issue too.

Embedded Custom NextJS App on Shopify Storefront changes asset file paths

I'm wondering if anyone could help me. I have built a custom NextJS app on Heroku intended to be embedded on my Shopify storefront using Shopify's app proxy. It's mostly working, so it is showing my app when I go to the proxy url within my Shopify store however all of the CSS & JS filepaths have been changed to my Shopify URL rather than remaining as my Heroku app URL.
I have seen someone else have this exact same problem: https://community.shopify.com/c/Shopify-APIs-SDKs/App-Proxy-Sending-index-html-throws-404-on-the-JS-and-CSS-files/td-p/586595
However when I try to implement this solution it keeps breaking the app. I'm relatively new to Node.js/Next.js so while I'm still learning I largely depend on documentation to follow to set up or resolve this type of issue. However, I cannot find any tutorials or documentation specific to this issue.
Is anyone able to advise what I need to do within my server.js to make the '/_next/static/...' files remain to be pulled from my Heroku app URL rather than the Shopify URL when being viewed from the proxy URL? I have included the contents of my server.js file below.
require('isomorphic-fetch');
const dotenv = require('dotenv');
const Koa = require('koa');
const next = require('next');
const { default: createShopifyAuth } = require('#shopify/koa-shopify-auth');
const { verifyRequest } = require('#shopify/koa-shopify-auth');
const { default: Shopify, ApiVersion } = require('#shopify/shopify-api');
const Router = require('koa-router');
dotenv.config();
Shopify.Context.initialize({
API_KEY: process.env.SHOPIFY_API_KEY,
API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
SCOPES: process.env.SHOPIFY_API_SCOPES.split(","),
HOST_NAME: process.env.SHOPIFY_APP_URL.replace(/https:\/\//, ""),
API_VERSION: ApiVersion.October20,
IS_EMBEDDED_APP: true,
SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
});
const port = parseInt(process.env.PORT, 10) || 3000;
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
const ACTIVE_SHOPIFY_SHOPS = {};
app.prepare().then(() => {
const server = new Koa();
const router = new Router();
server.keys = [Shopify.Context.API_SECRET_KEY];
server.use(
createShopifyAuth({
apiKey: process.env.SHOPIFY_API_KEY,
secret: process.env.SHOPIFY_API_SECRET,
scopes: ["read_products"],
afterAuth(ctx) {
const { shop, scope } = ctx.state.shopify;
ACTIVE_SHOPIFY_SHOPS[shop] = scope;
ctx.redirect(`/?shop=${shop}`);
},
}),
);
const handleRequest = async (ctx) => {
console.log('ctx.req.url', ctx.req.host);
await handle(ctx.req, ctx.res);
ctx.respond = false;
ctx.res.statusCode = 200;
};
router.get("/", async (ctx) => {
const shop = ctx.query.shop;
ACTIVE_SHOPIFY_SHOPS[shop] = ctx.query.shop;
if (ACTIVE_SHOPIFY_SHOPS[shop] === undefined) {
ctx.redirect(`/auth?shop=${shop}`);
} else {
await handleRequest(ctx);
}
});
router.get("/_next/*", handleRequest);
router.get("(.*)", verifyRequest(), handleRequest);
server.use(router.allowedMethods());
server.use(router.routes());
server.listen(port, () => {
console.log(`> Ready on http://localhost:${port}`);
});
});
Many thanks
A Proxy is simply a callback to your App, where as a response you provide either Liquid, or JSON. If you are trying to return links to assets such as CSS or JS, links are just strings. So can just send them as JSON key:value pairs.
A simple workaround is to use assetPrefix on next.config.js.
The assetPrefix will be prefixed for the /_next/ path automatically, so the assets are refered with absolute URL.
const isProd = process.env.NODE_ENV === 'production'
module.exports = {
assetPrefix: isProd ? 'https://yourappdomain.com' : '',
}
https://nextjs.org/docs/api-reference/next.config.js/cdn-support-with-asset-prefix

Not getting expected results from included files in Nodejs

I'm not getting expected results by including files in Nodejs. Here is my code:
Service Route File
const express = require('express');
const router = express.Router();
const path = require('path');
const config = require('../config');
const serviceAdapter = require('./serviceAdapter');
module.exports = (preRequestPath, serviceBaseUrl) => {
console.log("On server start", preRequestPath)
router.post('/*', (req, res) => {
console.log("On request", preRequestPath)
const axiosHttp = serviceAdapter(serviceBaseUrl);
axiosHttp.post(preRequestPath+req.path, req.body).then(resp => {
res.send(resp.data)
}).catch(err => {
res.status(404).sendFile(path.join(__dirname + '/../404.html'));
});
});
return router;
}
Main Server File
const express = require('express');
const userApiService = require('./routes/userService');
const userAdminService = require('./routes/userService');
app.use('/api/user_service/', userApiService("/api", config.userServiceUrl) );
app.use('/admin/user_service/', userAdminService("/admin", config.userServiceUrl) );
var server = app.listen(3000, function(){
console.log('Server listening on port 3000');
});
module.exports = server;
Expecting Console Result:
On server start /api
On server start /admin
On request /api (when hitting http://baseurl.com/api/<anything>)
On request /admin (when hitting http://baseurl.com/admin/<anything>)
But Getting Console Output as:
On server start /api
On server start /admin
On request /api (when hitting http://baseurl.com/api/<anything>)
On request /api (when hitting http://baseurl.com/admin/<anything>)
Both the time, returning /api path.
Can anyone tell me why is it happening and what's the solution?
You're creating only one router in userService.js (the first file). It's created once before the function so you really only end up with one router. The first time you require it the router gets created, but the second time you require it Node knows it was already loaded and it's not re-initialized. You should be creating a different router for each case like this:
const express = require('express');
// const router = express.Router(); <-- don't do it here
const path = require('path');
const config = require('../config');
const serviceAdapter = require('./serviceAdapter');
module.exports = (preRequestPath, serviceBaseUrl) => {
const router = express.Router(); // <--- create a new router for each case
console.log("On server start", preRequestPath)
router.post('/*', (req, res) => {
console.log("On request", preRequestPath)
const axiosHttp = serviceAdapter(serviceBaseUrl);
axiosHttp.post(preRequestPath+req.path, req.body).then(resp => {
res.send(resp.data)
}).catch(err => {
res.status(404).sendFile(path.join(__dirname + '/../404.html'));
});
});
return router;
}
Also in your main server file you only need to require it once. It's just a function to create the service so you don't need 2 different variables holding that function. So you can initialize both using the one function like this:
// const userApiService = require('./routes/userService');
// const userAdminService = require('./routes/userService');
const service = require('./routes/userService');
app.use('/api/user_service/', service("/api", config.userServiceUrl) );
app.use('/admin/user_service/', service("/admin", config.userServiceUrl) );

Dynamically load routes with express.js

I am using express.js as a webserver and would like an easy way to separate all the "app.get" and "app.post" functions to separate files. For example, if I would like to specify get and post functions for a login page, I would like to have a login.js file in a routes folder that is dynamically loaded (will automatically add all of the files without having to specify each one) when I run node app.js
I have tried this this solution!, but it isn't working for me.
app.js
var express=require("express");
var app=express();
var fs=require("fs");
var routePath="./routers/"; //add one folder then put your route files there my router folder name is routers
fs.readdirSync(routePath).forEach(function(file) {
var route=routePath+file;
require(route)(app);
});
app.listen(9123);
I have put below two routers in that folder
route1.js
module.exports=function(app){
app.get('/',function(req,res){
res.send('/ called successfully...');
});
}
route2.js
module.exports=function(app){
app.get('/upload',function(req,res){
res.send('/upload called successfully...');
});
}
Typescript
routes/testroute.ts
import { Router } from 'express';
const router = Router();
router.get('/test',() => {
// Do your stuffs Here
});
export = router;
index.ts
let app = express()
const routePath = path.join(__dirname, 'routes');
fs.readdirSync(routePath).forEach(async (filename) => {
let route = path.join(routePath, filename);
try {
const item = await import(route);
app.use('/api', item.default);
} catch (error) {
console.log(error.message);
}
});
app.listen()
I ended up using a recursive approach to keep the code readable and asynchronous:
// routes
processRoutePath(__dirname + "/routes");
function processRoutePath(route_path) {
fs.readdirSync(route_path).forEach(function(file) {
var filepath = route_path + '/' + file;
fs.stat(filepath, function(err,stat) {
if (stat.isDirectory()) {
processRoutePath(filepath);
} else {
console.info('Loading route: ' + filepath);
require(filepath)(app, passport);
}
});
});
}
This could be made more robust by checking fro correct file extensions etc, but I keep my routes folder clean and did not want the added complexity
With this approach, there is no need to write routes manually. Just setup a directory structure like the URL paths. Example route is at /routes/user/table/table.get.js and API route will be /user/table.
import app from './app'
import fs from 'fs-readdir-recursive'
import each from 'lodash/each'
import nth from 'lodash/nth'
import join from 'lodash/join'
import initial from 'lodash/initial'
const routes = fs(`${__dirname}/routes`)
each(routes, route => {
let paths = route.split('/')
// An entity has several HTTP verbs
let entity = `/api/${join(initial(paths), '/')}`
// The action contains a HTTP verb
let action = nth(paths, -1)
// Remove the last element to correctly apply action
paths.pop()
action = `./routes/${join(paths, '/')}/${action.slice(0, -3)}`
app.use(entity, require(action))
})
Example route:
import { Router } from 'express'
import Table from '#models/table.model'
const routes = Router()
routes.get('/', (req, res, next) => {
Table
.find({user: userIdentifier})
.select('-user')
.lean()
.then(table => res.json(table))
.catch(error => next(error))
})
module.exports = routes

Resources