server.js (5262B)
1 /** 2 * React Starter Kit (https://www.reactstarterkit.com/) 3 * 4 * Copyright © 2014-present Kriasoft, LLC. All rights reserved. 5 * 6 * This source code is licensed under the MIT license found in the 7 * LICENSE.txt file in the root directory of this source tree. 8 */ 9 10 import path from 'path'; 11 import express from 'express'; 12 import cookieParser from 'cookie-parser'; 13 import bodyParser from 'body-parser'; 14 import expressJwt from 'express-jwt'; 15 import expressGraphQL from 'express-graphql'; 16 import jwt from 'jsonwebtoken'; 17 import React from 'react'; 18 import ReactDOM from 'react-dom/server'; 19 import UniversalRouter from 'universal-router'; 20 import PrettyError from 'pretty-error'; 21 import App from './components/App'; 22 import Html from './components/Html'; 23 import { ErrorPageWithoutStyle } from './routes/error/ErrorPage'; 24 import errorPageStyle from './routes/error/ErrorPage.css'; 25 import passport from './core/passport'; 26 import models from './data/models'; 27 import schema from './data/schema'; 28 import routes from './routes'; 29 import assets from './assets.json'; // eslint-disable-line import/no-unresolved 30 import { port, auth } from './config'; 31 32 const app = express(); 33 34 // 35 // Tell any CSS tooling (such as Material UI) to use all vendor prefixes if the 36 // user agent is not known. 37 // ----------------------------------------------------------------------------- 38 global.navigator = global.navigator || {}; 39 global.navigator.userAgent = global.navigator.userAgent || 'all'; 40 41 // 42 // Register Node.js middleware 43 // ----------------------------------------------------------------------------- 44 app.use(express.static(path.join(__dirname, 'public'))); 45 app.use(cookieParser()); 46 app.use(bodyParser.urlencoded({ extended: true })); 47 app.use(bodyParser.json()); 48 49 // 50 // Authentication 51 // ----------------------------------------------------------------------------- 52 app.use(expressJwt({ 53 secret: auth.jwt.secret, 54 credentialsRequired: false, 55 getToken: req => req.cookies.id_token, 56 })); 57 app.use(passport.initialize()); 58 59 if (__DEV__) { 60 app.enable('trust proxy'); 61 } 62 app.get('/login/facebook', 63 passport.authenticate('facebook', { scope: ['email', 'user_location'], session: false }), 64 ); 65 app.get('/login/facebook/return', 66 passport.authenticate('facebook', { failureRedirect: '/login', session: false }), 67 (req, res) => { 68 const expiresIn = 60 * 60 * 24 * 180; // 180 days 69 const token = jwt.sign(req.user, auth.jwt.secret, { expiresIn }); 70 res.cookie('id_token', token, { maxAge: 1000 * expiresIn, httpOnly: true }); 71 res.redirect('/'); 72 }, 73 ); 74 75 // 76 // Register API middleware 77 // ----------------------------------------------------------------------------- 78 app.use('/graphql', expressGraphQL(req => ({ 79 schema, 80 graphiql: __DEV__, 81 rootValue: { request: req }, 82 pretty: __DEV__, 83 }))); 84 85 // 86 // Register server-side rendering middleware 87 // ----------------------------------------------------------------------------- 88 app.get('*', async (req, res, next) => { 89 try { 90 const css = new Set(); 91 92 // Global (context) variables that can be easily accessed from any React component 93 // https://facebook.github.io/react/docs/context.html 94 const context = { 95 // Enables critical path CSS rendering 96 // https://github.com/kriasoft/isomorphic-style-loader 97 insertCss: (...styles) => { 98 // eslint-disable-next-line no-underscore-dangle 99 styles.forEach(style => css.add(style._getCss())); 100 }, 101 }; 102 103 const route = await UniversalRouter.resolve(routes, { 104 path: req.path, 105 query: req.query, 106 }); 107 108 if (route.redirect) { 109 res.redirect(route.status || 302, route.redirect); 110 return; 111 } 112 113 const data = { ...route }; 114 data.children = ReactDOM.renderToString(<App context={context}>{route.component}</App>); 115 data.styles = [ 116 { id: 'css', cssText: [...css].join('') }, 117 ]; 118 data.scripts = [ 119 assets.vendor.js, 120 assets.client.js, 121 ]; 122 if (assets[route.chunk]) { 123 data.scripts.push(assets[route.chunk].js); 124 } 125 126 const html = ReactDOM.renderToStaticMarkup(<Html {...data} />); 127 res.status(route.status || 200); 128 res.send(`<!doctype html>${html}`); 129 } catch (err) { 130 next(err); 131 } 132 }); 133 134 // 135 // Error handling 136 // ----------------------------------------------------------------------------- 137 const pe = new PrettyError(); 138 pe.skipNodeFiles(); 139 pe.skipPackage('express'); 140 141 app.use((err, req, res, next) => { // eslint-disable-line no-unused-vars 142 console.log(pe.render(err)); // eslint-disable-line no-console 143 const html = ReactDOM.renderToStaticMarkup( 144 <Html 145 title="Internal Server Error" 146 description={err.message} 147 styles={[{ id: 'css', cssText: errorPageStyle._getCss() }]} // eslint-disable-line no-underscore-dangle 148 > 149 {ReactDOM.renderToString(<ErrorPageWithoutStyle error={err} />)} 150 </Html>, 151 ); 152 res.status(err.status || 500); 153 res.send(`<!doctype html>${html}`); 154 }); 155 156 // 157 // Launch the server 158 // ----------------------------------------------------------------------------- 159 /* eslint-disable no-console */ 160 models.sync().catch(err => console.error(err.stack)).then(() => { 161 app.listen(port, () => { 162 console.log(`The server is running at http://localhost:${port}/`); 163 }); 164 }); 165 /* eslint-enable no-console */