├── .gitignore ├── src ├── public │ └── js │ │ ├── lib │ │ ├── requests │ │ │ ├── getRequest.js │ │ │ └── createRequest.js │ │ └── api │ │ │ └── products.js │ │ ├── components │ │ ├── footer │ │ │ └── Footer.js │ │ ├── home │ │ │ ├── components │ │ │ │ └── topProduct │ │ │ │ │ └── TopProduct.js │ │ │ └── Home.js │ │ ├── header │ │ │ └── Header.js │ │ ├── app │ │ │ ├── ClientRouter.js │ │ │ ├── ServerRouter.js │ │ │ └── App.js │ │ └── product │ │ │ └── Product.js │ │ └── main.js ├── lib │ └── services │ │ ├── index.js │ │ └── productService │ │ ├── productData.js │ │ └── ProductService.js ├── views │ ├── index.js │ └── home.html └── app.js ├── .vscode └── settings.json ├── server.js ├── package.json ├── README.md └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /src/public/js/lib/requests/getRequest.js: -------------------------------------------------------------------------------- 1 | const createRequest = require("./createRequest"); 2 | 3 | module.exports = createRequest("GET"); 4 | -------------------------------------------------------------------------------- /src/lib/services/index.js: -------------------------------------------------------------------------------- 1 | const ProductService = require("./productService/ProductService"); 2 | 3 | module.exports = { 4 | productService: new ProductService() 5 | }; 6 | -------------------------------------------------------------------------------- /src/public/js/components/footer/Footer.js: -------------------------------------------------------------------------------- 1 | const React = require("react"); 2 | 3 | const Footer = () => { 4 | return ; 5 | }; 6 | 7 | module.exports = Footer; 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "spellright.language": [ 3 | "en" 4 | ], 5 | "spellright.documentTypes": [ 6 | "markdown", 7 | "latex", 8 | "plaintext" 9 | ] 10 | } -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const app = require("./src/app"); 2 | 3 | const port = process.env.PORT || 3000; 4 | 5 | app.listen(port, () => { 6 | console.log("Running server on port:", port); 7 | console.log("--------------------------"); 8 | }); 9 | -------------------------------------------------------------------------------- /src/public/js/lib/requests/createRequest.js: -------------------------------------------------------------------------------- 1 | module.exports = method => data => { 2 | return { 3 | method, 4 | headers: { 5 | "Content-Type": "application/json" 6 | }, 7 | body: JSON.stringify(data) 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /src/lib/services/productService/productData.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 1: { 3 | id: 1, 4 | name: "foo" 5 | }, 6 | 2: { 7 | id: 2, 8 | name: "bar" 9 | }, 10 | 3: { 11 | id: 3, 12 | name: "baz" 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /src/public/js/main.js: -------------------------------------------------------------------------------- 1 | const React = require("react"); 2 | const ReactDOM = require("react-dom"); 3 | const Router = require("./components/app/ClientRouter"); 4 | 5 | ReactDOM.render( 6 | , 7 | document.getElementById("root") 8 | ); 9 | -------------------------------------------------------------------------------- /src/public/js/components/home/components/topProduct/TopProduct.js: -------------------------------------------------------------------------------- 1 | const React = require("react"); 2 | const { Link } = require("react-router-dom"); 3 | 4 | module.exports = ({ product }) => { 5 | return ( 6 |
  • 7 | {product.name} 8 |
  • 9 | ); 10 | }; 11 | -------------------------------------------------------------------------------- /src/public/js/components/header/Header.js: -------------------------------------------------------------------------------- 1 | const React = require("react"); 2 | const { Link } = require("react-router-dom"); 3 | 4 | const Header = () => { 5 | return ( 6 | 11 | ); 12 | }; 13 | 14 | module.exports = Header; 15 | -------------------------------------------------------------------------------- /src/lib/services/productService/ProductService.js: -------------------------------------------------------------------------------- 1 | const productData = require("./productData"); 2 | 3 | class ProductService { 4 | async getProduct(productId) { 5 | return productData[productId]; 6 | } 7 | 8 | async getTopProducts() { 9 | return Object.values(productData); 10 | } 11 | } 12 | 13 | module.exports = ProductService; 14 | -------------------------------------------------------------------------------- /src/public/js/components/app/ClientRouter.js: -------------------------------------------------------------------------------- 1 | const React = require("react"); 2 | const App = require("./App"); 3 | const { BrowserRouter } = require("react-router-dom"); 4 | 5 | const ClientRouter = ({ state }) => { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | }; 12 | 13 | module.exports = ClientRouter; 14 | -------------------------------------------------------------------------------- /src/public/js/components/app/ServerRouter.js: -------------------------------------------------------------------------------- 1 | const React = require("react"); 2 | const App = require("./App"); 3 | const { StaticRouter } = require("react-router-dom"); 4 | 5 | const ServerApp = ({ url, state }) => { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | }; 12 | 13 | module.exports = ServerApp; 14 | -------------------------------------------------------------------------------- /src/public/js/lib/api/products.js: -------------------------------------------------------------------------------- 1 | const getRequest = require("../requests/getRequest"); 2 | 3 | const getTopProducts = async () => { 4 | const res = await fetch("/api/v1/products", getRequest()); 5 | return res.json(); 6 | }; 7 | 8 | const getProduct = async productId => { 9 | const res = await fetch(`/api/v1/products/${productId}`, getRequest()); 10 | return res.json(); 11 | }; 12 | 13 | module.exports = { 14 | getTopProducts, 15 | getProduct 16 | }; 17 | -------------------------------------------------------------------------------- /src/views/index.js: -------------------------------------------------------------------------------- 1 | const html = (jsx, state = {}) => { 2 | return ` 3 | 4 | 5 | 6 | 7 | 8 | 9 | Document 10 | 11 | 12 | 13 | 14 |
    ${jsx}
    15 | 16 | 17 | 18 | 19 | `; 20 | }; 21 | 22 | module.exports = html; 23 | -------------------------------------------------------------------------------- /src/public/js/components/product/Product.js: -------------------------------------------------------------------------------- 1 | const React = require("react"); 2 | const { getProduct } = require("../../lib/api/products"); 3 | 4 | class Product extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = { product: props.product }; 8 | } 9 | 10 | async componentDidMount() { 11 | if (this.props.product.id != this.props.match.params.productId) { 12 | const product = await getProduct(this.props.match.params.productId); 13 | this.setState({ product }); 14 | } 15 | } 16 | 17 | render() { 18 | return ( 19 |
    20 | {this.state.product.name} 21 |
    22 | ); 23 | } 24 | } 25 | 26 | module.exports = Product; 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ssr-react", 3 | "version": "1.0.0", 4 | "description": "## What we will cover", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "nodemon ./bundle.server.js", 8 | "webpack": "npx webpack --watch", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "express": "4.16.4", 15 | "react": "16.6.3", 16 | "react-dom": "16.6.3", 17 | "react-router-dom": "4.3.1" 18 | }, 19 | "devDependencies": { 20 | "@babel/core": "7.1.6", 21 | "@babel/preset-react": "7.0.0", 22 | "babel-loader": "8.0.4", 23 | "nodemon": "1.18.6", 24 | "webpack": "4.26.0", 25 | "webpack-cli": "3.1.2" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/public/js/components/app/App.js: -------------------------------------------------------------------------------- 1 | const React = require("react"); 2 | const Home = require("../home/Home"); 3 | const Product = require("../product/Product"); 4 | const Header = require("../header/Header"); 5 | const Footer = require("../footer/Footer"); 6 | const { Route, Switch } = require("react-router-dom"); 7 | 8 | const initState = { 9 | products: [], 10 | selectedProduct: {} 11 | } 12 | 13 | const App = ({state = initState}) => { 14 | return ( 15 | <> 16 |
    17 | 18 | } /> 19 | } /> 20 | } /> 21 | 22 |