部署在 Netlify 上时使用 Express 公开 API 路由
Expose API routes with Express when deployed on Netlify
我用 create-react-app that I build in ./client/build
and serve with express 创建了一个网站。
使用 express,我还设置了 2 个基本的 API 路由,它们在 dev (localhost) 中运行良好。
我要在 netlify 上部署什么?
我用 react-scripts
构建客户端
然后我手动将环境设置为 production 以使用 webpack
在本地构建服务器
最后,我将我的代码推送到我的 GitLab 存储库,它会自动触发部署。
使用 Netlify 部署的设置:
Build command: Not set
Publish directory: client/build
问题
当我尝试使用我的 API 路线之一时,出现错误 404 ...
项目
我的项目结构是这样的:
| package.json
| server.js
| webpack.config.js
+---API
| dbHandler.js
| routesHandler.js
+---client
| | package.json
| +---src
| | | App.js
| | | ...
| +---node_modules
| +---public
| | index.html
| | manifest.json
| | _redirects
| \---build
| | index.html
| | manifest.json
| | _redirects
| | asset-manifest.json
| | favicon_rings.ico
| | service-worker.js
| \---static
| +---media
| | ...
| +---js
| | main.1e7ccdbf.js
| | main.1e7ccdbf.js.map
| \---css
| main.fc8f2d26.css
| main.fc8f2d26.css.map
+---public
| _redirects
\---bundles
bundle.js
代码
摘自./package.json
:
"main": "server.js",
"homepage": "https://custom.netlify.com",
"scripts": {
"start": "node server.js",
"build": "webpack --config webpack.config.js",
"postinstall": "npm run build",
"start:dev": "nodemon -e js --exec babel-node -- ./server.js"
}
./client/package.json
:
"homepage": "https://custom.netlify.com",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build && cp build/index.html build/404.html",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
},
"proxy": "http://localhost:5000"
./client/build/_redirects
和 ./public/_redirects
:
# Redirects from what the browser requests to what we serve
/* /index.html 200
./server.js
:
import webpackDevMiddleware from "webpack-dev-middleware";
import webpackHotMiddleware from "webpack-hot-middleware";
import routesHandler from "./API/routesHandler";
import config from "./webpack.config";
import bodyParser from "body-parser";
import webpack from "webpack";
import express from "express";
import path from "path";
const port = process.env.PORT || 5000;
const ExpressServer = express();
const CLI_BUILD_DIR = path.join(__dirname, "client/build");
const HTML_BUNDLE = path.join(CLI_BUILD_DIR, "index.html");
const compiler = webpack(config);
const isDevelopment = process.env.NODE_ENV === "development";
ExpressServer.use(bodyParser.urlencoded({ extended: true }));
ExpressServer.use(bodyParser.json());
ExpressServer.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept"
);
next();
});
if (isDevelopment) {
ExpressServer.use(
webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath
})
);
ExpressServer.use(webpackHotMiddleware(compiler));
} else {
// Serve static files from the React app
ExpressServer.use(express.static(CLI_BUILD_DIR));
}
// Define API routes
var router = express.Router();
router.post("/register", routesHandler.register);
router.post("/login", routesHandler.login);
// Put all API endpoints under '/API'
ExpressServer.use("/API", router);
// All other routes will be directed to the main page of user interface
ExpressServer.get("/", (req, res) => res.sendFile(HTML_BUNDLE));
ExpressServer.get("*", (req, res) => res.redirect(301, "/"));
// Start the server
ExpressServer.listen(port, function() {
console.log("Express server listening on http://localhost:" + port);
});
./webpack.config.js
:
const path = require('path');
const webpack = require('webpack');
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
module.exports = {
entry: [
'webpack-hot-middleware/client',
'./API/routesHandler.js',
'./API/dbHandler.js',
'./server.js'
],
externals: {
jquery: 'jQuery',
'react/addons': 'react',
'react/lib/ExecutionEnvironment': 'react',
'react/lib/ReactContext': 'react',
},
output: {
path: path.resolve(__dirname, 'bundles'),
filename: 'bundle.js',
publicPath: '/public',
sourceMapFilename: 'bundle.map',
},
devtool: process.env.NODE_ENV === 'production'
? undefined : 'cheap-module-eval-source-map',
resolve: {
modules: ['node_modules', './client/build', './API', '.'],
extensions: ['.js', '.jsx', '.css'],
},
module: {
rules: [
{
test: /(\.js$|\.jsx$)/,
exclude: /(node_modules|bower_components)/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['react', 'es2015', 'stage-0', 'airbnb'],
},
},
],
},
{
test: /(\.css$|\.scss$)/,
use: ['style-loader', 'css-loader', 'sass-loader'],
},
{
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|jpeg|gif)(\?v=\d+\.\d+\.\d+)?$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[ext]'
}
}
}
],
},
node: {
console: false,
fs: 'empty',
fsevents: 'empty'
},
plugins: [
new webpack.IgnorePlugin(/fs/)],
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
minimize: true,
compressor: {
warnings: false,
},
}),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV)
},
}),
],
};
Netlify 用于静态文件托管 - 您不能将 Express 应用程序部署到它,只能部署到您的前端。您需要为 API 托管使用不同的服务(例如 DigitalOcean 或 Heroku)。如果您想使用他们的 CDN 进行响应缓存,则可以使用 redirects 通过 Netlify 将流量路由到该 API 服务器。
编辑: 这不再完全正确 - Netlify 现在也有 support for deploying AWS Lambda functions,允许 JavaScript 和 Go 成为 运行服务器端。
今年早些时候 Netlify 添加了对 AWS Lamdba 函数的支持,现在这实际上是很有可能的。这里有一些资源供那些现在发现这个问题的人使用。
https://www.netlify.com/docs/functions/
https://www.netlify.com/blog/2018/09/13/how-to-run-express.js-apps-with-netlify-functions/
我用 create-react-app that I build in ./client/build
and serve with express 创建了一个网站。
使用 express,我还设置了 2 个基本的 API 路由,它们在 dev (localhost) 中运行良好。
我要在 netlify 上部署什么?
我用 react-scripts
构建客户端
然后我手动将环境设置为 production 以使用 webpack
在本地构建服务器
最后,我将我的代码推送到我的 GitLab 存储库,它会自动触发部署。
使用 Netlify 部署的设置:
Build command: Not set
Publish directory: client/build
问题
当我尝试使用我的 API 路线之一时,出现错误 404 ...
项目
我的项目结构是这样的:
| package.json
| server.js
| webpack.config.js
+---API
| dbHandler.js
| routesHandler.js
+---client
| | package.json
| +---src
| | | App.js
| | | ...
| +---node_modules
| +---public
| | index.html
| | manifest.json
| | _redirects
| \---build
| | index.html
| | manifest.json
| | _redirects
| | asset-manifest.json
| | favicon_rings.ico
| | service-worker.js
| \---static
| +---media
| | ...
| +---js
| | main.1e7ccdbf.js
| | main.1e7ccdbf.js.map
| \---css
| main.fc8f2d26.css
| main.fc8f2d26.css.map
+---public
| _redirects
\---bundles
bundle.js
代码
摘自./package.json
:
"main": "server.js",
"homepage": "https://custom.netlify.com",
"scripts": {
"start": "node server.js",
"build": "webpack --config webpack.config.js",
"postinstall": "npm run build",
"start:dev": "nodemon -e js --exec babel-node -- ./server.js"
}
./client/package.json
:
"homepage": "https://custom.netlify.com",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build && cp build/index.html build/404.html",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
},
"proxy": "http://localhost:5000"
./client/build/_redirects
和 ./public/_redirects
:
# Redirects from what the browser requests to what we serve
/* /index.html 200
./server.js
:
import webpackDevMiddleware from "webpack-dev-middleware";
import webpackHotMiddleware from "webpack-hot-middleware";
import routesHandler from "./API/routesHandler";
import config from "./webpack.config";
import bodyParser from "body-parser";
import webpack from "webpack";
import express from "express";
import path from "path";
const port = process.env.PORT || 5000;
const ExpressServer = express();
const CLI_BUILD_DIR = path.join(__dirname, "client/build");
const HTML_BUNDLE = path.join(CLI_BUILD_DIR, "index.html");
const compiler = webpack(config);
const isDevelopment = process.env.NODE_ENV === "development";
ExpressServer.use(bodyParser.urlencoded({ extended: true }));
ExpressServer.use(bodyParser.json());
ExpressServer.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept"
);
next();
});
if (isDevelopment) {
ExpressServer.use(
webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath
})
);
ExpressServer.use(webpackHotMiddleware(compiler));
} else {
// Serve static files from the React app
ExpressServer.use(express.static(CLI_BUILD_DIR));
}
// Define API routes
var router = express.Router();
router.post("/register", routesHandler.register);
router.post("/login", routesHandler.login);
// Put all API endpoints under '/API'
ExpressServer.use("/API", router);
// All other routes will be directed to the main page of user interface
ExpressServer.get("/", (req, res) => res.sendFile(HTML_BUNDLE));
ExpressServer.get("*", (req, res) => res.redirect(301, "/"));
// Start the server
ExpressServer.listen(port, function() {
console.log("Express server listening on http://localhost:" + port);
});
./webpack.config.js
:
const path = require('path');
const webpack = require('webpack');
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
module.exports = {
entry: [
'webpack-hot-middleware/client',
'./API/routesHandler.js',
'./API/dbHandler.js',
'./server.js'
],
externals: {
jquery: 'jQuery',
'react/addons': 'react',
'react/lib/ExecutionEnvironment': 'react',
'react/lib/ReactContext': 'react',
},
output: {
path: path.resolve(__dirname, 'bundles'),
filename: 'bundle.js',
publicPath: '/public',
sourceMapFilename: 'bundle.map',
},
devtool: process.env.NODE_ENV === 'production'
? undefined : 'cheap-module-eval-source-map',
resolve: {
modules: ['node_modules', './client/build', './API', '.'],
extensions: ['.js', '.jsx', '.css'],
},
module: {
rules: [
{
test: /(\.js$|\.jsx$)/,
exclude: /(node_modules|bower_components)/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['react', 'es2015', 'stage-0', 'airbnb'],
},
},
],
},
{
test: /(\.css$|\.scss$)/,
use: ['style-loader', 'css-loader', 'sass-loader'],
},
{
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|jpeg|gif)(\?v=\d+\.\d+\.\d+)?$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[ext]'
}
}
}
],
},
node: {
console: false,
fs: 'empty',
fsevents: 'empty'
},
plugins: [
new webpack.IgnorePlugin(/fs/)],
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
minimize: true,
compressor: {
warnings: false,
},
}),
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV)
},
}),
],
};
Netlify 用于静态文件托管 - 您不能将 Express 应用程序部署到它,只能部署到您的前端。您需要为 API 托管使用不同的服务(例如 DigitalOcean 或 Heroku)。如果您想使用他们的 CDN 进行响应缓存,则可以使用 redirects 通过 Netlify 将流量路由到该 API 服务器。
编辑: 这不再完全正确 - Netlify 现在也有 support for deploying AWS Lambda functions,允许 JavaScript 和 Go 成为 运行服务器端。
今年早些时候 Netlify 添加了对 AWS Lamdba 函数的支持,现在这实际上是很有可能的。这里有一些资源供那些现在发现这个问题的人使用。
https://www.netlify.com/docs/functions/
https://www.netlify.com/blog/2018/09/13/how-to-run-express.js-apps-with-netlify-functions/