4

I'm currently working on a project using React (via create-react-app) and JSONServer for the API.

I have a single Git repository structured as follow :

|-- client
|-- api

To work in a dev environment, I start both of the folders with npm start in order to have http://localhost:3000 for the React app and http://localhost:3001 for the API.

This way (thanks to Axios) I can connect to http://localhost:3001/api to retrieve my content

import axios from 'axios'

export default axios.create({
  baseURL: 'http://localhost:3001/api'
})

Everything works great so far.

Now I want to deploy my app and API to Heroku and this is where things get complicated.

So here's my questions and it would be very helpful to have some advice on what is the best way to handle it:

  • Should I create two apps on Heroku ? One for the client and one for the API?
  • If I have two apps on Heroku, how can I reach the API safely from the client? Maybe fetching the url of the API app (like https://myapp.herokuapp.com/api) but I'll have to deal with CORS issues.
  • Or should I create only one app with API and put the client's build in the same folder/url (Eg. : serving public files of the build with JSONServer)

For the moment, I have a server.js file at the root of my client's folder and a Procfile with web: node server.js to launch a server for the build. Then I fetch data with Axios from another Heroku app (which is actually the API app).

Any advice/help will be very appreciated! Thanks!

P.S.: here's my package.json ans server.js files for client and api

Client's server.js (at the root of client's folder)

const express = require('express')
const http = require('http')
const path = require('path')

const app = express()
app.use(express.static(path.join(__dirname, 'build')))

const port = process.env.PORT || '8080'
app.set('port', port)

const server = http.createServer(app)
server.listen(port, () => console.log(`Running on localhost:${port}`))

Api's server.js (at the root of api's folder)

const express = require('express');
const jsonServer = require('json-server');
const router = express.Router();
const server = jsonServer.create();
const mainRoute = jsonServer.router('db.json');
const middlewares = jsonServer.defaults({ noCors: false });
const port = process.env.PORT || 3001;

router.use('/api', mainRoute)
server.use(middlewares);
server.use(router);

server.listen(port);

Client's package.json:

{
  "name": "portfolio",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@emotion/core": "^10.0.28",
    "@emotion/styled": "^10.0.27",
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.5.0",
    "@testing-library/user-event": "^7.2.1",
    "axios": "^0.19.2",
    "express": "^4.17.1",
    "google-spreadsheet": "^3.0.11",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-intl": "^4.6.3",
    "react-lazyload": "^2.6.8",
    "react-router-dom": "^5.2.0",
    "react-scripts": "3.4.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "postinstall": "react-scripts build"
  },
  "proxy": "https://my-api.herokuapp.com/api",
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "eslint-plugin-react": "^7.20.0",
    "react-spring": "^8.0.27",
    "standard": "^14.3.4"
  }
}

Api's package.json:

{
  "name": "json-server-heroku",
  "version": "1.0.0",
  "description": "Simple json-base database to deploy to Heroku",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js",
    "fetch": "node fetch-data.js"
  },
  "keywords": [
    "json-server,heroku, node, REST API"
  ],
  "author": "Jesper Orb",
  "license": "ISC",
  "dependencies": {
    "google-spreadsheet": "^3.0.11",
    "json-server": "^0.16.1"
  }
}
user990463
  • 439
  • 1
  • 7
  • 27
  • Can you show your whole directory structure so that we can see if your server's `server.js` and `package.json` files for server is at the root folder, or inside the api folder? – onuriltan Jul 06 '20 at 07:59
  • Yes sure! I added them to the post. – user990463 Jul 06 '20 at 08:17
  • So you don't have package.json at the root level? There are 2 package.json files inside api and client separately right? – onuriltan Jul 06 '20 at 08:19
  • Yes, I have one package.json file for each (one in client's folder and one in api's folder). Both of these at the root level. But there's is nothing at the level 0 (where client and api folders exists). – user990463 Jul 06 '20 at 08:33
  • I can suggest a way if you want to use only one heroku app – onuriltan Jul 06 '20 at 08:34
  • I heard that is a good practice to separate Client and API in Heroku just in case if one of them is slow or in maintenance. – user990463 Jul 06 '20 at 08:36
  • Correct, if you only go with one app scalability is an issue in general. But you mentioned there is an actual api app already so I guess you need to only scale that since the other app doing the main work? – onuriltan Jul 06 '20 at 08:40
  • Yes, that is correct. – user990463 Jul 06 '20 at 08:58

1 Answers1

6

I see you are using json server, so you don't need to use express to serve your static assets, instead you need to move your build folder to root level of the project and rename it as public so that json-server will see it and serve it according to their markdown on github

  1. Move your api/package.json and server.js to the root level of your application.
  2. In order to move your client/build folder(after running npm run build from ./client) and rename it as public, add a heroku-postbuild script to the root package.json like this "heroku-postbuild": "cd client && npm install && npm install --only=dev --no-shrinkwrap && npm run build && mv -v build/ .. && cd .. && mv build public"
  3. add your main api url's to your react app as

client/.env.development

REACT_APP_BASE_URL=http://localhost:4000/api

client/.env.production

REACT_APP_BASE_URL=https://myapp.herokuapp.com/api
  1. and let the json-server serve your static frontend files automatically.
onuriltan
  • 3,730
  • 4
  • 25
  • 37
  • 1
    Thank you for that thorough explanation! – user990463 Jul 06 '20 at 09:12
  • Hey, sorry to bother again but the deployment on Heroku went fine until I get a 404 error (nginx). Is it related to the file 'server.js' which is still in the folder 'api' and not at the root of the project? – user990463 Jul 06 '20 at 19:13
  • Which approach did you follow? – onuriltan Jul 06 '20 at 19:15
  • If you follow the second approach you need separate git repositories for client and server since heroku will only see one app at a time – onuriltan Jul 06 '20 at 19:23
  • The first one, with only one app. – user990463 Jul 06 '20 at 19:42
  • 1
    To be more precise, the client works well, but not the API. It tries to connect to 'localhost/3001/api' and I suspect Axios 'baseURL' which is set to: 'process.env.baseURL || 'http://localhost:3001/api'' – user990463 Jul 06 '20 at 20:07
  • Or maybe the proxy in package.json which is set to: "proxy": "http://localhost:30001/api", like in your post. – user990463 Jul 06 '20 at 20:09
  • You can delete `proxy:` from `client/package.json` and use environmental variables to determine base api url instead – onuriltan Jul 07 '20 at 08:16
  • I've tried but I cannot get your first solution working on Heroku (even if it's working locally). – user990463 Jul 07 '20 at 16:27
  • Yes: GET https://myapp.herokuapp.com/api/nav 404 (Not Found) (anonymous) @ VM81:1 (anonymous) @ 2.c49c5944.chunk.js:2 e.exports @ 2.c49c5944.chunk.js:2 e.exports @ 2.c49c5944.chunk.js:2 Promise.then (async) l.request @ 2.c49c5944.chunk.js:2 r.forEach.l. @ 2.c49c5944.chunk.js:2 (anonymous) @ 2.c49c5944.chunk.js:2 (anonymous) @ main.b6762cad.chunk.js:1 l @ 2.c49c5944.chunk.js:2 (anonymous) @ 2.c49c5944.chunk.js:2 ... I have 404 for every API calls – user990463 Jul 08 '20 at 07:17
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/217437/discussion-between-onuriltan-and-user990463). – onuriltan Jul 08 '20 at 07:19