Expose API routes with Express when deployed on Netlify

R3uK picture R3uK · Nov 30, 2017 · Viewed 7.1k times · Source

I've created a site with that I build in ./client/build and serve with .

Using , I also setup 2 basic API routes that work well in dev (localhost).

What I do to deploy on ?

I build the client with react-scripts
Then I manually set the environment to production to build locally the server with webpack
Finally I push my code to my GitLab repo, and it triggers the deploy automatically.

Used Netlify deploy's settings :

Build command:         Not set
Publish directory:     client/build

Issue

When I try to use one of my API routes, I get an error 404 ...

Project

My project structure is like this :

|   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 

Code

Extract from ./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 and ./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)
      },
    }),
  ],
};

Answer

Joe Clay picture Joe Clay · Nov 30, 2017

Netlify is for static file hosting - you can't deploy an Express application to it, only your frontend. You'll need to use a different service (such as DigitalOcean or Heroku) for your API hosting. You could potentially then route traffic to that API server via Netlify using redirects if you want to use their CDN for response caching.

Edit: This is no longer entirely true - Netlify now also has support for deploying AWS Lambda functions, allowing JavaScript and Go to be run on the server-side.