I have a react app, which uses a router and works fine if the URL is not entered directly into the address bar, but rather is set by react router when navigating the app. I have added ExpressJS to the app, by running the react app via the Express proxy. The goal is to re-direct any dynamic requests to the react app, so that it can handle it, instead of Express. I have looked at many examples and tried using them, but I had no luck in making the re-direct work.
The folder structure and key file locations are as follows:
[Root directory]
---- [client]
--------- [node_modules]
--------- [public]
--------- [scss]
--------- [src]
--------- package.json
--------- webpack.config.js
---- [server]
--------- index.js
---- [node_modules]
---- package.json
The "client" directory contains the react app. The "server" directory contains the Express app. The Express app's package.json is in the root directory, while react app's package.json is in the "client" directory.
server/index.js
const express = require('express');
const path = require('path');
const PORT = process.env.PORT || 5000;
const app = express();
// Serve static files
app.use(express.static(path.resolve(__dirname, '../client/public')));
app.get('*', function(request, response) {
response.sendFile(path.resolve(__dirname, '../client/public', 'index.html'));
});
app.listen(PORT, function () {
console.error(`Listening on port ${PORT}`);
});
client/package.json
Contains "proxy": "http://localhost:5000"
client/webpack.config.js
Contains the following for devServer, entry, and output (I excluded various loaders as irrelevant for URL processing):
const BUILD_DIR = path.resolve(__dirname, 'build');
const SRC_DIR = path.resolve(__dirname, 'src');
module.exports = (env = {}) => {
return {
entry: {
index: [SRC_DIR + '/index.js']
},
output: {
path: BUILD_DIR,
filename: '[name].bundle.js'
},
devtool: env.prod ? 'source-map' : 'cheap-module-eval-source-map',
devServer: {
contentBase: BUILD_DIR,
compress: true,
hot: true,
open: true,
historyApiFallback: true
}
}
};
client/src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, combineReducers, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import createHistory from 'history/createBrowserHistory';
import { Route } from 'react-router';
import { ConnectedRouter, routerReducer, routerMiddleware, push } from 'react-router-redux';
import rootReducer from './reducers';
// Wrapper
import SiteWrapper from './wrappers/SiteWrapper/'
// Pages
import Error404 from './ErrorPages/Error404/'
const history = createHistory();
const middleware = routerMiddleware(history);
const store = createStore(
combineReducers({
rootReducer,
router: routerReducer
}),
applyMiddleware(middleware)
)
ReactDOM.render((
<Provider store={store}>
<ConnectedRouter history={history}>
<div>
<Route exact path="/404" name="Error 404" component={Error404}/>
<Route path="/" name="Home" component={SiteWrapper}/>
</div>
</ConnectedRouter>
</Provider>
), document.getElementById('root'));
I first run the Express app, which starts listening on port 5000, and then I run the react app. When entering a dynamic URL http://localhost:7500/dynamicurl/test-dynamic-url, the app no longer shows 404, but then it doesn't show anything at all, and this is what I see in the developer console:
Refused to apply style from 'http://localhost:7500/dynamicurl/index.fonts.css' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
test-dynamic-url:1 Refused to apply style from 'http://localhost:7500/dynamicurl/index.styles.css' because its MIME type ('text/html') is not a supported stylesheet MIME type, and strict MIME checking is enabled.
test-dynamic-url:54 GET http://localhost:7500/dynamicurl/index.bundle.js net::ERR_ABORTED
What am I doing wrong here? Did I miss something?
Thanks in advance
Related
I am making a web app and have made back-end with express, mongodb and node.js, while the frontend is made in React.js.
The backend runs completely fine on its own, when run using "nodemon server.js"
But when connected to frontend using axios, it throws "Network Error" error.
Here is my code to server.js (back-end, connecting url) and http-common.js (front-end, axios)
server.js
import cors from "cors";
import userquery from "./student/api/query.routes.js";
import admin from "./admin/api/admin.routes.js";
const app = express();
app.use(cors());
app.use(express.json());
app.use("/api/v1/user", userquery);
app.use("/api/v1/admin", admin);
app.use("*", (req, res) => {
res.status(404).json({ error: "not found" });
});
export default app;
query.routes.js
import QueryCtrl from "./query.controller.js";
import personalQueriesCtrl from "./personalQueries.controller.js";
import SubjectCtrl from "./subject.controller.js";
import UserCtrl from "./user.controller.js";
const router = express.Router(); //creates routes people can go to
router.route("/").get(QueryCtrl.apiGetQueries);
router.route("/signup").post(UserCtrl.apiPostUser);
router.route("/subjects").get(SubjectCtrl.apiGetSubjects);
router.route("/AskQuery").post(personalQueriesCtrl.apiPostQuery);
export default router;
http-common.js
export default axios.create({
baseURL: "http://localhost:5000/api/v1/user/",
headers: {
"Content-type": "application/json",
},
proxy: false,
});
Please help if you can!
Summary: Long question so here's the quick summary. I can run the commands ng build && ng run app-name:server and npm run dev:server successfully and see no errors on console whenever I try to connect to server. But page is loading infinitely. I have tried to apply NgxSsrTimeoutModule.forRoot({ timeout: 500 }) nothing changed. Made other changes but saw no progress. Really stuck at this point having headaches.
Hello,
I'm trying to convert a SPA Angular application to an SSR-compatible one. Application is a little bit chunky, so I couldn't create a new SSR rendered application and move the modules to that one, one by one. First, my versions are:
#angular-devkit/architect 0.1301.2
#angular-devkit/build-angular 13.1.2
#angular-devkit/core 13.1.2
#angular-devkit/schematics 13.1.2
#angular/cli 13.1.2
#angular/platform-server 13.1.3
#nguniversal/builders 13.1.1
#nguniversal/express-engine 13.0.2
#schematics/angular 13.1.2
rxjs 6.6.7
typescript 4.5.4
I have made some changes to get development server working. First change was on angular.json. I had error Angular Universal Configuration 'development' is not set in the workspace:
"serve-ssr": {
"builder": "#nguniversal/builders:ssr-dev-server",
"configurations": {
"production": {
"browserTarget": "appName:build:production",
"serverTarget": "appName:server:production"
},
"development": ...
},
"defaultConfiguration": "development"
},
First change was on angular.json. Referring to this issue
I have changed as this:
"serve-ssr": {
"builder": "#nguniversal/builders:ssr-dev-server",
"options": { "browserTarget": "appName:build", "serverTarget": "appName:server"},
"configurations": {
"production": {
"browserTarget": "appName:build:production",
"serverTarget": "appName:server:production"
}
}
},
Second change was on ServerModule.
We had an application starter basically an Angular service requesting an appConfig.json to make application configurations. I had Could not load file '../../assets/config/appConfig.json' which is an error thrown from that service. So basic ServerModule changed as this;
import { NgModule, Inject } from '#angular/core';
import { INITIAL_CONFIG, PlatformConfig, ServerModule } from '#angular/platform-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
import { NgxSsrTimeoutModule } from '#ngx-ssr/timeout';
#NgModule({
imports: [
AppModule,
ServerModule,
NgxSsrTimeoutModule.forRoot({ timeout: 500 })
],
bootstrap: [AppComponent],
})
export class AppServerModule {
constructor(#Inject(INITIAL_CONFIG) private config: PlatformConfig) {
this.config.useAbsoluteUrl = true;
}
}
Also solved the "window not defined" error by adding domino library
to the server code.
After changing this, I could successfully run the comman npm run dev:ssr. Take note that, before doing this, I could run the command ng build && ng run app-name:server but not npm run dev:ssr. And after that, I had a running Node server on my localhost:4200 but page is always in loading state, I could see nothing on network tab (Chrome Dev Tools), no client side application taking action and nothing on console (just says Messages received, value changed...). After all these, I implemented *appShellRender and *appShellNoRender directives, and just decided to render a loading animation in the server, nothing changed;
app.component.ts:
<div *appShellRender>
<app-loading-animation></app-loading-animation>
</div>
<div *appShellNoRender>
<ng-container appCardQuery appSystemIdTracker>
<mat-sidenav-container>
<mat-sidenav #sidenav>
<app-side-nav (closeSideNav)="sidenav.close()"></app-side-nav>
</mat-sidenav>
<mat-sidenav-content (scroll) = "onScroll($event)">
<ng-container *ngIf="isToolbarOn">
<app-toolbar (SideNavToggle)="sidenav.toggle()"></app-toolbar>
</ng-container>
<main>
<!-- <div style="background-color: black; width: 250px;height: 250px;" (click)="testClick()"></div> -->
<router-outlet></router-outlet>
</main>
<div class="spacer30"></div>
<ng-container *ngIf="isAppAboutOn">
<app-about></app-about>
</ng-container>
</mat-sidenav-content>
</mat-sidenav-container>
</ng-container>
</div>
server.ts:
import 'zone.js/dist/zone-node';
import { ngExpressEngine } from '#nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '#angular/common';
import { existsSync } from 'fs';
const domino = require('domino');
// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
const server = express();
const distFolder = join(process.cwd(), 'dist/appName/browser');
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
const win = domino.createWindow(indexHtml);
global['window'] = win;
global['document'] = win.document;
global['navigator'] = win.navigator;
global['screen'] = win.screen;
// Our Universal express-engine (found # https://github.com/angular/universal/tree/master/modules/express-engine)
server.engine('html', ngExpressEngine({
bootstrap: AppServerModule,
}));
server.set('view engine', 'html');
server.set('views', distFolder);
// Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { });
// Serve static files from /browser
server.get('*.*', express.static(distFolder, {
maxAge: '1y'
}));
// All regular routes use the Universal engine
server.get('*', (req, res) => {
res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
});
return server;
}
function run(): void {
const port = process.env['PORT'] || 4000;
// Start up the Node server
const server = app();
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = mainModule && mainModule.filename || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
run();
}
export * from './src/main.server';
Error Possibilities
I see no errors on console but there's obviously something wrong.
We're using AgmCoreModule which is an Angular component to use Maps on application. Also leaflet.
index.js:
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.7.1/dist/leaflet.css" integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin="" />
<script src="https://unpkg.com/leaflet#1.7.1/dist/leaflet.js" integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA==" crossorigin=""></script>
These might be causing errors?
I have launched my first node.js app using express.js for the backend and react.js for the frontend. I have it deployed as a background service on a VM with Ubuntu 20.04. The app itself is pretty simple. It makes an API call, gets the latest data and displays it to the enduser in a web browser interface. I initially deployed it on a free Heroku account, where it works fine and always display the latest info from the API call. But when I deployed it on a private VM with Ubuntu 20.04, I noticed that it does not show the latest API info anymore, i.e. it makes the initial API call during npm start and then probably caches it on the server. So, if I come to the app the next day, all the data is from yesterday. To see the latest data, I have to kill the background service on Ubuntu and then do "npm start", which starts the server and makes the API call. I then see the latest data in the browser. This is my first time working with all this technology, so if you have had a similar issue with deploying on Ubuntu, please let me know how I can fix it.
Here is my package.json "scripts":
"scripts": {
"start:dev": "nodemon --exec babel-node server/server.js --ignore public/",
"build": "babel server --out-dir dist",
"start": "node dist/server.js &",
"dev": "webpack -w --mode=development",
"prod": "webpack --mode=production"
}
Here is my server.js code:
import config, { nodeEnv, logStars } from "./config";
import apiRouter from "./api";
import sassMiddleware from "node-sass-middleware";
import path from "path";
import AMdataPullDone from "./AMdataPullDone";
import express from "express";
const server = express();
server.use(
sassMiddleware({
src: path.join(__dirname, "sass"),
dest: path.join(__dirname, "public"),
})
);
server.set("view engine", "ejs");
server.get("/", (req, res) => {
AMdataPullDone.then(({ initialMarkup, initialData }) => {
res.render("index", {
initialMarkup,
initialData,
});
}).catch(console.error);
});
server.use("/api", apiRouter);
server.use(express.static("public"));
server.listen(config.port, config.host, () => {
console.info("Express listening on port ", config.serverUrl);
});
And here is my index.js file with the reach App:
import React from "react";
import ReactDOM from "react-dom";
import App from "./components/App";
ReactDOM.hydrate(
<App initialData={window.initialData} />,
document.getElementById("root")
);
AMdataPullDone.js code:
import React from "react";
import ReactDOMServer from "react-dom/server";
import App from "./src/components/App";
import generateToken from "./utils/generateToken";
import serverGetAMData from "./serverGetAMData";
const AMdataPullDone = new Promise((resolve, reject) => {
generateToken()
.then(({ access_token }) => {
serverGetAMData(
access_token,
"URL_to_external_API"
)
.then((segResult) => {
const allData = {
initialMarkup: ReactDOMServer.renderToString(
<App initialData={segResult} />
),
initialData: segResult,
};
return resolve(allData);
})
.catch((err) => {
return reject(err.response);
});
})
.catch(console.error);
});
export default AMdataPullDone;
serverGetAMData.js code:
import axios from "axios";
import config from "./config";
const serverGetAMData = async (access_token, apiUrl) => {
const folders = [];
await axios
.get(apiUrl, {
headers: {
Authorization: `Bearer ${access_token}`,
"x-api-key": config.AM_IMS_client_id,
"x-gw-ims-org-id": config.org_id,
}
})
.then((resp) => {
folders.push(...resp.data);
//return resp.data;
})
.catch(console.error);
return folders;
};
export default serverGetAMData;
In my sapper app, i try to import a file into the server.js file like this:
import sirv from 'sirv';
import polka from 'polka';
import compression from 'compression';
import * as sapper from '#sapper/server';
const { PORT, NODE_ENV } = process.env;
const dev = NODE_ENV === 'development';
const pushController = require('./backend/controllers/push');
polka()
.use(
compression({ threshold: 0 }),
sirv('static', { dev }),
sapper.middleware()
)
.post('/something/route', pushController.subscribe)
.listen(PORT, err => {
if (err) console.log('error', err);
});
But i always get the Error on console:
Error: Cannot find module './backend/controllers/push
my root folder looks like this:
- src
- backend
- controllers
- push.js
- client.js
- server.js
- service-worker.js
- template.html
I am using sapper with the default rollup config. Could the error therefore be related to rollup? How can I fix it?
A quick test shows it's ok with your structure. Please make sure you have
# push.js
function pushController() {
...
}
...
module.exports = { pushController }
and use import instead of require
# server.js
import { pushController } from './backend/controllers/push'
I've read a ton of the previous SO questions regarding this however, none seems to solve any of my problems.
index.test.ts
import request from 'supertest';
import * as server from '../server';
import 'jest'
//close server after each request
afterEach( //cannot find name 'afterEach'
async (): Promise<void> => {
await server.close(); // Property 'close' does not exist on type 'typeof import(<path to file)'
},
);
describe('get /', (): void => { //Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i #types/jest` or `npm i #types/mocha`
it('should respond as expected', async (): Promise<void> => {// cannot find 'it'...
const response = await request(server).get('/');
expect(response.status).toEqual(200); // cannot find 'expect'
expect(response.body.data).toEqual('Sending some JSON'); // cannot find 'expect'...
});
});
I have installed both jest and mocha types, just to make sure that it wasn't registering one over the other. Pretty much everything is not being recognized,
.babelrcc
{
"presets": ["#babel/preset-typescript"]
}
jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
};
tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"noImplicitAny": true,
"preserveConstEnums": true,
"outDir": "./dist",
"sourceMap": true,
"esModuleInterop": true
},
"include": ["./src/**/*"],
"exclude": ["test", "**/*/spec.ts", "**/*/test.ts"]
}
server
import * as dotenv from 'dotenv';
dotenv.config();
import Koa from 'koa';
import logger from 'koa-logger';
import apiRoutes from './Routes';
import cors from '#koa/cors';
import bodyParser from 'koa-bodyparser';
const app = new Koa();
const PORT = process.env.PORT || 3001;
const ENV = process.env.NODE_ENV || 'Development';
app.use(logger());
app.use(cors());
app.use(bodyParser());
app.use(apiRoutes);
export const server = app.listen(PORT, (): void => {
console.log(`🌍 Server listening on port ${PORT} - ${ENV} environment`);
});
I don't know what other information is relevant if there is something else I am missing I will update this with the information
You are exporting the named variable server directly while you are trying to import it as if it were the default export.
Just change it to import { server } from './server'; in index.test.ts and it will work as expected. You can also change the export to export default server in server.ts but I tend to avoid that based on this article.
Read more about export on MDN.