I have recently become interested in the subject of server side rendering and have written an application through articles and tutorials that works, but does not show the images that are in the application. I would like my server rendering the App component to render it together with the image in it, but I can't find a way to do it.
Server.js
import express from 'express'
import path from 'path'
import fs from 'fs'
import React from 'react';
import ReactDOMServer from 'react-dom/server'
import App from '../src/App'
const app = express()
const PORT = process.env.PORT || 3001
app.get('/', (req, res) => {
fs.readFile(path.resolve('./build/index.html'), 'utf-8', (err, data) => {
if(err) {
console.log('something went wrong ', err)
return res.status(500).send('better luck next time')
}
return res.send(
data.replace(
'<div id="root"></div>',
`<div id="root">${ReactDOMServer.renderToString(<App />)}</div>`
)
)
})
})
app.use(express.static(path.resolve(__dirname, '..', 'build')))
app.listen(PORT, () => {
console.log(`App running on ${PORT}`)
})
App.js
import React, { useState } from 'react'
import './App.css';
import p1 from './images/p1.jpg'
const App = (context) => {
const [count, setCount] = useState(0);
const handleIncrament = () => {
setCount(count+1)
}
const handleDecrament = () => {
setCount(count -1)
}
return (
<div className="App">
<p>{count}</p>
<button onClick={handleIncrament}>Incrament</button>
<button onClick={handleDecrament}>Decrament</button>
<img src={p1} />
</div>
);
}
export default App;
Doesn't this line of code provide access to all the files in the build folder along with the media files?
app.use(express.static(path.resolve(__dirname, '..', 'build')))
I also saw in one of the topics a method to work around this problem by eliminating the imports for images and replacing them with the absolute path to the file. However, before reaching for this solution, I would like to ask if there is any other way? Without removing the import syntax?
Related
I'm trying to build a instagram scraper with puppeteer and react that works with putting the username on an input and then I want to show the scraped data on the console, I already built the puppeteer script and It works, it returns the data correctly, But I have some issues trying to get the data from a post with axios, I'm using node js and express for my server, when I try to do the post with axios I keep getting an error.
I want to write the username on the input, then I want the puppeteer script to run, and then I want to console log the data that the puppeteer script returns
Error on console
POST http://localhost:4000/api/getData/username_im_scraping net::ERR_CONNECTION_REFUSED
This is my code
Server > index.js
const path = require("path");
const express = require("express");
const webpack = require("webpack");
const cors= require('cors');
const webpackDevMiddleware = require("webpack-dev-middleware");
const webpackHotMiddleware = require("webpack-hot-middleware");
const config = require(path.join(__dirname, "../webpack.config.js"));
const compiler = webpack(config);
const app = express();
const { script } = require("./script");
app.use(webpackDevMiddleware(compiler, config.devServer));
app.use(webpackHotMiddleware(compiler));
app.use(express.static(path.join(__dirname, '../build')));
app.use(cors());
app.get("/api/getData/:username", async (req, res) => {
console.log(`starting script for user ${req.params.username}`);
const data = await script(req.params.username);
console.log(`stopping script for user ${req.params.username}`);
res.send(data);
});
app.get('/*', (req, res) => {
res.sendFile(path.join(__dirname, '../build', 'index.html'));
});
app.listen(process.env.PORT || 4000, () => {
console.log('Server is listening on port 4000');
});
Homepage.js
import React, { useState } from "react";
import axios from "axios";
const Homepage = props => {
const [username, setUsername] = useState("");
const onChange = ({ target: { value } }) => setUsername(value);
const onClick = () => {
axios.post('http://localhost:4000/api/getData/' + username, {
header: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8;application/json' },
mode: "cors"
})
.then((response) => {
console.log(response);
})
.catch((error) => {
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
console.log(error.request);
} else {
console.log('Error', error.message);
}
})
};
return (
<div>
Time to start coding!
<input value={username} onChange={onChange} />
<button onClick={onClick}>Get instagram followers!</button>
</div>
);
};
export default Homepage;
The problem here is you defined your route with get like app.get("/api/getData/:username") but you are sending a post request. Either change the router to app.post or your axios method to axios.get.
UPDATE
Besides the changes above, after you shared the repository with me i checked and saw you have another problem, which was that you were not running your server so the ERR_CONNECTION_REFUSED message shown.
I forked your repository and made the following changes and created a PR for it. Please take a look at it, and if you want just merge it to your master branch.
P.S please for the next time create a .gitignore file and add node_modules to there so you don't push and pull your node_modules which takes some more amount of time.
I'm just learning Express/React and I'm trying to get set up with routes and basic database connections. I suspect I'm missing something very simple. I've tried to boil it down to the following.
Backend
server.js:
require('dotenv').config({path: '../.env'});
const mysql = require('mysql');
const express = require('express');
const bodyParser = require('body-parser');
const port = process.env.PORT || 5000;
const app = express();
const router = express.Router();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
const users = require('./routes/api/users');
app.use('/api/users', users);
const events = require('./routes/api/events');
app.use('/api/events', events);
const db = mysql.createConnection({
host: process.env.DB_HOST,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD
});
db.connect(function(err) {
if (err) throw err;
console.log('MySQL Connected!');
})
app.listen(port, () => console.log(`Listening on port ${port}`));
/routes/api/events.js:
const express = require('express');
const router = express.Router();
// GET api/events
router.get('/', (req, res) => {
res.send({id: "1", name: "hi"});
});
module.exports = router;
Frontend
App.js:
import React, { Component } from 'react';
import EventList from './components/EventList';
class App extends Component {
render() {
return (
<div className="App">
<EventList/>
</div>)
}
}
export default App;
/components/EventList.js:
import React, { Component } from 'react';
import axios from 'axios';
class EventList extends Component {
constructor() {
super();
this.state = {
events: []
}
}
componentDidMount() {
axios.get('/api/events')
.then(events => {
this.setState({events: events.data})
})
.catch(err => console.log(err))
}
render() {
var events = this.state.events;
return (
<div>
<p>Events:</p>
<ul>
{ events.map(({ id, name }) => (
<li>{name}</li>
))}
</ul>
</div>
)
}
}
export default EventList;
The error I get is http://localhost:3000/api/events 500 (Internal Server Error). What am I missing? I have truly scoured all of the docs I can find, but I'm not quite getting it.
Edit
I haven't changed anything, but now I'm getting a 404 (Not Found) instead. I had been getting a 500 for awhile, so it wasn't a momentary fluke. I'm not sure what could have changed.
Update
It may help to know that the .env variables I'm pointing to are actually for a remote MySQL database (i.e., DB_HOST != localhost, but a remote URL). Eventually, I'd like to connect the GET call on the events route to that db, but since it doesn't work with what I have here I figured my first issue to solve was upstream. As noted in comments, the PORT var I'm loading in is 3306. When I start the server, it says it's listening on 3306 as expected.
I think you are running your server on port 5000 and front on port 3000. if you request events with http://localhost:5000/api/events instead of /api/events, you would get 200 status code with your json data.
// as is
axios.get('/api/events')
// to be
axios.get('http://localhost:5000/api/events')
You could try typing componentDidMount function like this.
componentDidMount = async () =>{
//like this
}
In addition, I would recommend making the GET ALL its own function so you could just invoke in the componentDidMount function. whenever you run another CRUD action it will automatically update your events for you and instead of setting state every time you can invoke the this.getEvents() again to update it that way.
componentDidMount = async () =>{
this.getEvents()
}
Also you need to add this package (npm install cors) its so you can connect your api to your frontend.
Import it like this in your server.js file
const cors = require('cors')
app.use(cors())
You should add a proxy inside package.json in frontend like this.
"proxy": "http://localhost:5000",
I'm setting up a single-page App with React router using Express router on the server side (for serving the first index page with the react router and then standing by for API requests). Currently I only have 2 routes: '/' and '/history'
-clicking on "history" link from the home page gets me there without any problems.
-Manually typing "/history" route makes the browser stay on loading forever.
-Reloading the page while being on "/history" makes the browser stay on loading forever.
how can i go about solving this?
I tried rearranging the routes on express, After this I tried setting the app.all('*') method to handle all requests. Finally I tried changing app.all with app.get...,but neither of these help me.
my index.js (for express router):
/*Express Server*/
const express = require('express');
const app = express();
const morgan = require('morgan');
const path = require('path');
const bodyParser = require('body-parser');
const { mongoose } = require('./database');
//settings
app.set('port', process.env.PORT || 3000);
//middleware
app.use(morgan('dev'));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
//routes
app.use('/api/month',require('./routes/monthly.routes'));
app.use('/history',require('./routes/history.routes'));
//static files
app.use(express.static(path.join(__dirname, 'public')))
app.get(async (req,res)=>{
await res.sendFile(path.join(__dirname, '/public/index'), (err)=> {
if (err) {
res.status(500).send(err)
}
})
});
//Start server
app.listen(app.get('port'), ()=>{
console.log(`Server listening on ${app.get('port')}`);
});
router part of my app.js (for react router):
import React, { Component } from 'react';
import ReactDom from 'react-dom';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import Axios from 'axios';
import Home from './Home'
import Latest from './Latest'
class App extends Component {
constructor(props) {
super(props);
this.state = {
RecentExpenses: [],
monthState: null,
Total: 0,
Today: null
}
};
render() {
return (
<BrowserRouter>
<Switch>
<Route exact path='/' render={(props) => <Home {...props} Today={this.state.Today} Total={this.state.Total} RecentExpenses={this.state.RecentExpenses} />} />
<Route exact path='/history' render={(props) => <Latest {...props} Recent={this.state.RecentExpenses} month={this.state.monthState} />} />
</Switch>
</BrowserRouter>
)
}
}
ReactDom.render(<App/>, document.getElementById('app'));
What I expect is to always receive the same file from the server, so react router can take on as the navigation router. but it stays loading forever with this being thrown into the node console:
(node:8756) UnhandledPromiseRejectionWarning: Error: Cannot find module 'html'
at Function.Module._resolveFilename (internal/modules/cjs/loader.js:571:15)
at Function.Module._load (internal/modules/cjs/loader.js:497:25)
at Module.require (internal/modules/cjs/loader.js:626:17)
at require (internal/modules/cjs/helpers.js:20:18)
On your index.js you could replace that:
app.use(express.static(path.join(__dirname, 'public')))
app.get(async (req,res)=>{
await res.sendFile(path.join(__dirname, '/public/index'), (err)=> {
if (err) {
res.status(500).send(err)
}
})
});
For that:
app.use(express.static(`${process.cwd()}/public/`))
app.get('*', async (req, res) => {
await res.sendFile(`${process.cwd()}/public/index.html`, (err) => {
if (err) {
res.status(500).send(err)
}
})
});
You should be able to access /history without any problem.
I was able to fix it, turns out that it was a routing error. as my history.routes.js was handling the /history requests (with a deprecated part of my code) and it never got to call app.use('*'....).
Bypassing this specific route allowed to get me to serve the index.js and continue the react routing from there. it is a matter of understanding how and when both routings work.
PS: #aquilesb 's answer helped with setting the proper path, for anyone that might have an issue with this.
I try to get the :userId "albert" from this url
http://localhost:5000/search/albert?query=al&page=1
at server side but failed, what can I do to get the react-router defined params correctly at node.js with express?
routes.js
[
{
path: '/search/:userId',
component: Search,
}, {
path: '/search',
component: Search,
}
...
]
server.js
server.get('*', async (req, res, next) => {
const pageData = await routes
.filter(route => matchPath(req.path, route))
.map((route) => {
console.log(route)
return route.component
})
}
The React-Router Way
React Router V4 does include a way to extract param data server-side using their matchPath() function, using their standard parameter implementation, "/path-name/:param" route matching.
In this case, it allows me to do a lot of server-side stuff based on the parameter before the express app responds with the page data.
NOTE: this is probably not the most basic implementation, but it's a pared down version of my complete SSR react implementation that makes use of matchPath().
Requirements
Server-side rendered react app
React-router-dom v4
Centralized routes file (because SSR)
Express app server (I'm hosting my express app on Firebase)
In This Example, a server-side express app attempts to run an "initialAction" function in each component during a fresh page load. It passes promise resolve and reject to know when the function is completed running, and the request object which may contain useful params we can extract with matchPath(). It does this for every matching route, again, using matchPath().
Routes.js Example
Where :id is the "id" param in the URL.
const routes = [
{
path: "/news-feed/:id",
component: NewsFeed,
exact: true
},
]
export default routes;
Component Example
Just showing the initialAction() function in the component
import { Link, matchPath } from 'react-router-dom';
class NewsFeed extends Component {
// Server always passes ability to resolve, reject in the initial action
// for async data requirements. req object always passed from express to
// the initial action.
static initialAction(resolve, reject, req) {
function getRouteData() {
let matchingRoute = routes.find(route => {
return matchPath(req.path, route);
});
console.log("Matching Route: ", matchingRoute);
return matchPath(req.path, matchingRoute);
}
let routeData = getRouteData();
console.log("Route Data: ", routeData);
}
/** REST OF COMPONENT **/
Console.log output for the url www.example.com/news-feed/test would be
Route Data: { path: '/news-feed/:id',
url: '/news-feed/test',
isExact: true,
params: { id: 'test' } }
As you can see, we've found our param on the server-side using no regex. matchPath() did the work for us. We can use nice, clean urls.
Server-side index.js
Where the initial action is called, with the promise resolve, reject, and req objects. Keep in mind this is a firebase hosting example and may differ for different hosting providers - your method for the initialAction function call may also differ.
import React from "react";
import ReactDOMServer from 'react-dom/server';
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { StaticRouter, matchPath } from "react-router-dom";
import routes from "../shared/components/App/routes.js";
import express from "express";
import * as functions from "firebase-functions";
// Import Components, Reducers, Styles
import App from "../shared/components/App";
import reducers from "../shared/reducers";
// Prepare our store to be enhanced with middleware
const middleware = [thunk];
const createStoreWithMiddleware = applyMiddleware(...middleware)(createStore);
// Create store, compatible with REDUX_DEVTOOLS (chrome extension)
const store = createStoreWithMiddleware(reducers);
// Implement cors middleware to allow cross-origin
const cors = require('cors')({ origin: true });
const app = express();
app.get('**', (req, res) => {
cors(req, res, () => {
// Finds the component for the given route, runs the "initial action" on the component
// The initialAction is a function on all server-side renderable components that must retrieve data before sending the http response
// Initial action always requires (resolve, reject, req), and returns a promise.
const promises = routes.reduce((acc, route) => {
if (matchPath(req.url, route) && route.component && route.component.initialAction) {
acc.push(new Promise(function (resolve, reject) {
// console.log("Calling initial action...");
store.dispatch(route.component.initialAction(resolve, reject, req));
}));
}
return acc;
}, []);
// Send our response only once all promises (from all components included in the route) have resolved
Promise.all(promises)
.then(() => {
const context = {};
const html = ReactDOMServer.renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
</Provider>
);
const preloadedState = store.getState();
res.status(200).send(renderFullPage(html, preloadedState));
})
.catch(function (error) {
console.log("Promise error at server", error);
});
});
});
module.exports = functions.https.onRequest(app);
Just used a sample node.js app to make a server.js which could be like
const express = require('express')
const app = express()
app.get('/search/:userid', (req, res) => res.json({ key: `Hello World for search with id=${req.params.userid}` }))
app.get('/search', (req, res) => res.send('Hello World!i for search'))
app.get('*', (req, res) => res.send('Hello World!'))
app.listen(3000, () => console.log('Example app listening on port 3000!'))
For the page number and other url params you can do like
req.query['page']
to retrieve the parameters.
I am trying to separate responsibilities, dividing in different files and modules the different actions that I will carry out in my application on the server side.
I ran into a problem, which I can not understand. I try to export from the file which starts the server, the variable app, in which I store express in the following way:
server.js
import express from 'express';
import webpack from 'webpack';
import webpackDevMiddleware from 'webpack-dev-middleware';
import webpackConfig from '../webpack.config';
import path from 'path';
const app = express();
app.set('port', process.env.PORT || 3000);
app.use(webpackDevMiddleware(webpack(webpackConfig)));
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, '..', 'public', 'index.html'));
});
app.get('/api', (req, res) => {
res.json({api: "Woks Fine"});
});
app.listen(app.get('port'), () => {
console.log("App Start in Port", app.get('port'));
});
export default app;
apiGoogleMaps.js
import app from '../server.js';
export function respuestaMensaje(apiUrl, app) {
console.log(apiUrl);
app.post(apiUrl, (req, res) => {
console.log(req.body);
});
}
adressjs
import React, { Component } from 'react';
import { render } from 'react-dom';
import request from 'superagent';
import { respuestaMensaje } from '../../../src/handlers/apiGoogleMap.js';
class AddressInput extends Component{
constructor(){
super();
this.state = {
address: "",
api:"http://maps.google.com/maps/api/geocode/json?address=",
direccion: "",
latitud: "",
longitud:""
};
}
render(){
return(
<div>
<form>
<input type="text" value={this.state.address} onChange={this.updateAdress.bind(this)}/>
<button onClick={this.getAddressGeo.bind(this)}>Consultar</button>
</form>
<ul>
<li><label>Direccion:</label>{this.state.direccion}</li>
<li><label>Latitud:{this.state.latitud}</label></li>
<li><label>Longitud:{this.state.longitud}</label></li>
</ul>
</div>
)
}
updateAdress(event){
this.setState({
address: event.target.value
});
}
getAddressGeo(e){
e.preventDefault();
const apiUrl = this.state.api + this.state.address;
respuestaMensaje(apiUrl);
}
}
export default AddressInput;
Project Structure
Many errors like this appear:
[1] WARNING in ./node_modules/uglifyjs-webpack-plugin/node_modules/uglify-es/tools/node.js
[1] 18:11-32 Critical dependency: the request of a dependency is an expression
[1] # ./node_modules/uglifyjs-webpack-plugin/node_modules/uglify-es/tools/node.js
[1] # ./node_modules/uglifyjs-webpack-plugin/dist/uglify/minify.js
[1] # ./node_modules/uglifyjs-webpack-plugin/dist/uglify/index.js
[1] # ./node_modules/uglifyjs-webpack-plugin/dist/index.js
[1] # ./node_modules/uglifyjs-webpack-plugin/dist/cjs.js
[1] # (webpack)/lib/WebpackOptionsDefaulter.js
[1] # (webpack)/lib/webpack.js
[1] # ./src/server.js
[1] # ./src/handlers/apiGoogleMap.js
[1] # ./src/ui/components/address.js
[1] # ./src/ui/app.js
[1] # ./src/ui/index.js
[1]
Full error log
This error is caused by the webpack import in server.js, used as middleware:
import webpack from 'webpack';
import webpackDevMiddleware from 'webpack-dev-middleware';
import webpackConfig from '../webpack.config';
import path from 'path';
const app = express();
app.set('port', process.env.PORT || 3000);
app.use(webpackDevMiddleware(webpack(webpackConfig)));
The telling frames from the stack trace are:
[1] # (webpack)/lib/WebpackOptionsDefaulter.js
[1] # (webpack)/lib/webpack.js
[1] # ./src/server.js
It appears you can't include webpack in a webpack bundle, at least not by importing it, thanks to the dynamic imports that webpack uses.
The same errors appear with express (specifically express.view), explained in this issue:
There's a dynamical requirement in ExpressJS: https://github.com/expressjs/express/blob/master/lib/view.js#L78
You can mark express (and/or webpack) as an external dependency to prevent them from being included in the bundle (I have not used this library, it is linked from the issue).
Instead of exporting your app, which the main backbone of your entire server, use a router in your apiGoogleMaps.js file and export it.
Use the router in your app.js file successfully! :D
birds.route.js
var express = require('express')
var router = express.Router()
router.get('/', function (req, res) {
res.send('Birds home page')
})
router.get('/:id', function (req, res) {
res.send(req.params.id);
})
module.exports = router
app.js
const myBirds = require('./birds');
...
...
app.use('/birds', myBirds);