Today we will be discussing how to build a full stack app with a Node/Express backend and a React frontend
What the frontend is going on??
Node (specify Express) is our backend. Here is Express's job:
- Serving up a static HTML page with our React code. The HTML page itself has only one div:
<div id="root"></div>
- React will run on the frontend to handle all the rendering
- All database transactions (for CRUD actions). We will be using Postgres (but the same concepts apply for any DB)
- Responding (with JSON) to API requests from the the frontend
Locally, Node will be running on port 4200.
React is our frontend. Here is React's job:
- Page rendering
- Frontend routing (via React Router)
- Making AJAX requests (via Axios) to the backend to CRUD data. The client does not talk to our database. Just to our server. The server talks to the database.
Locally, React will be running on port 3000.
Refresher here on why we have a local server to use React
Using Express as our backend and React as our frontend is an alternative to doing all of our rendering from the backend (with EJS or Handlebars). This is more complex but has many advantages.
Having rendering and routing done from the frontend allows us to create SPAs. This is appears faster for the client.
- The client does not have to wait for an entire page to load to see part of a page.
- If a connection is lost, the client can still navigate the app and see any data that does not need to be request via AJAX.
- The browser can also cache the pages with a Service Worker.
create-react-app
does this for us.
Having a separation of concerns can keep our code more organized and is helpful when working on large teams.
- All logic dealing with how data should be displayed lives on the frontend. (Not the case when using a view engine)
- The backend deals with responding to and responding with JSON
- Backend engineers can work on the API and frontend engineers can work rendering. These can even be entirely separate repos (not what we will be doing today but many teams do do this)
Locally we will be using concurrently
. This will allow us to run Express and React from one process! That means only one tab in terminal.
On Heroku we only need to build the React code when we deploy and run the server. The server will be serving up the React code. In order to do this we will have Heroku build all of our React code. It will take all of our Webpack and Babel code and spit out browser ready files.
More info on deploying create-react-app
apps here
It is 100% OK if you don't understand all of it. You may (and should) copy-paste most of what you see here.
For real: understanding how to make the pieces that are specific to your app is much more important than understanding how to get the environments set up. This is a place where it is OK to just copy and paste.
At the top-level our app in an Express app. It has it's own package.json
that looks something like this. The scripts
section is what's important here.
{
"name": "lecture_u03_d06_fullstack_react",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "nodemon server.js",
"start": "concurrently \"npm run dev\" \"npm run client\"",
"server": "node server.js",
"client": "cd client && npm start",
"heroku-postbuild": "cd client/ && npm install && npm install --only=dev --no-shrinkwrap && npm run build"
},
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.18.3",
"concurrently": "^3.5.1",
"express": "^4.16.3",
"pg-promise": "^8.4.4"
},
"devDependencies": {
"nodemon": "^1.17.4"
}
}
Breakdown on each piece here
Inside of the scripts
block:
test
: whatever you tests you want to rundev
: you can run this to run just your server (but you probably won't)start
: in this case will run both the server and clientserver
: will be run by Heroku. Does not run client.client
: you can run this to run just your client (but you probably won't)heroku-postbuild
: definitely copy-paste this one. Heroku will automatically run this command after you deploy. It will build all of your React code and put it inside ofclient/build
directory.
Running npm start
will run both Node and React concurrently! One tab! Go to localhost:3000 and you should see your React app.
As always you need to
npm install
first.
As you can see, this one also has some dependencies we might need for our Node app. These all get here by doing, for example, npm install --save body-parser
inside of the root of our project.
Add a file called Procfile
in the root. Heroku will automatically read and run the web
command when you deploy.
web: npm run server
Your Procfile
will look exactly like the above. This way, locally you can run whatever you want (which in our case is npm start
) but Heroku will run npm run server
(which according our package.json
will run node server.js
).
Your server.js
looks just as we have done it in the past with a few additions.
When Heroku runs scripts.heroku-postbuild
it will put all of the browser-ready React code in the ./client/build
directory. We want all of this to be public.
// ...
const path = require('path');
// ...
// make everything in ./client/build public
app.use(express.static(path.resolve(__dirname, './client/build')));
Then we should end with something like this:
// all routes / routers above here
// fallback
app.get('*', (req, res) => {
if (isXhr(req)) { // if ajax request
// no route found
res.status(404).json({error: 'not found'});
} else { // else, browser request
// let react handle routing client side
res.sendFile(path.resolve(__dirname, './client/build', 'index.html'));
}
});
// https://stackoverflow.com/a/28540611
const isXhr = req => req.xhr || req.headers.accept.indexOf('json') > -1;
// this works for checking Axios requests but not every AJAX library
app.get('*')
is a last chance to give a response when no route matches (so this must go last). In the callback we are checking to see if the request is Xhr/Ajax. If so, we can send a 404
. If not, we can assume it is a browser request and we should let React-Router handle the routing.
All the routes before *
should start with /api
(/api/people
, /api/people/:id
, etc.) This way you can have a route in React /people
that is not the same as an route in your Express app.
Our React code goes inside of a client
directory. It has it's own package.json
. It looks something like this. The proxy
attribute is what's important here. That is what allows us to locally make requests to the backend on a different port. (React is 3000, Express is 4200).
{
"name": "client",
"version": "0.1.0",
"private": true,
"proxy": "http://localhost:4200/",
"dependencies": {
"axios": "^0.18.0",
"react": "^16.3.2",
"react-dom": "^16.3.2",
"react-router-dom": "^4.2.2",
"react-scripts": "1.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
As you can see, this one also has some dependencies we might need for our React app. These all get here by doing, for example, npm install --save axios
inside of the client
directory.
The rest of the code inside of your client
directory is just a React App created with create-react-app
. Since we have it in our Express repo, it should not be it's own repo (no repos inside of repos). So you should create the app with create-react-app
not cra
.
You can make all api requests to your app without ever having to specify the port or domain.
Example:
class ThingsIndex extends Component {
// ...
componentDidMount () {
// do not add port or domain. this will work in both dev and prod! (local and heroku)
axios.get('/api/things').then(res => {
this.setState({things: res.data})
});
}
}
Do yourself a favor. Don't have any other servers running. Close out of all over Terminal and Atom windows. You should have one terminal windows with exactly the three following tabs:
- One running
npm start
from the root directory. That will run Node and React concurrently. - One that stays in the command line in the root directory.
- Here you can run
npm install --save <WHATEVER>
for any packages needed by your server
- Here you can run
- One that stays in the command line in the
client
directory.- Here you can run
npm install --save <WHATEVER>
for any packages needed by your React app
- Here you can run
The order is up to you but pick an order than makes sense to you and stick with it. If you stick with this you are much less likely to feel disorganized.
Let us build a frontend to the Express doggo
app we have been working on. (It should similar to this but it should be it's own repo).
- Close out of all extra Terminal and Atom windows. At most you should have this repo open in Atom and possibly this directory open in terminal.
- In a new Terminal window,
cd
into yourdoggo
Express app. The repo itself should be an App. (Not an app in a subdirectory of the Repo. I.e the root of the repo haspackage.json
). Open up Atom here.
Commit after each step below:
- Make the
scripts
section of yourpackage.json
look like that of this repo.- If your file is called
index.js
instead ofserver.js
that part might change.
- If your file is called
- Add a
Procfile
that looks exactly like this one. npm install --save concurrently
, addpackage.json
/package-lock.json
- Update your
server.js
section to reflect what we saw here - Change the port from 3000 to 4200
- Update your dog router path to be
/api/dogs
instead of just/dogs
- Inside of the root of your project run
create-create-app client
- Do not use
cra
as this creates a new repo.
- Do not use
- Open up a new terminal tab and
cd
into that directory. - Add
"proxy": "http://localhost:4200/",
toclient/package.json
- Order does not matter but keep in mind that there should be a comma at the end of a line if and only if it is followed by another attribute
- Open a new tab.
cd
into the root of your project (not inclient
) and runnpm start
. Go to localhost:3000 and there should be a React App running. - You should now have exactly 3 tabs
- Test that the proxy is working
- We are going to be using
Axios
but as a quick test we can usefetch
- Paste this into the Chrome console in the React tab
fetch('/api/dogs').then(r => r.json()).then(console.log)
- If you get a response with 3 dogs you are good to go!
- We are going to be using
Now you can actually write your React code. Inside of the client
directory:
npm install --save axios
. Add files and commitnpm install --save react-router-dom
. Add files and commit- Create a
/dogs
route. List all the of their names - Create a
/dogs/:id
route. Show information about each dog - Add a link to
/dogs
somewhere that is always visible - Add a link to each dog on the
/dogs
page. - Make this app into your own! Add fun things!
Try deploying your app!
- If this app has already been deployed just run
git push heroku master
. - If you have not yet deployed, follow the instructions from this lecture
- If something goes wrong:
- Check the above steps. Is your
package.json
correct? Yourclient/package.json
? Yourserver.js
? Is yourProfile
checked in? - See if you get any info from
heroku logs --tail
. Read it closely - If something is showing but it's not what you expect, open up the Chrome console. Is an error? A 404? A 500?
- Check the above steps. Is your