Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expose API routes with Express when deployed on Netlify

I've created a site with create-react-app that I build in ./client/build and serve with express.

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

What I do to deploy on netlify ?

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)
      },
    }),
  ],
};
like image 546
R3uK Avatar asked Nov 30 '17 15:11

R3uK


People also ask

Does express work on Netlify?

js on netlify. AWS's serverless Lambda functions open a world of possibilities for running on-demand, server-side code without having to run a dedicated server. Netlify lets you deploy serverless Lambda functions without an AWS account, and with function management handled directly within Netlify.

Can I host API on Netlify?

Netlify also securely stores credentials required for your frontend to connect to remote backend services and can also proxy API requests through to your own domain.

Where can I host Express API?

js express RESTful API, you have many alternatives of hosting platforms such as Heroku, digital ocean, aws, gcp e.t.c which is perfectly fine.


2 Answers

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.

like image 154
Joe Clay Avatar answered Oct 11 '22 05:10

Joe Clay


With Netlify's addition of support AWS Lamdba functions earlier this year, this is actually quite possible now. Here are some resources for those who find this question now.

https://www.netlify.com/docs/functions/

https://www.netlify.com/blog/2018/09/13/how-to-run-express.js-apps-with-netlify-functions/

like image 31
zigzag Avatar answered Oct 11 '22 06:10

zigzag